Skip to content

URL Instrumentation prevents Session Delegates from being called in async contexts #744

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
MustafaHaddara opened this issue Apr 7, 2025 · 0 comments · May be fixed by #747
Open

URL Instrumentation prevents Session Delegates from being called in async contexts #744

MustafaHaddara opened this issue Apr 7, 2025 · 0 comments · May be fixed by #747

Comments

@MustafaHaddara
Copy link

MustafaHaddara commented Apr 7, 2025

The Problem

For example, let's say we have this session delegate:

class SessionDelegate: NSObject, URLSessionDataDelegate, URLSessionTaskDelegate {
  var callCount = 0

  func urlSession(
    _ session: URLSession,
    task: URLSessionTask,
    didFinishCollecting metrics: URLSessionTaskMetrics
  ) {
    callCount += 1
    print("delegate called")
  }
}

and then we use this as a delegate for our session and make a synchronous request, everything is fine. For example:

let delegate = SessionDelegate()
let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)

let url = URL(string: "http://httpbin.org/get")!
let request = URLRequest(url: url)

let task = session.dataTask(with: request)
task.resume()

After this runs, delegate.callCount will be 1 and delegate called will be printed in the console. So far so good.

If we switch to making our request async and use this delegate as a delegate for the task, things are still fine. For example:

let session = URLSession(configuration: .default)
let url = URL(string: "http://httpbin.org/get")!
let request = URLRequest(url: url)
do {
    _ = try await session.data(for: request, delegate: delegate)
} catch {
    return
}

Then, again, this works the same as above.

However, if we make the request async and use this delegate as the delegate for the entire session, then things fall apart. That code looks like this:

let session = URLSession(configuration: .default, delegate: delegate, delegateQueue: nil)
let url = URL(string: "http://httpbin.org/get")!
let request = URLRequest(url: url)
do {
    _ = try await session.data(for: request)
} catch {
    return
}

And when this runs, delegate.callCount remains 0 and there is no delegate called message in the console.

Standalone Example

I've reproduced this case in the URLSessionInstrumentationTests and in the standalone Network Sample. My full code change is here.

Potential Solution

It appears that the URLSession instrumentation uses task delegates to manage its instrumentation. It looks at outgoing tasks (ie. here) and, if they don't have a task delegate, it attaches one. However, as far as I can tell, that means the iOS runtime never calls the session delegate, because it thinks the task already has a delegate.

We could update this FakeDelegate to accept the existing session delegate and proxy that. I prototyped this here, and all of the existing tests, as well as the new ones I added in the standalone example above, pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant