Skip to content

fix: suspend-aware active-time watchdog for iOS split-bundle segments#65

Merged
Minnzen merged 3 commits into
mainfrom
fix/splitbundle-ios-active-watchdog
Jun 12, 2026
Merged

fix: suspend-aware active-time watchdog for iOS split-bundle segments#65
Minnzen merged 3 commits into
mainfrom
fix/splitbundle-ios-active-watchdog

Conversation

@huhuanming

Copy link
Copy Markdown
Contributor

Problem

The 30s segment-eval watchdog in ios/SplitBundleLoader.mm used a bare dispatch_after(DISPATCH_TIME_NOW + 30s). When the app was backgrounded/suspended during cold start while a UI segment's eval was still buffered (waiting for the entry bundle to finish), the deadline elapsed during suspension. On foreground resume, the stale watchdog block won the SBLSettleGuard race against the buffered runtime executor — which was ~1ms from succeeding — and false-rejected the segment as SPLIT_BUNDLE_TIMEOUT. The lazy route then hit its error boundary → white screen (matched in production logs: a ~10.5 min gap where the GCD timer only fired on resume).

Fix

Replace the bare dispatch_after with SBLActiveWatchdog:

  • CLOCK_UPTIME_RAW active-time accumulator — only foreground/active time counts toward the 30s, so suspended time never accrues.
  • Cancelable dispatch_source_t polling timer (survives pause/resume, unlike a one-shot dispatch_after).
  • Pauses on UIApplicationWillResignActive (samples the resign instant synchronously so a delayed _queue block can't fold suspended-but-awake time in), resumes with a 500ms grace on DidBecomeActive so the buffered executor flushes first.
  • Fails safe (fireLocked, retryable timeout) if the timer source can't be created — no indefinite hang.
  • SBLSettleGuard remains the sole settle authority; success path cancels the watchdog; all mutable timing state serialized on a private serial queue.

Review

Two adversarial review rounds (Claude + Codex). Round 2 caught the handleWillResignActive synchronous-timestamp bug (a residual instance of the same root cause) — fixed.

Build / rollout

⚠️ Native change — NOT OTA-able. Requires nitrogen codegen → version bump → republish @onekeyfe/react-native-split-bundle-loader → consume in app-monorepo (pod install) → ship with a native build. The JS-side amplifier fix (React.lazy self-heal) ships separately in app-monorepo and IS OTA-able.

Test plan (on-device)

Cold launch → immediately background → wait > watchdog window → foreground. Before: white screen (false SPLIT_BUNDLE_TIMEOUT). After: active-time excludes background, 500ms grace lets the executor flush, no false fire; a genuine entry wedge still times out correctly.

The 30s segment-eval watchdog used a bare dispatch_after; when the app
was suspended during cold start while a segment's eval was buffered, the
deadline elapsed during suspension and the stale watchdog won the
SBLSettleGuard race on resume, false-rejecting the segment as
SPLIT_BUNDLE_TIMEOUT (white screen).

Replace it with SBLActiveWatchdog: a CLOCK_UPTIME_RAW active-time
accumulator + cancelable dispatch_source timer that pauses on
WillResignActive (sampling the resign instant synchronously so suspended
time can't be folded in) and resumes with a 500ms grace on DidBecomeActive,
giving the buffered executor a chance to flush before the watchdog can fire.
Fails safe (fireLocked) if the timer source can't be created.
The active-time watchdog folded its pause on an async _queue block, so a
timer tick already pending on _queue before WillResignActive could run
first on resume — tickLocked recomputes `now` at execution time, so a
suspension-delayed tick saw stale _isActive==YES + the old interval start
and folded suspended-but-awake time, firing a false SPLIT_BUNDLE_TIMEOUT
before the resume grace was armed.

Fold + pause via dispatch_sync so the clock is stopped (and any pending
tick is flushed at a valid pre-suspension instant) before the lifecycle
callback returns. No deadlock: _queue blocks never sync back to main.
@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn Critical
Critical CVE: npm shell-quote quote() does not escape newlines in object .op values

CVE: GHSA-w7jw-789q-3m8p shell-quote quote() does not escape newlines in object .op values (CRITICAL)

Affected versions: >= 1.1.0 < 1.8.4

Patched version: 1.8.4

From: ?npm/react-native@0.83.0npm/@react-native-community/cli-platform-android@20.0.0npm/@react-native-community/cli-platform-ios@20.0.0npm/@react-native-community/cli@20.0.0npm/shell-quote@1.8.3

ℹ Read more on: This package | This alert | What is a critical CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/shell-quote@1.8.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm react-native is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: example/react-native/package.jsonnpm/react-native@0.83.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/react-native@0.83.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm string-width is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/release-it@19.1.0npm/string-width@8.1.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/string-width@8.1.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@Minnzen Minnzen merged commit aebc928 into main Jun 12, 2026
3 checks passed
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.

2 participants