From 405e23217ddb2dcdc109cea0593f55f94bb0391a Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Tue, 20 Jan 2026 01:27:12 -0500 Subject: [PATCH 1/6] fix: SDK stuck when login with restricted external ID --- .../operations/impl/executors/LoginUserOperationExecutor.kt | 2 ++ .../user/internal/operations/LoginUserOperationExecutorTests.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt index 46968b3e71..b9c39902e1 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt @@ -237,6 +237,8 @@ internal class LoginUserOperationExecutor( ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) NetworkUtils.ResponseStatusType.UNAUTHORIZED -> ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) + NetworkUtils.ResponseStatusType.INVALID -> + ExecutionResponse(ExecutionResult.FAIL_NORETRY) else -> ExecutionResponse(ExecutionResult.FAIL_PAUSE_OPREPO) } diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt index d80dc5531a..307a66544e 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt @@ -159,7 +159,7 @@ class LoginUserOperationExecutorTests : FunSpec({ val response = loginUserOperationExecutor.execute(operations) // Then - response.result shouldBe ExecutionResult.FAIL_PAUSE_OPREPO + response.result shouldBe ExecutionResult.FAIL_NORETRY coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) } } From d26af101817623798bd47eeaf4cda83028b67442 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Tue, 20 Jan 2026 11:43:49 -0500 Subject: [PATCH 2/6] Update test with backend error 400 --- .../LoginUserOperationExecutorTests.kt | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt index 307a66544e..ec49d8bad4 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt @@ -136,7 +136,7 @@ class LoginUserOperationExecutorTests : FunSpec({ coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) } } - test("login anonymous user fails with no retry when backend error condition exists") { + test("login anonymous user fails with no retry when hit with backend error 404") { // Given val mockUserBackendService = mockk() coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } throws BackendException(404, "NOT FOUND") @@ -158,6 +158,33 @@ class LoginUserOperationExecutorTests : FunSpec({ // When val response = loginUserOperationExecutor.execute(operations) + // Then + response.result shouldBe ExecutionResult.FAIL_PAUSE_OPREPO + coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) } + } + + test("login anonymous user fails with no retry when hit with backend error 400") { + // Given + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } throws BackendException(400, "INVALID") + + val mockIdentityOperationExecutor = mockk() + + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockSubscriptionsModelStore = mockk() + + val loginUserOperationExecutor = + LoginUserOperationExecutor(mockIdentityOperationExecutor, AndroidMockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext()) + val operations = + listOf( + LoginUserOperation(appId, localOneSignalId, null, null), + createSubscriptionOperation, + ) + + // When + val response = loginUserOperationExecutor.execute(operations) + // Then response.result shouldBe ExecutionResult.FAIL_NORETRY coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) } From 0c2afc53c65264387b1ea946162fb24dd4ab1689 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Thu, 29 Jan 2026 11:51:15 -0500 Subject: [PATCH 3/6] fix: add new execution result for invalid login Co-Authored-By: AR Abdul Azeez --- .../core/internal/operations/IOperationExecutor.kt | 7 +++++++ .../core/internal/operations/impl/OperationRepo.kt | 1 + .../impl/executors/LoginUserOperationExecutor.kt | 2 +- .../internal/operations/LoginUserOperationExecutorTests.kt | 4 ++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationExecutor.kt index 4bae598969..b91cbfe67c 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationExecutor.kt @@ -77,6 +77,13 @@ enum class ExecutionResult { */ FAIL_CONFLICT, + /** + * Used in special create user case. + * The operation failed due to invalid arguments (eg. restricted external ID is used.) + * We should not retry the operation as the external ID should not be used again. + */ + FAIL_INVALID_LOGIN, + /** * Used in special create user case. * The operation failed due to a non-retryable error. Pause the operation repo diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt index 4439b688e3..058a0db4c9 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt @@ -270,6 +270,7 @@ internal class OperationRepo( } ExecutionResult.FAIL_UNAUTHORIZED, // TODO: Need to provide callback for app to reset JWT. For now, fail with no retry. ExecutionResult.FAIL_NORETRY, + ExecutionResult.FAIL_INVALID_LOGIN, ExecutionResult.FAIL_CONFLICT, -> { Logging.error("Operation execution failed without retry: $operations") diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt index b9c39902e1..9adfbeefac 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt @@ -238,7 +238,7 @@ internal class LoginUserOperationExecutor( NetworkUtils.ResponseStatusType.UNAUTHORIZED -> ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) NetworkUtils.ResponseStatusType.INVALID -> - ExecutionResponse(ExecutionResult.FAIL_NORETRY) + ExecutionResponse(ExecutionResult.FAIL_INVALID_LOGIN) else -> ExecutionResponse(ExecutionResult.FAIL_PAUSE_OPREPO) } diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt index ec49d8bad4..92d2696c25 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt @@ -163,7 +163,7 @@ class LoginUserOperationExecutorTests : FunSpec({ coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) } } - test("login anonymous user fails with no retry when hit with backend error 400") { + test("login anonymous user fails with LOGIN_INVALID when hit with backend error 400") { // Given val mockUserBackendService = mockk() coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } throws BackendException(400, "INVALID") @@ -186,7 +186,7 @@ class LoginUserOperationExecutorTests : FunSpec({ val response = loginUserOperationExecutor.execute(operations) // Then - response.result shouldBe ExecutionResult.FAIL_NORETRY + response.result shouldBe ExecutionResult.FAIL_INVALID_LOGIN coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) } } From 3bf45400e8d0e097b3d39b8338936ffc683e6663 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Fri, 30 Jan 2026 12:20:39 -0500 Subject: [PATCH 4/6] add a comment Co-Authored-By: AR Abdul Azeez --- .../operations/impl/executors/LoginUserOperationExecutor.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt index 9adfbeefac..32a8af0f2b 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt @@ -134,6 +134,8 @@ internal class LoginUserOperationExecutor( ) createUser(loginUserOp, operations) } + // For all other errors, the request will be dropped and will not create the user + // e.g. ExecutionResult.FAIL_INVALID_LOGIN else -> ExecutionResponse(result.result) } } From ec1ef0308331e4cb3020a0272fd0b7bb8bbaa462 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Mon, 2 Feb 2026 12:31:19 -0500 Subject: [PATCH 5/6] fix: remove set FAIL_PAUSE_OPERPO Co-Authored-By: AR Abdul Azeez --- .../internal/operations/IOperationExecutor.kt | 7 ------- .../internal/operations/impl/OperationRepo.kt | 16 ---------------- .../impl/executors/LoginUserOperationExecutor.kt | 4 +--- 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationExecutor.kt index b91cbfe67c..9b9b92c3ed 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationExecutor.kt @@ -83,11 +83,4 @@ enum class ExecutionResult { * We should not retry the operation as the external ID should not be used again. */ FAIL_INVALID_LOGIN, - - /** - * Used in special create user case. - * The operation failed due to a non-retryable error. Pause the operation repo - * and retry on a new session, giving the SDK a chance to recover from the failed user create. - */ - FAIL_PAUSE_OPREPO, } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt index 058a0db4c9..0bab393182 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt @@ -57,7 +57,6 @@ internal class OperationRepo( internal val queue = mutableListOf() private val waiter = WaiterWithValue() private val retryWaiter = WaiterWithValue() - private var paused = false private val initialized = CompletableDeferred() override suspend fun awaitInitialized() { @@ -101,7 +100,6 @@ internal class OperationRepo( } override fun start() { - paused = false scope.launch { // load saved operations first then start processing the queue to ensure correct operation order loadSavedOperations() @@ -182,11 +180,6 @@ internal class OperationRepo( waitForNewOperationAndExecutionInterval() enqueueIntoBucket++ while (true) { - if (paused) { - Logging.debug("OperationRepo is paused") - return - } - val ops = getNextOps(executeBucket) Logging.debug("processQueueForever:ops:\n$ops") @@ -299,15 +292,6 @@ internal class OperationRepo( } } } - ExecutionResult.FAIL_PAUSE_OPREPO -> { - Logging.error("Operation execution failed with eventual retry, pausing the operation repo: $operations") - // keep the failed operation and pause the operation repo from executing - paused = true - // add back all operations to the front of the queue to be re-executed. - synchronized(queue) { - ops.reversed().forEach { queue.add(0, it) } - } - } } // if there are operations provided on the result, we need to enqueue them at the diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt index 32a8af0f2b..d86bb540f5 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt @@ -239,10 +239,8 @@ internal class LoginUserOperationExecutor( ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) NetworkUtils.ResponseStatusType.UNAUTHORIZED -> ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) - NetworkUtils.ResponseStatusType.INVALID -> - ExecutionResponse(ExecutionResult.FAIL_INVALID_LOGIN) else -> - ExecutionResponse(ExecutionResult.FAIL_PAUSE_OPREPO) + ExecutionResponse(ExecutionResult.FAIL_INVALID_LOGIN) } } } From 04d2153cbe0fd49d2097d8429a171200ca11c33e Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Mon, 2 Feb 2026 12:41:29 -0500 Subject: [PATCH 6/6] test: replace FAIL_PAUSE_OPERPO with FAIL_INVLIAD_LOGIN Co-Authored-By: AR Abdul Azeez --- .../user/internal/operations/LoginUserOperationExecutorTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt index 92d2696c25..9b1352ce36 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt @@ -159,7 +159,7 @@ class LoginUserOperationExecutorTests : FunSpec({ val response = loginUserOperationExecutor.execute(operations) // Then - response.result shouldBe ExecutionResult.FAIL_PAUSE_OPREPO + response.result shouldBe ExecutionResult.FAIL_INVALID_LOGIN coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) } }