Skip to content

Commit 8a08d02

Browse files
committed
Stop SSE recording task after flush and avoid shadowing self
1 parent a43e2c4 commit 8a08d02

1 file changed

Lines changed: 18 additions & 14 deletions

File tree

Sources/Replay/Playback.swift

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public final class PlaybackURLProtocol: URLProtocol, @unchecked Sendable {
206206
// URLProtocol predates Swift concurrency; wrap self to cross isolation boundary
207207
let sendableSelf = UnsafeSendable(value: self)
208208
streamTask = Task {
209-
let `self` = sendableSelf.value
209+
let urlProtocol = sendableSelf.value
210210
do {
211211
var matchingRequest = markedRequest
212212

@@ -228,10 +228,10 @@ public final class PlaybackURLProtocol: URLProtocol, @unchecked Sendable {
228228

229229
switch disposition {
230230
case .recorded(let response, let data):
231-
self.client?.urlProtocol(
232-
self, didReceive: response, cacheStoragePolicy: .notAllowed)
233-
self.client?.urlProtocol(self, didLoad: data)
234-
self.client?.urlProtocolDidFinishLoading(self)
231+
urlProtocol.client?.urlProtocol(
232+
urlProtocol, didReceive: response, cacheStoragePolicy: .notAllowed)
233+
urlProtocol.client?.urlProtocol(urlProtocol, didLoad: data)
234+
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
235235

236236
case .error(let error):
237237
throw error
@@ -247,19 +247,20 @@ public final class PlaybackURLProtocol: URLProtocol, @unchecked Sendable {
247247
config.timeoutIntervalForResource = .infinity
248248
let session = URLSession(configuration: config, delegate: delegate, delegateQueue: nil)
249249
let dataTask = session.dataTask(with: matchingRequest)
250+
let sendableDataTask = UnsafeSendable(value: dataTask)
250251

251252
// Initialize dataStream BEFORE starting the request
252253
let dataStream = delegate.dataStream
253254

254255
// Store task reference for cancellation
255-
self.urlSessionTask = dataTask
256+
urlProtocol.urlSessionTask = dataTask
256257
dataTask.resume()
257258

258259
// Wait for response
259260
let httpResponse = try await delegate.waitForResponse()
260261

261-
self.client?.urlProtocol(
262-
self, didReceive: httpResponse, cacheStoragePolicy: .notAllowed)
262+
urlProtocol.client?.urlProtocol(
263+
urlProtocol, didReceive: httpResponse, cacheStoragePolicy: .notAllowed)
263264

264265
// Stream data incrementally, collecting for potential recording
265266
// Use a class to allow capturing in the finish handler
@@ -268,13 +269,13 @@ public final class PlaybackURLProtocol: URLProtocol, @unchecked Sendable {
268269
var finished = false
269270
}
270271
let state = StreamState()
271-
let protocolId = ObjectIdentifier(self)
272+
let protocolId = ObjectIdentifier(urlProtocol)
272273
let capturedRequest = matchingRequest
273274
let capturedResponse = httpResponse
274275

275276
// Register finish handler for flush()
276277
if shouldRecord {
277-
await store.registerStreamingProtocol(id: protocolId) { [state] in
278+
await store.registerStreamingProtocol(id: protocolId) { [state, sendableDataTask] in
278279
guard !state.finished, !state.collectedData.isEmpty else { return }
279280
state.finished = true
280281
let duration = Date().timeIntervalSince(startTime)
@@ -291,16 +292,19 @@ public final class PlaybackURLProtocol: URLProtocol, @unchecked Sendable {
291292
"Replay: Failed to record response for \(requestDescription(capturedRequest)): \(error)"
292293
)
293294
}
295+
296+
// Stop the underlying stream so it doesn't keep accumulating data after `flush()` is called
297+
sendableDataTask.value.cancel()
294298
}
295299
}
296300

297301
let urlTask = dataTask
298302
do {
299303
try await withTaskCancellationHandler {
300304
for try await chunk in dataStream {
301-
if Task.isCancelled { break }
305+
if Task.isCancelled || state.finished { break }
302306
state.collectedData.append(chunk)
303-
self.client?.urlProtocol(self, didLoad: chunk)
307+
urlProtocol.client?.urlProtocol(urlProtocol, didLoad: chunk)
304308
}
305309
} onCancel: {
306310
urlTask.cancel()
@@ -330,11 +334,11 @@ public final class PlaybackURLProtocol: URLProtocol, @unchecked Sendable {
330334
}
331335

332336
session.invalidateAndCancel()
333-
self.client?.urlProtocolDidFinishLoading(self)
337+
urlProtocol.client?.urlProtocolDidFinishLoading(urlProtocol)
334338
}
335339
} catch {
336340
if !Task.isCancelled {
337-
self.client?.urlProtocol(self, didFailWithError: error)
341+
urlProtocol.client?.urlProtocol(urlProtocol, didFailWithError: error)
338342
}
339343
}
340344
}

0 commit comments

Comments
 (0)