Skip to content

[RUM-16961] Fix URLSession network tracking when usesClassicLoadingMode is false#3017

Draft
mariedm wants to merge 4 commits into
developfrom
mariedm/RUM-16961/nw-urlsession-task-tracking
Draft

[RUM-16961] Fix URLSession network tracking when usesClassicLoadingMode is false#3017
mariedm wants to merge 4 commits into
developfrom
mariedm/RUM-16961/nw-urlsession-task-tracking

Conversation

@mariedm

@mariedm mariedm commented Jun 24, 2026

Copy link
Copy Markdown
Member

What and why?

On iOS/tvOS 18.4+, setting URLSessionConfiguration.usesClassicLoadingMode = false switches URLSession to a Network.framework-backed task stack that returns NWURLSessionTask instances instead of __NSCFLocalSessionTask. Our existing swizzles only targeted __NSCFLocalSessionTask, so all network tracking silently stopped for sessions using this configuration.

Fixes #3012.

How?

Added two inner classes to the existing swizzlers — no changes to public API or the NetworkInstrumentationFeature interface:

  • NWTaskResume (in URLSessionTaskSwizzler): swizzles NWURLSessionTask.resume alongside the classic task, using the same interceptResume callback.
  • NWTaskComplete (in URLSessionTaskStateSwizzler): swizzles NWURLSessionTask.completeTaskWithError: (the equivalent of setState: on the new stack) and maps it to a completed-state transition.

Both return nil on iOS/tvOS < 18.4 if NWURLSessionTask is not present, so there is no impact on existing behavior.

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 — N/A, no public API changes
  • Run make api-surface when adding new APIs — N/A, no new APIs

@mariedm mariedm force-pushed the mariedm/RUM-16961/nw-urlsession-task-tracking branch 12 times, most recently from f3c935b to 3ef258f Compare June 25, 2026 10:15
@mariedm mariedm force-pushed the mariedm/RUM-16961/nw-urlsession-task-tracking branch from 3ef258f to d3f1e8a Compare June 25, 2026 11:20
let notifyInterceptionDidStart = expectation(description: "Notify interception did start")
let notifyInterceptionDidComplete = expectation(description: "Notify interception did complete")
let server = ServerMock(delivery: .success(response: .mockResponseWith(statusCode: 200), data: .mock(ofSize: 10)))
let server = ServerMock(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The skipIsMainThreadCheck changes are unrelated to this issue but should reduce flakiness.

@mariedm mariedm marked this pull request as ready for review June 26, 2026 09:38
@mariedm mariedm requested review from a team as code owners June 26, 2026 09:38

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f94238880

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +122 to +123
intercept(task, error)
previousImplementation(task, Self.selector, error)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Call the original NW completion before finishing

When an NWURLSessionTask without a completion handler fails or completes through this hook, this is the only path that calls task(_:didChangeToState:). Because intercept enqueues completion work that reads task.response, task.error, and countOfBytesReceived, running it before previousImplementation lets the SDK finish and remove the interception before Foundation has populated those fields; the same ordering exists in the retryable hook below. That can emit automatic resources missing the error/status/size for usesClassicLoadingMode = false requests, so call the original completion first or pass the completion data through before finishing.

Useful? React with 👍 / 👎.

defer { lock.unlock() }
taskSetState = try TaskSetState.build()
taskSetState?.swizzle(intercept: interceptSetState)
// NWURLSessionTask bypasses setState:; hook into its completion method instead.

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.

It seems there are some use cases where the completion method completeTaskWithError: is not fired:

  • NW tasks without completion handlers.
  • successful async/await session.data(from:)

let session = URLSession(configuration: configuration)
defer { session.invalidateAndCancel() }

// When - no completion handler: relies entirely on NWTaskComplete swizzle for end tracking

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.

Since url is https://localhost:1, this test only validates the failure path

@mariedm mariedm marked this pull request as draft June 26, 2026 11:37
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.

RUM network resources not tracked when URLSessionConfiguration.usesClassicLoadingMode = false (iOS 18.4+)

2 participants