Custom URLSession Memory & CPU Usage
Platform
iOS
Environment
Develop, TestFlight
Installed
Swift Package Manager
Version
8.30.0
Xcode Version
15.2
Did it work on previous versions?
No response
Steps to Reproduce
Using the sample URLSession here: https://github.com/getsentry/sentry-cocoa/discussions/4047#discussioncomment-9739280
I added a button that sends an exception and an error in to my app. When I disable the URL Session, everything sends nicely. When I reenable it, I see two things happening: 1- I memory usage continuously increases with 100% CPU usage until it force shuts the app 2- The canonicalRequest function never gets called after the handshake with the backend, no matter how many times SentrySDK.capture(exception:) or SentrySDK.capture(error:) are called
I just have an empty screen with a button to test this and the app reliably always crashes.
Expected Result
No crashes and consistent memory usage similar to the default URLSession.
Actual Result
The app exhausts the memory
class CustomURLProtocol: URLProtocol {
override class func canInit(with request: URLRequest) -> Bool {
return URLProtocol.property(forKey: "Handled", in: request) == nil
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
guard let newUrl = URL(string: "xxx/forwardSentry") else { return request }
var newRequest = URLRequest(url: newUrl)
newRequest.httpBody = request.httpBody
newRequest.httpBodyStream = request.httpBodyStream
newRequest.httpMethod = request.httpMethod
newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields
// Breakpoint here never gets stopped after the first 5 times
return newRequest
}
override func startLoading() {
URLProtocol.setProperty(true, forKey: "Handled", in: request as! NSMutableURLRequest)
let newTask = URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data { self.client?.urlProtocol(self, didLoad: data) }
if let response = response { self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed) }
if let error = error { self.client?.urlProtocol(self, didFailWithError: error) }
self.client?.urlProtocolDidFinishLoading(self)
}
newTask.resume()
}
override func stopLoading() {
// Not implemented on purpose
}
}
@main
struct TestApp: App {
init() {
let config = URLSessionConfiguration.default
config.protocolClasses = [CustomURLProtocol.self]
SentrySDK.start { options in
options.dsn = "https://734ed0faf0e9b359e6490bf0272427bf@o4507339912904704.ingest.us.sentry.io/4507391647547392"
options.urlSession = URLSession(configuration: config)
options.debug = true // Enabled debug when first installing is always helpful
// Enable tracing to capture 100% of transactions for tracing.
// Use 'options.tracesSampleRate' to set the sampling rate.
// We recommend setting a sample rate in production.
options.enableTracing = true
options.tracesSampleRate = 0.005
options.profilesSampleRate = 0.1
options.maxBreadcrumbs = 1
}
}
var body: some Scene {
WindowGroup {
Button {
let id = SentrySDK.capture(exception: NSException(name: .genericException, reason: "Login Failure"))
print(id.description) //ID is correctly populated, but the backend never receives a request, nor the dashboard has an exception when the URL Session is on
} label: {
Text("Sign-in")
.foregroundColor(.white)
.padding(.horizontal, 24)
.padding(.vertical, 12)
.background(RoundedRectangle(cornerRadius: 10).fill(.cyan))
}
}
}
}
Are you willing to submit a PR?
No response
Hello @Memocana, thanks for reaching out. The code snippet provided at #4047 is a starting point where you can continue to write your own custom URLSession. However, we don't have the bandwidth to help you optimize it and handle all the edge cases. Users should be cautious when using a custom URLSession.
We will discuss about providing a safer alternative to implement proxies.
We will try to repro the issue and follow up here.
Hey there! I was able to identify the root cause of the issue on our end. It looks like when the backend sends 200 with body/headers that are missing/wrong the SDK goes in to a frozen state. We worked with our backend guys to make sure the responses are sent the correct way, but the SDK should still be able to handle these scenarios more gracefully.
Can you give an example of wrong header/body that your server was returning?
URLResponse:
{ URL: <tunnel_url> },
{
Status Code: 200,
Headers {
"Content-Length" = (
2
);
"Content-Type" = (
"application/json; charset=utf-8"
);
Date = (
"Fri, 19 Jul 2024 17:12:02 GMT"
);
Etag = (
"W/\"2-vyGp6PvFo4RvsFtPoIWeCReyIC8\""
);
"Strict-Transport-Security" = (
"max-age=15552000; includeSubDomains"
);
"content-security-policy" = (
"default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests"
);
"cross-origin-embedder-policy" = (
"require-corp"
);
"cross-origin-opener-policy" = (
"same-origin"
);
"cross-origin-resource-policy" = (
"same-origin"
);
"origin-agent-cluster" = (
"?1"
);
"referrer-policy" = (
"no-referrer"
);
"x-content-type-options" = (
nosniff
);
"x-dns-prefetch-control" = (
off
);
"x-download-options" = (
noopen
);
"x-frame-options" = (
SAMEORIGIN
);
"x-permitted-cross-domain-policies" = (
none
);
"x-xss-protection" = (
0
);
}
}
This was the initial response we had, and it contained no body. We also tried modifying the body to return { "id": <id> } similarly to how Sentry API returns but the cpu issues persisted even after that until the headers were identical too.
Closing due to inactivity. Please reopen if the problem persists.