Skip to content

Commit 3dd1e84

Browse files
AR Abdul Azeezcursoragent
andcommitted
chore: [SDK-4213] remove background-threading FF, make async init default
The sdk_background_threading remote flag is fully rolled out, so the background/async path is now the unconditional default and all FF gating is removed: - Drop SDK_BACKGROUND_THREADING from FeatureFlag and its FeatureManager side-effect; delete ThreadingMode. - Collapse all ThreadUtils branches to the OneSignalDispatchers path and rename runOnSerialIOIfBackgroundThreading -> runOnSerialIO. - OneSignalImp: remove isBackgroundThreadingEnabled, the featureManager field, the injectable ioDispatcher param, and the now-dead requireInitForOperation / warnIfBlockingOnMainThread. initWithContext always dispatches init asynchronously, eliminating the FF/cached-config read from the init decision path. - StartupService / OneSignalCrashUploaderWrapper: always run async. - Update tests to the default async behavior; rename ThreadUtilsFeatureFlagTests -> ThreadUtilsDispatchTests. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 6d73438 commit 3dd1e84

26 files changed

Lines changed: 274 additions & 1004 deletions

File tree

Lines changed: 26 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1-
@file:Suppress("GlobalCoroutineUsage")
2-
31
package com.onesignal.common.threading
42

53
import com.onesignal.debug.internal.logging.Logging
64
import kotlinx.coroutines.Dispatchers
7-
import kotlinx.coroutines.GlobalScope
85
import kotlinx.coroutines.Job
9-
import kotlinx.coroutines.launch
10-
import kotlinx.coroutines.runBlocking
116
import kotlinx.coroutines.withContext
12-
import kotlin.concurrent.thread
137

