Skip to content

feat: SDK-4176: gate background threading behind remote feature flag#2595

Merged
abdulraqeeb33 merged 3 commits intomainfrom
SDK-4176-anr-ff-features
Mar 26, 2026
Merged

feat: SDK-4176: gate background threading behind remote feature flag#2595
abdulraqeeb33 merged 3 commits intomainfrom
SDK-4176-anr-ff-features

Conversation

@abdulraqeeb33
Copy link
Copy Markdown
Contributor

Summary

  • add a generic feature-flag pipeline using ConfigModel.features and introduce BACKGROUND_THREADING as a next-run feature.
  • gate core threading-dependent behavior (startup scheduling, init/login/logout paths, thread utils, crash uploader, http/prefs helpers) so FF ON uses the new background-threading model and FF OFF preserves legacy behavior.
  • harden startup/feature-resolution paths with guardrails and logging, and add tests for FF ON/OFF execution paths (FeatureManagerTests, ThreadUtilsFeatureFlagTests, StartupServiceTests, crash uploader test updates).

Test plan

  • ./gradlew --console=plain --no-daemon :onesignal:core:testDebugUnitTest --tests "com.onesignal.core.internal.features.FeatureManagerTests" --tests "com.onesignal.common.threading.ThreadUtilsFeatureFlagTests" --tests "com.onesignal.core.internal.startup.StartupServiceTests"
  • ./gradlew --console=plain --no-daemon :onesignal:core:testDebugUnitTest --tests "com.onesignal.internal.OneSignalImpTests" --tests "com.onesignal.core.internal.preferences.PreferencesServiceTests"
  • manual sanity: local override hook in FeatureManager allows forcing BACKGROUND_THREADING for device-side verification.

Made with Cursor

Add a generic feature-manager pipeline backed by ConfigModel.features and gate core startup/init/threading behavior behind BACKGROUND_THREADING, with next-run latching and test coverage for both FF-on and FF-off execution paths.

Made-with: Cursor
Copilot AI review requested due to automatic review settings March 25, 2026 16:17
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a remote-config–driven feature-flag mechanism (ConfigModel.features) and uses it to gate a new background-threading execution model behind the BACKGROUND_THREADING flag, while attempting to preserve legacy behavior when the flag is off.

Changes:

  • Add feature-flag plumbing (ConfigModel.features, backend parsing, FeatureManager, FeatureFlag) and a global ThreadingMode switch.
  • Gate core startup + threading helpers (startup scheduling, thread utils, crash uploader, HTTP/prefs helpers) based on BACKGROUND_THREADING.
  • Add/adjust unit tests to validate feature-flag ON/OFF behavior for FeatureManager, ThreadUtils, startup scheduling, and crash uploader wrapper.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt Adds features: List<String> and JSON list deserialization support.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/IParamsBackendService.kt Extends ParamsObject to include features.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt Parses features array from backend response.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt Hydrates ConfigModel.features from backend params.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureFlag.kt Defines feature flags + activation mode (RUNTIME vs NEXT_RUN).
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/features/FeatureManager.kt Implements feature evaluation and applies threading side-effects.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/ThreadingMode.kt Adds global switch for background-threading mode.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/ThreadUtils.kt Gates coroutine/dispatcher usage vs legacy execution based on ThreadingMode.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/startup/StartupService.kt Gates service start scheduling between OneSignal dispatchers and legacy threads.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/debug/internal/crash/OneSignalCrashUploaderWrapper.kt Gates crash uploader startup threading strategy behind the feature flag.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/impl/PreferencesService.kt Routes async loop to launchOnIO and adds extra logging/guardrails.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt Switches IO execution to launchOnIO helper.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt Gates init/login/logout and service getters based on background-threading flag.
OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt Registers FeatureManager as IFeatureManager.
OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/features/FeatureManagerTests.kt Adds tests for NEXT_RUN behavior and threading-mode side effects.
OneSignalSDK/onesignal/core/src/test/java/com/onesignal/common/threading/ThreadUtilsFeatureFlagTests.kt Adds tests for launchOnIO/Default gating behavior.
OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/startup/StartupServiceTests.kt Updates startup tests to inject a mocked IFeatureManager and adds OFF-path test.
OneSignalSDK/onesignal/core/src/test/java/com/onesignal/debug/internal/crash/OneSignalCrashUploaderWrapperTest.kt Updates tests for new crash uploader wrapper constructor dependency on IFeatureManager.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 188 to 198
val prefsToWrite = getSharedPrefsByName(storeKey)

