Skip to content

fix: RUM Session inflation on prewarmed iOS apps - #3088

Open
marco-saia-datadog wants to merge 1 commit into
developfrom
marcosaia/fix/prewarm-sessions-inflation
Open

fix: RUM Session inflation on prewarmed iOS apps#3088
marco-saia-datadog wants to merge 1 commit into
developfrom
marcosaia/fix/prewarm-sessions-inflation

Conversation

@marco-saia-datadog

Copy link
Copy Markdown
Member

What and why?

Fixes RUM session inflation on prewarmed iOS apps.

PR #2299 (RUM-8372, first shipped in dd-sdk-ios 3.0.0) restricted the ApplicationLaunch view guard so it can only ever pass on RUMSDKInitCommand, whereas before that PR the same guard could pass on any later command once the app left the background.

We have received complaints of orphaned sessions in React Native SDK since the introduction of iOS v3. We believe that the cause is the application launch guard now permanently fails for any launch where the SDK initializes while the app is still backgrounded, and the code marks the app "activated" (applicationActive = true) before even checking whether the guard passed, the SDK gives up retrying and the session sits with zero views until it hits the 15-minute inactivity timeout and closes. This produces an empty near-zero-duration orphan session whenever no other RUM event arrives in that window.

How?

In RUMApplicationScope.swift:

1. Move applicationActive = true after the check in startApplicationLaunchView.

    private func startApplicationLaunchView(on command: RUMCommand, context: DatadogContext, writer: Writer) {
-        applicationActive = true
        
        let isUserLaunch = context.launchInfo.launchReason == .userLaunch
        let isPrewarmed = context.launchInfo.launchReason == .prewarming
        let isBackgroundLaunch = context.launchInfo.launchReason == .backgroundLaunch
        let isStartedInForeground = command is RUMSDKInitCommand && context.applicationStateHistory.currentState != .background
        guard isUserLaunch || (isPrewarmed && isStartedInForeground) || (isBackgroundLaunch && isStartedInForeground) else {
            return
        }

+       applicationActive = true       

Previously, applicationActive was set to true before the guard that decides if the view should start, now it's set only when the guard passes.

When the SDK initializes while the app is still in the background (the prewarming case that causes the bug), the guard fails and applicationActive now stays false, so the SDK keeps retrying on each new command. This means the first startView (or similar) command still finds Session A alive and keeps using it, instead of Session A expiring with no views.

2. Stop the retry once a session actually gets renewed

Since applicationActive can now stay false for longer, we need to make sure the retry stops once it's no longer needed. We set applicationActive = true at the start of refresh() and startNewSession(), which are the two places that create a new session after the previous one expired or was stopped.

3. Fix a side effect: don't recreate the initial session by mistake

There's an older piece of code that creates a session if none exists yet and applicationActive is false (meant for a case where, in theory, no session was ever created). With change 1, applicationActive can now stay false well into the app's life, and this old code could accidentally trigger again after a session already expired (e.g. when a session times out on a background/foreground lifecycle event). This would cause two problems: the new session woiuld get the wrong "start reason" (appears as a fresh prewarm launch instead of a session that timed out), and it would trigger a telemetry log meant to catch a bug.

That old code path now only runs if no session has ever been created (didCreateInitialSessionCount == 0). If a session already existed and expired, the correct path (startNewSession()) takes over instead, with the right start reason and no false error log.

Validation

We are unable to verify that this fix resolves the prewarming-scenario issues in a sample app, since iOS provides no reliable way to force a genuine prewarming condition on demand. We only rely on unit tests passing for this PR.

Review checklist

  • Feature or bugfix MUST have appropriate tests (unit, integration)
  • Make sure each commit and the PR mention the Issue number or JIRA reference
  • Add CHANGELOG entry for user facing changes
  • Add Objective-C interface for public APIs - see our guidelines (internal)
  • Run make api-surface when adding new APIs

@marco-saia-datadog
marco-saia-datadog requested review from a team as code owners July 24, 2026 13:42

@datadog-prod-us1-5 datadog-prod-us1-5 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Datadog Autotest: PASS

More details

The prewarmed-session state changes preserve the existing command flow: the initial session remains available until the first user event, while expired sessions renew with the correct inactivity precondition and stop the deferred initialization path. Apple-platform execution was unavailable in this sandbox, so confidence is based on the diff, call graph, and successful Swift parsing.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Datadog Autotest · Commit 7659ec1 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

@ncreated ncreated left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice catch on the root cause 👌 . One general comment on test coverage: the new unit tests exercise RUMApplicationScope in isolation with hand-built DatadogContext/command sequences, which makes it hard to tell whether the exact scenario being fixed is reachable through the real app lifecycle.

Datadog/IntegrationUnitTests/RUM/RUMSessionTimeOutTests.swift already has testGivenBackgroundSession_whenItTimesOut_andNextEventIsTrackedInForeground, which covers a closely related prewarm + timeout + foreground-transition flow using the AppRunner framework from #2299. Could we add a case in that neighborhood that reproduces the specific inflation scenario this PR fixes (SDK init while backgrounded, silent period past the session timeout, then a real event)? That would give us an integration-level regression test alongside the unit tests, and confirm the fix addresses something that actually happens end-to-end rather than only a state reachable by calling RUMApplicationScope directly.

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