Skip to content

Commit 3041c52

Browse files
committed
use the retryAfterSeconds in OperationRepo
Use the retryAfterSeconds to stall processing in the OperationRepo. We wait in the OperationRepo as this allows more operations to be grouped together, as we getGroupableOperations will be called after the delay. There are number of other places that use HTTPClient other than the OperationRepo, we will address those in a follow up commit.
1 parent 71c4960 commit 3041c52

File tree

10 files changed

+87
-12
lines changed

10 files changed

+87
-12
lines changed

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/NetworkUtils.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ object NetworkUtils {
1717
401, 403 -> ResponseStatusType.UNAUTHORIZED
1818
404, 410 -> ResponseStatusType.MISSING
1919
409 -> ResponseStatusType.CONFLICT
20+
429 -> ResponseStatusType.RETRYABLE
2021
else -> ResponseStatusType.RETRYABLE
2122
}
2223
}

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,18 @@ class ConfigModel : Model() {
177177
setLongProperty(::opRepoPostCreateRetryUpTo.name, value)
178178
}
179179

180+
/**
181+
* The number of milliseconds times the number of times FAIL_RETRY
182+
* is returned from an executor for a specific operation. AKA this
183+
* backoff will increase each time we retry a specific operation
184+
* by this value.
185+
*/
186+
var opRepoDefaultFailRetryBackoff: Long
187+
get() = getLongProperty(::opRepoDefaultFailRetryBackoff.name) { 15_000 }
188+
set(value) {
189+
setLongProperty(::opRepoDefaultFailRetryBackoff.name, value)
190+
}
191+
180192
/**
181193
* The minimum number of milliseconds required to pass to allow the fetching of IAM to occur.
182194
*/

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import kotlinx.coroutines.launch
1818
import kotlinx.coroutines.newSingleThreadContext
1919
import kotlinx.coroutines.withTimeoutOrNull
2020
import java.util.UUID
21+
import kotlin.math.max
2122
import kotlin.reflect.KClass
2223

2324
internal class OperationRepo(
@@ -221,6 +222,7 @@ internal class OperationRepo(
221222
}
222223
}
223224

225+
var highestRetries = 0
224226
when (response.result) {
225227
ExecutionResult.SUCCESS -> {
226228
// on success we remove the operation from the store and wake any waiters
@@ -248,7 +250,6 @@ internal class OperationRepo(
248250
ExecutionResult.FAIL_RETRY -> {
249251
Logging.error("Operation execution failed, retrying: $operations")
250252
// add back all operations to the front of the queue to be re-executed.
251-
var highestRetries = 0
252253
synchronized(queue) {
253254
ops.reversed().forEach {
254255
if (++it.retries > highestRetries) {
@@ -257,7 +258,6 @@ internal class OperationRepo(
257258
queue.add(0, it)
258259
}
259260
}
260-
delayBeforeRetry(highestRetries)
261261
}
262262
ExecutionResult.FAIL_PAUSE_OPREPO -> {
263263
Logging.error("Operation execution failed with eventual retry, pausing the operation repo: $operations")
@@ -282,6 +282,8 @@ internal class OperationRepo(
282282
}
283283
}
284284
}
285+
286+
delayBeforeNextExecution(highestRetries, response.retryAfterSeconds)
285287
} catch (e: Throwable) {
286288
Logging.log(LogLevel.ERROR, "Error attempting to execute operation: $ops", e)
287289

@@ -291,8 +293,18 @@ internal class OperationRepo(
291293
}
292294
}
293295

294-
suspend fun delayBeforeRetry(retries: Int) {
295-
val delayFor = retries * 15_000L
296+
/**
297+
* Wait which ever is longer, retryAfterSeconds returned by the server,
298+
* or based on the retry count.
299+
*/
300+
suspend fun delayBeforeNextExecution(
301+
retries: Int,
302+
retryAfterSeconds: Int?,
303+
) {
304+
Logging.debug("retryAfterSeconds: $retryAfterSeconds")
305+
val retryAfterSecondsNonNull = retryAfterSeconds?.toLong() ?: 0L
306+
val delayForOnRetries = retries * _configModelStore.model.opRepoDefaultFailRetryBackoff
307+
val delayFor = max(delayForOnRetries, retryAfterSecondsNonNull * 1_000)
296308
if (delayFor < 1) return
297309
Logging.error("Operations being delay for: $delayFor ms")
298310
delay(delayFor)

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class OperationRepoTests : FunSpec({
143143
// Given
144144
val mocks = Mocks()
145145
val opRepo = mocks.operationRepo
146-
coEvery { opRepo.delayBeforeRetry(any()) } just runs
146+
coEvery { opRepo.delayBeforeNextExecution(any(), any()) } just runs
147147
coEvery {
148148
mocks.executor.execute(any())
149149
} returns ExecutionResponse(ExecutionResult.FAIL_RETRY) andThen ExecutionResponse(ExecutionResult.SUCCESS)
@@ -166,7 +166,7 @@ class OperationRepoTests : FunSpec({
166166
it[0] shouldBe operation
167167
},
168168
)
169-
opRepo.delayBeforeRetry(1)
169+
opRepo.delayBeforeNextExecution(1, null)
170170
mocks.executor.execute(
171171
withArg {
172172
it.count() shouldBe 1
@@ -177,6 +177,31 @@ class OperationRepoTests : FunSpec({
177177
}
178178
}
179179

180+
test("delays processing queue by retryAfterSeconds from the last executor's results") {
181+
// Given
182+
val mocks = Mocks()
183+
val opRepo = mocks.operationRepo
184+
coEvery {
185+
mocks.executor.execute(any())
186+
} returns ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = 1) andThen ExecutionResponse(ExecutionResult.SUCCESS)
187+
188+
// When
189+
opRepo.start()
190+
opRepo.enqueue(mockOperation())
191+
val response1 =
192+
withTimeoutOrNull(999) {
193+
opRepo.enqueueAndWait(mockOperation())
194+
}
195+
val response2 =
196+
withTimeoutOrNull(100) {
197+
opRepo.enqueueAndWait(mockOperation())
198+
}
199+
200+
// Then
201+
response1 shouldBe null
202+
response2 shouldBe true
203+
}
204+
180205
test("enqueue operation executes and is removed when executed after fail") {
181206
// Given
182207
val mocks = Mocks()

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,14 @@ class IdentityOperationExecutorTests : FunSpec({
5656
test("execution of set alias operation with network timeout") {
5757
// Given
5858
val mockIdentityBackendService = mockk<IIdentityBackendService>()
59-
coEvery { mockIdentityBackendService.setAlias(any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT")
59+
coEvery {
60+
mockIdentityBackendService.setAlias(
61+
any(),
62+
any(),
63+
any(),
64+
any(),
65+
)
66+
} throws BackendException(408, "TIMEOUT", retryAfterSeconds = 10)
6067

6168
val mockIdentityModelStore = MockHelper.identityModelStore()
6269
val mockBuildUserService = mockk<IRebuildUserService>()
@@ -71,6 +78,7 @@ class IdentityOperationExecutorTests : FunSpec({
7178

7279
// Then
7380
response.result shouldBe ExecutionResult.FAIL_RETRY
81+
response.retryAfterSeconds shouldBe 10
7482
}
7583

7684
test("execution of set alias operation with non-retryable error") {

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/LoginUserOperationExecutorTests.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class LoginUserOperationExecutorTests : FunSpec({
8787
test("login anonymous user fails with retry when network condition exists") {
8888
// Given
8989
val mockUserBackendService = mockk<IUserBackendService>()
90-
coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT")
90+
coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT", retryAfterSeconds = 10)
9191

9292
val mockIdentityOperationExecutor = mockk<IdentityOperationExecutor>()
9393

@@ -96,14 +96,25 @@ class LoginUserOperationExecutorTests : FunSpec({
9696
val mockSubscriptionsModelStore = mockk<SubscriptionModelStore>()
9797

9898
val loginUserOperationExecutor =
99-
LoginUserOperationExecutor(mockIdentityOperationExecutor, MockHelper.applicationService(), MockHelper.deviceService(), mockUserBackendService, mockIdentityModelStore, mockPropertiesModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), MockHelper.languageContext())
99+
LoginUserOperationExecutor(
100+
mockIdentityOperationExecutor,
101+
MockHelper.applicationService(),
102+
MockHelper.deviceService(),
103+
mockUserBackendService,
104+
mockIdentityModelStore,
105+
mockPropertiesModelStore,
106+
mockSubscriptionsModelStore,
107+
MockHelper.configModelStore(),
108+
MockHelper.languageContext(),
109+
)
100110
val operations = listOf<Operation>(LoginUserOperation(appId, localOneSignalId, null, null))
101111

102112
// When
103113
val response = loginUserOperationExecutor.execute(operations)
104114

105115
// Then
106116
response.result shouldBe ExecutionResult.FAIL_RETRY
117+
response.retryAfterSeconds shouldBe 10
107118
coVerify(exactly = 1) { mockUserBackendService.createUser(appId, mapOf(), any(), any()) }
108119
}
109120

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ class RefreshUserOperationExecutorTests : FunSpec({
177177
test("refresh user fails with retry when there is a network condition") {
178178
// Given
179179
val mockUserBackendService = mockk<IUserBackendService>()
180-
coEvery { mockUserBackendService.getUser(appId, IdentityConstants.ONESIGNAL_ID, remoteOneSignalId) } throws BackendException(408)
180+
coEvery {
181+
mockUserBackendService.getUser(appId, IdentityConstants.ONESIGNAL_ID, remoteOneSignalId)
182+
} throws BackendException(408, retryAfterSeconds = 10)
181183

182184
// Given
183185
val mockIdentityModelStore = MockHelper.identityModelStore()
@@ -203,6 +205,7 @@ class RefreshUserOperationExecutorTests : FunSpec({
203205

204206
// Then
205207
response.result shouldBe ExecutionResult.FAIL_RETRY
208+
response.retryAfterSeconds shouldBe 10
206209
coVerify(exactly = 1) {
207210
mockUserBackendService.getUser(appId, IdentityConstants.ONESIGNAL_ID, remoteOneSignalId)
208211
}

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class SubscriptionOperationExecutorTests : FunSpec({
9393
test("create subscription fails with retry when there is a network condition") {
9494
// Given
9595
val mockSubscriptionBackendService = mockk<ISubscriptionBackendService>()
96-
coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(408)
96+
coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(408, retryAfterSeconds = 10)
9797

9898
val mockSubscriptionsModelStore = mockk<SubscriptionModelStore>()
9999
val mockBuildUserService = mockk<IRebuildUserService>()
@@ -127,6 +127,7 @@ class SubscriptionOperationExecutorTests : FunSpec({
127127

128128
// Then
129129
response.result shouldBe ExecutionResult.FAIL_RETRY
130+
response.retryAfterSeconds shouldBe 10
130131
coVerify(exactly = 1) {
131132
mockSubscriptionBackendService.createSubscription(
132133
appId,

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ class UpdateUserOperationExecutorTests : FunSpec({
311311
test("update user single operation fails with MISSING, but isInMissingRetryWindow") {
312312
// Given
313313
val mockUserBackendService = mockk<IUserBackendService>()
314-
coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } throws BackendException(404)
314+
coEvery { mockUserBackendService.updateUser(any(), any(), any(), any(), any(), any()) } throws BackendException(404, retryAfterSeconds = 10)
315315

316316
// Given
317317
val mockIdentityModelStore = MockHelper.identityModelStore()
@@ -337,5 +337,6 @@ class UpdateUserOperationExecutorTests : FunSpec({
337337

338338
// Then
339339
response.result shouldBe ExecutionResult.FAIL_RETRY
340+
response.retryAfterSeconds shouldBe 10
340341
}
341342
})

OneSignalSDK/onesignal/testhelpers/src/main/java/com/onesignal/mocks/MockHelper.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ object MockHelper {
4848
configModel.opRepoPostWakeDelay = 1
4949
configModel.opRepoPostCreateDelay = 1
5050
configModel.opRepoPostCreateRetryUpTo = 1
51+
configModel.opRepoDefaultFailRetryBackoff = 1
5152

5253
configModel.appId = DEFAULT_APP_ID
5354

0 commit comments

Comments
 (0)