148
/**
159
* Modernized ThreadUtils that leverages OneSignalDispatchers for better thread management.
@@ -30,26 +24,11 @@ import kotlin.concurrent.thread
3024
*
3125
*/
3226
fun suspendifyOnMain(block: suspend () -> Unit) {
33-
if (ThreadingMode.useBackgroundThreading) {
34-
OneSignalDispatchers.launchOnIO {
35-
try {
36-
withContext(Dispatchers.Main) { block() }
37-
} catch (e: Exception) {
38-
Logging.error("Exception in suspendifyOnMain", e)
39-
}
40-
}
41-
return
42-
}
43-
44-
thread {
27+
OneSignalDispatchers.launchOnIO {
4528
try {
46-
runBlocking {
47-
withContext(Dispatchers.Main) {
48-
block()
49-
}
50-
}
29+
withContext(Dispatchers.Main) { block() }
5130
} catch (e: Exception) {
52-
Logging.error("Exception on thread with switch to main", e)
31+
Logging.error("Exception in suspendifyOnMain", e)
5332
}
5433
}
5534
}
@@ -101,7 +80,7 @@ fun suspendifyOnDefault(block: suspend () -> Unit) {
10180
/**
10281
* Runs [block] on the single-thread serial IO dispatcher. Tasks from any thread execute
10382
* one-at-a-time in submission order — the entry point for lifecycle handlers that need to
104-
* preserve event ordering. Always routes off-main regardless of [ThreadingMode.useBackgroundThreading].
83+
* preserve event ordering.
10584
*
10685
* Capture time-sensitive state (timestamps, "current" snapshots) on the caller's thread
10786
* before invoking — the block itself may run later under load.
@@ -117,17 +96,12 @@ fun suspendifyOnSerialIO(block: suspend () -> Unit) {
11796
}
11897

11998
/**
120-
* FF-gated rollout helper for lifecycle offload work. When [ThreadingMode.useBackgroundThreading]
121-
* is on, dispatches [block] to the serial IO thread; when off, runs it inline so the control
122-
* cohort retains the original behavior. The block is non-suspending so the FF-off branch doesn't
123-
* need a [kotlinx.coroutines.runBlocking].
99+
* Dispatches lifecycle offload work to the serial IO thread. The block is non-suspending so
100+
* callers can hand off plain (non-coroutine) work to the single serial IO worker, preserving
101+
* submission order across lifecycle events.
124102
*/
125-
fun runOnSerialIOIfBackgroundThreading(block: () -> Unit) {
126-
if (ThreadingMode.useBackgroundThreading) {
127-
suspendifyOnSerialIO { block() }
128-
} else {
129-
block()
130-
}
103+
fun runOnSerialIO(block: () -> Unit) {
104+
suspendifyOnSerialIO { block() }
131105
}
132106

133107
/**
@@ -143,30 +117,9 @@ fun suspendifyWithCompletion(
143117
block: suspend () -> Unit,
144118
onComplete: (() -> Unit)? = null,
145119
) {
146-
if (ThreadingMode.useBackgroundThreading) {
147-
if (useIO) {
148-
OneSignalDispatchers.launchOnIO {
149-
try {
150-
block()
151-
onComplete?.invoke()
152-
} catch (e: Exception) {
153-
Logging.error("Exception in suspendifyWithCompletion", e)
154-
}
155-
}
156-
} else {
157-
OneSignalDispatchers.launchOnDefault {
158-
try {
159-
block()
160-
onComplete?.invoke()
161-
} catch (e: Exception) {
162-
Logging.error("Exception in suspendifyWithCompletion", e)
163-
}
164-
}
165-
}
166-
return
167-
}
168-
169-
GlobalScope.launch(if (useIO) Dispatchers.IO else Dispatchers.Default) {
120+
val launch: (suspend () -> Unit) -> Job =
121+
if (useIO) OneSignalDispatchers::launchOnIO else OneSignalDispatchers::launchOnDefault
122+
launch {
170123
try {
171124
block()
172125
onComplete?.invoke()
@@ -191,32 +144,9 @@ fun suspendifyWithErrorHandling(
191144
onError: ((Exception) -> Unit)? = null,
192145
onComplete: (() -> Unit)? = null,
193146
) {
194-
if (ThreadingMode.useBackgroundThreading) {
195-
if (useIO) {
196-
OneSignalDispatchers.launchOnIO {
197-
try {
198-
block()
199-
onComplete?.invoke()
200-
} catch (e: Exception) {
201-
Logging.error("Exception in suspendifyWithErrorHandling", e)
202-
onError?.invoke(e)
203-
}
204-
}
205-
} else {
206-
OneSignalDispatchers.launchOnDefault {
207-
try {
208-
block()
209-
onComplete?.invoke()
210-
} catch (e: Exception) {
211-
Logging.error("Exception in suspendifyWithErrorHandling", e)
212-
onError?.invoke(e)
213-
}
214-
}
215-
}
216-
return
217-
}
218-
219-
GlobalScope.launch(if (useIO) Dispatchers.IO else Dispatchers.Default) {
147+
val launch: (suspend () -> Unit) -> Job =
148+
if (useIO) OneSignalDispatchers::launchOnIO else OneSignalDispatchers::launchOnDefault
149+
launch {
220150
try {
221151
block()
222152
onComplete?.invoke()
@@ -235,21 +165,11 @@ fun suspendifyWithErrorHandling(
235165
* @return Job that can be used to wait for completion with .join()
236166
*/
237167
fun launchOnIO(block: suspend () -> Unit): Job {
238-
return if (ThreadingMode.useBackgroundThreading) {
239-
OneSignalDispatchers.launchOnIO {
240-
try {
241-
block()
242-
} catch (e: Exception) {
243-
Logging.error("Exception in launchOnIO", e)
244-
}
245-
}
246-
} else {
247-
GlobalScope.launch(Dispatchers.IO) {
248-
try {
249-
block()
250-
} catch (e: Exception) {
251-
Logging.error("Exception in launchOnIO", e)
252-
}
168+
return OneSignalDispatchers.launchOnIO {
169+
try {
170+
block()
171+
} catch (e: Exception) {
172+
Logging.error("Exception in launchOnIO", e)
253173
}
254174
}
255175
}
@@ -261,22 +181,12 @@ fun launchOnIO(block: suspend () -> Unit): Job {
261181
* @param block The suspending code to execute
262182
* @return Job that can be used to wait for completion with .join()
263183
*/
264-
fun launchOnDefault(block: suspend () -> Unit): kotlinx.coroutines.Job {
265-
return if (ThreadingMode.useBackgroundThreading) {
266-
OneSignalDispatchers.launchOnDefault {
267-
try {
268-
block()
269-
} catch (e: Exception) {
270-
Logging.error("Exception in launchOnDefault", e)
271-
}
272-
}
273-
} else {
274-
GlobalScope.launch(Dispatchers.Default) {
275-
try {
276-
block()
277-
} catch (e: Exception) {
278-
Logging.error("Exception in launchOnDefault", e)
279-
}
184+
fun launchOnDefault(block: suspend () -> Unit): Job {
185+
return OneSignalDispatchers.launchOnDefault {
186+
try {
187+
block()
188+
} catch (e: Exception) {
189+
Logging.error("Exception in launchOnDefault", e)
280190
}
281191
}
282192
}

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/ThreadingMode.kt

Lines changed: 0 additions & 25 deletions
This file was deleted.

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/background/impl/BackgroundManager.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import android.content.ComponentName
3232
import android.content.Context
3333
import android.content.pm.PackageManager
3434
import androidx.core.content.ContextCompat
35-
import com.onesignal.common.threading.runOnSerialIOIfBackgroundThreading
35+
import com.onesignal.common.threading.runOnSerialIO
3636
import com.onesignal.core.internal.application.IApplicationLifecycleHandler
3737
import com.onesignal.core.internal.application.IApplicationService
3838
import com.onesignal.core.internal.background.IBackgroundManager
@@ -75,11 +75,11 @@ internal class BackgroundManager(
7575
// main thread for seconds (SDK-4505). Offload to SerialIO so a rapid unfocus -> focus burst
7676
// still runs cancel-then-schedule in submission order. FF gates the rollout.
7777
override fun onFocus(firedOnSubscribe: Boolean) {
78-
runOnSerialIOIfBackgroundThreading { cancelSyncTask() }
78+
runOnSerialIO { cancelSyncTask() }
7979
}
8080

8181
override fun onUnfocused() {
82-
runOnSerialIOIfBackgroundThreading { scheduleBackground() }
82+
runOnSerialIO { scheduleBackground() }
8383
}
8484

8585
private fun scheduleBackground() {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import com.onesignal.common.modeling.ISingletonModelStoreChangeHandler
44
import com.onesignal.common.modeling.ModelChangeTags
55
import com.onesignal.common.modeling.ModelChangedArgs
66
import com.onesignal.common.threading.OneSignalDispatchers
7-
import com.onesignal.common.threading.runOnSerialIOIfBackgroundThreading
7+
import com.onesignal.common.threading.runOnSerialIO
88
import com.onesignal.core.internal.application.IApplicationLifecycleHandler
99
import com.onesignal.core.internal.application.IApplicationService
1010
import com.onesignal.core.internal.backend.IFeatureFlagsBackendService
@@ -74,11 +74,11 @@ internal class FeatureFlagsRefreshService(
7474
// OneSignalDispatchers consumer and pay the lazy-chain init cost on the main thread
7575
// (SDK-4506). SerialIO also preserves order with the matching onUnfocused cancel.
7676
override fun onFocus(firedOnSubscribe: Boolean) {
77-
runOnSerialIOIfBackgroundThreading { restartForegroundPolling() }
77+
runOnSerialIO { restartForegroundPolling() }
7878
}
7979

8080
override fun onUnfocused() {
81-
runOnSerialIOIfBackgroundThreading {
81+
runOnSerialIO {
8282
// Qualify `this` so we lock on the service instance (same monitor
8383
// restartForegroundPolling acquires), not on the no-receiver lambda.
8484
synchronized(this@FeatureFlagsRefreshService) {

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureFlag.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,6 @@ enum class FeatureFlag(
2424
val key: String,
2525
val activationMode: FeatureActivationMode
2626
) {
27-
// Threading mode is selected once per app startup to avoid mixed-mode behavior mid-session.
28-
// Remote key (lowercase) must match backend / Turbine flag id.
29-
SDK_BACKGROUND_THREADING(
30-
"sdk_background_threading",
31-
FeatureActivationMode.APP_STARTUP
32-
),
33-
3427
/** JWT signing of SDK requests. IMMEDIATE so a kill-switch doesn't need a cold start. */
3528
SDK_IDENTITY_VERIFICATION(
3629
"sdk_identity_verification",

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureManager.kt

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.onesignal.core.internal.features
33
import com.onesignal.common.modeling.ISingletonModelStoreChangeHandler
44
import com.onesignal.common.modeling.ModelChangeTags
55
import com.onesignal.common.modeling.ModelChangedArgs
6-
import com.onesignal.common.threading.ThreadingMode
76
import com.onesignal.core.internal.backend.impl.FeatureFlagsJsonParser
87
import com.onesignal.core.internal.config.ConfigModel
98
import com.onesignal.core.internal.config.ConfigModelStore
@@ -153,17 +152,12 @@ internal class FeatureManager(
153152
}
154153
}
155154

155+
@Suppress("UNUSED_PARAMETER")
156156
private fun applySideEffects(
157157
feature: FeatureFlag,
158158
enabled: Boolean,
159159
) {
160160
when (feature) {
161-
FeatureFlag.SDK_BACKGROUND_THREADING ->
162-
ThreadingMode.updateUseBackgroundThreading(
163-
enabled = enabled,
164-
source = "FeatureManager:${feature.activationMode}"
165-
)
166-
167161
// SDK_IDENTITY_VERIFICATION has no side effect: IdentityVerificationService
168162
// reads featureStates directly via isEnabled() at gate-check time.
169163
FeatureFlag.SDK_IDENTITY_VERIFICATION -> {}
@@ -174,10 +168,10 @@ internal class FeatureManager(
174168
/**
175169
* Local-only test hook for forcing features ON without backend config.
176170
* Add feature keys here while testing locally, e.g.:
177-
* setOf(FeatureFlag.SDK_BACKGROUND_THREADING.key)
171+
* setOf(FeatureFlag.SDK_IDENTITY_VERIFICATION.key)
178172
*/
179173
private val localFeatureOverrides: Set<String> = emptySet()
180174
// private val localFeatureOverrides: Set<String> =
181-
// setOf(FeatureFlag.SDK_BACKGROUND_THREADING.key)
175+
// setOf(FeatureFlag.SDK_IDENTITY_VERIFICATION.key)
182176
}
183177
}

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/startup/StartupService.kt

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package com.onesignal.core.internal.startup
22

33
import com.onesignal.common.services.ServiceProvider
44
import com.onesignal.common.threading.OneSignalDispatchers
5-
import com.onesignal.core.internal.features.FeatureFlag
6-
import com.onesignal.core.internal.features.IFeatureManager
75
import com.onesignal.debug.internal.logging.Logging
86

97
internal class StartupService(
@@ -16,35 +14,14 @@ internal class StartupService(
1614
// schedule to start all startable services using OneSignal dispatcher
1715
@Suppress("TooGenericExceptionCaught")
1816
fun scheduleStart() {
19-
val useBackgroundThreading =
20-
try {
21-
val featureManager = services.getService<IFeatureManager>()
22-
featureManager.isEnabled(FeatureFlag.SDK_BACKGROUND_THREADING)
23-
} catch (t: Throwable) {
24-
Logging.warn("OneSignal: Failed to resolve BACKGROUND_THREADING in StartupService. Falling back to legacy thread.", t)
25-
false
26-
}
27-
28-
if (useBackgroundThreading) {
29-
OneSignalDispatchers.launchOnDefault {
30-
services.getAllServices<IStartableService>().forEach { startableService ->
31-
try {
32-
startableService.start()
33-
} catch (t: Throwable) {
34-
Logging.error("OneSignal: Startable service failed: ${startableService::class.java.simpleName}", t)
35-
}
17+
OneSignalDispatchers.launchOnDefault {
18+
services.getAllServices<IStartableService>().forEach { startableService ->
19+
try {
20+
startableService.start()
21+
} catch (t: Throwable) {
22+
Logging.error("OneSignal: Startable service failed: ${startableService::class.java.simpleName}", t)
3623
}
3724
}
38-
} else {
39-
Thread {
40-
services.getAllServices<IStartableService>().forEach { startableService ->
41-
try {
42-
startableService.start()
43-
} catch (t: Throwable) {
44-
Logging.error("OneSignal: Startable service failed: ${startableService::class.java.simpleName}", t)
45-
}
46-
}
47-
}.start()
4825
}
4926
}
5027
}

0 commit comments

Comments
 (0)