if (prefsToWrite == null) {
// the assumption here is there is no context yet, but will be. So ensure
// we wake up to try again and persist the preference.
if (!hasLoggedMissingAppContext) {
Logging.warn("OneSignal: PreferencesService app context unavailable, deferring writes")
hasLoggedMissingAppContext = true
}
waiter.wake()
continue
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prefsToWrite == null branch is effectively unreachable with the current getSharedPrefsByName implementation (it dereferences appContext with !! and always returns a SharedPreferences). If the app context is actually unavailable, this code will crash with an NPE before reaching the intended deferral logic/logging. Consider making getSharedPrefsByName safely return null when appContext isn't initialized yet (e.g., catch the NPE/IllegalStateException), or remove the null-handling path here if it can never happen by design.

Copilot uses AI. Check for mistakes.
Comment on lines +307 to 317
if (isBackgroundThreadingEnabled) {
// init in background and return immediately to ensure non-blocking
suspendifyOnIO {
internalInit(context, appId)
}
return true
}

return runBlocking(runtimeIoDispatcher) {
internalInit(context, appId)
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When BACKGROUND_THREADING is disabled, initWithContext now runs internalInit inside runBlocking(...), which blocks the calling thread until initialization completes. Since OneSignal.initWithContext(...) is commonly called from Application.onCreate (main thread), this can regress startup responsiveness and risk ANRs. Consider keeping initWithContext non-blocking regardless of the feature flag (dispatch init work to a background thread/dispatcher) and only gate the internal dispatcher/threading model behind the feature flag.

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +125
// Then
Thread.sleep(50)
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test relies on Thread.sleep(50) to wait for the background Thread { ... } path, which is timing-dependent and can be flaky on slower CI machines. Prefer a deterministic synchronization primitive (e.g., CountDownLatch, CompletableDeferred, or polling verify(timeout = ...) if available) to wait until both start() calls have happened.

Suggested change
// Then
Thread.sleep(50)
// Then - wait deterministically for any async work to complete
awaitIO()

Copilot uses AI. Check for mistakes.
@@ -46,15 +50,29 @@ internal class OneSignalCrashUploaderWrapper(
@Suppress("TooGenericExceptionCaught")
override fun start() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otel should use feature check too?

Copy link
Copy Markdown
Contributor Author

@abdulraqeeb33 abdulraqeeb33 Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already do

we short circuit here

Copy link
Copy Markdown
Contributor

@nan-li nan-li left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, noting that if FF are not returned by the server, we don't use cached values but go back to the default on the next run, correct?

AR Abdul Azeez added 2 commits March 26, 2026 13:56
Make PreferencesService context lookup safely nullable for early startup, replace timing-based startup test waiting with deterministic latch synchronization, and document intentional legacy blocking behavior when BACKGROUND_THREADING is off.

Made-with: Cursor
@abdulraqeeb33 abdulraqeeb33 changed the title SDK-4176: gate background threading behind remote feature flag feature: SDK-4176: gate background threading behind remote feature flag Mar 26, 2026
@abdulraqeeb33 abdulraqeeb33 changed the title feature: SDK-4176: gate background threading behind remote feature flag feat: SDK-4176: gate background threading behind remote feature flag Mar 26, 2026
@abdulraqeeb33 abdulraqeeb33 merged commit c779475 into main Mar 26, 2026
7 of 10 checks passed
@abdulraqeeb33 abdulraqeeb33 deleted the SDK-4176-anr-ff-features branch March 26, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants