Repository navigation
refactor: [SDK-5581] split OneSignalDispatchers for readability and testability - #2809
Merged
Merged
Conversation
Both pool modes now read lane sizes, queue caps, thread names, priorities and fallbacks from one Lane enum and build executors through one newLaneExecutor. Bootstrap still prestarts core threads; lazy does not. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Concurrent first callers already serialize on the StartupFlagsLatch lock, so choosing outside poolsLock unblocked nobody and needed a build-then-discard path. Pools are now chosen once under the lock. A choice failure is logged after publishing, so a log listener that launches work reuses the published pools instead of re-entering the choice. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…lDispatchers Status, metrics and awaitReadyForTest move to DispatcherDiagnostics.kt as extensions; choosing pools from the startup flags moves to PoolChoice.kt. OneSignalDispatchers keeps the launch API, the pools latch and failure logging. Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
📊 Diff Coverage ReportDiff Coverage Report (Changed Lines Only)Gate: aggregate coverage on changed executable lines must be ≥ 80% (JaCoCo line data for lines touched in the diff). Changed Files Coverage
Overall (aggregate gate)161/164 touched executable lines covered (98.2% — requires ≥ 80%) |
Degraded pool choices now go through the report path after publishing. The re-entry test drives that path, and uses unpublishPoolsForTest instead of reflecting on poolsRef. Co-authored-by: Cursor <cursoragent@cursor.com>
The Run decision is built once when a lane turns READY and cleared on close, so the dispatch hot path allocates nothing. Co-authored-by: Cursor <cursoragent@cursor.com>
abdulraqeeb33
marked this pull request as ready for review
October 21, 2026 19:57
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Multi-model review (Opus 5.5, GPT 5.6 Sol, Grok 4.7)
Lane settings match the old dual copies. Cached GateDecision.Run is only set with READY and cleared on close. Post-publish logging is the right place for listener re-entry.
Act on
publishFirstPoolswritespoolsRefonly afterchoosePoolsreturns, andpoolsLockis reentrant. A same-thread launch during choice publishes generation B, then the outer.also { poolsRef = it.pools }replaces it with A and leaks B. The base publish checkedexisting != nullandshutdown()the loser. The new listener test only launches fromrecordChoice, which already ran after publish on the base, so it does not pin this window.
Consider
- First-use flag read (
StartupFlagsCache/ SharedPreferences) now holdspoolsLock. The old comment kept that I/O outside the lock so otherlaunchOn*callers were not stuck behind it. Loser pools were never started, so widening the lock is not what prevents a second live set.
Noted
recordChoice/reportChoiceToTelemetrystill have a pre-existing check-then-set that can drop a degraded report (Opus + Sol).unpublishPoolsForTestdoes not resetprewarmStartedor pending telemetry (Grok).choosePoolsnow snapshotsbootstrapEnabledForTestbeforebeforePoolChoice(Grok). No current test relies on the old order.
Dismissed
run!!NPE onREADY:READYis only set together withrun.- Per-dispatch allocation: the
READYpath already allocated nothing on the base.
Sent by Cursor Automation: PR Reviews
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # OneSignalSDK/onesignal/core/src/main/java/com/onesignal/common/threading/OneSignalDispatchers.kt # OneSignalSDK/onesignal/core/src/test/java/com/onesignal/common/threading/DispatcherBootstrapFlagTests.kt
…enters Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Adapt to the readability split: the choice handoff hook lives in DispatcherTestHooks, and the Fallback readiness check moves to DispatcherDiagnostics. Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
|
LGTM, nothing blocking:
|
…est hooks first, keep currentPools private Co-authored-by: Cursor <cursoragent@cursor.com>
fadi-george
approved these changes
Oct 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Description
One line summary
Split
OneSignalDispatchers.ktinto focused files and gather its test hooks into one object. No behavior change except one latch fix and one allocation fix.Details
Stacked on #2805. Each commit stands alone and can be reviewed on its own.
Why
OneSignalDispatchers.ktis 602 lines on main and 378 at the head of feat: [SDK-5581] gate dispatcher bootstrap behind sdk_dispatcher_bootstrap #2805, with test hooks mixed in among the production code.What
0354c92b3Each lane is defined once inDispatcherLanes.kt, shared by both pool modes.7d58f125bThe six test hooks move intoDispatcherTestHooks.kt. They are still read on the production path, and are null outside tests.c7332599ePools are chosen under the pools lock, and a degraded choice is logged only after publishing. Before, that log could launch work that re-entered the latch and built a second set of pools. The test inDispatcherFailureLogTestsdrives this through the telemetry report path and fails (stack overflow) when the report moves back before publishing.5ccab9251Comments trimmed to constraints.f5c12ad6cGateDispatcher.dispatchreturns one decision instead of three mutable flags.dc5e27ea7builds theRundecision once per READY transition, so a ready dispatch allocates nothing.9d8fcc9c9Diagnostics and pool choice move toDispatcherDiagnostics.ktandPoolChoice.kt.The re-entry test uses
unpublishPoolsForTest()instead of reflecting onpoolsRef.If a pool choice ever re-enters (the pools lock is reentrant), the pools the inner call published are kept and the outer choice is shut down, as on #2805. A test in
DispatcherFailureLogTestsre-enters frombeforePoolChoiceand fails without the guard.Result
OneSignalDispatchers.ktis 206 lines: the public launch API, the latch, and the choice report. The threading package goes from 1,427 lines at the head of #2805 to 1,336.Testing