Skip to content
This repository was archived by the owner on Sep 15, 2025. It is now read-only.

Commit 2b7d4f6

Browse files
authored
Support Task cancellation (#844)
2 parents 3979cea + 5daedde commit 2b7d4f6

File tree

4 files changed

+73
-30
lines changed

4 files changed

+73
-30
lines changed

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ let package = Package(
1111
targets: [
1212
.binaryTarget(
1313
name: "WordPressKit",
14-
url: "https://github.com/user-attachments/files/20895757/WordPressKit.zip",
15-
checksum: "b08eaf182f0399303aadccb1a6dad6cad294a9c8d123d920889b15950c85e08f"
14+
url: "https://github.com/user-attachments/files/21518814/WordPressKit.zip",
15+
checksum: "a43e82909d851e78dff7fa64edba12635e2e96206c1fcdca075eae02dd4c157e"
1616
),
1717
]
1818
)

Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ SPEC CHECKSUMS:
1313

1414
PODFILE CHECKSUM: c0da9313733b88a1d938ba6a329dd46b895c7dea
1515

16-
COCOAPODS: 1.15.2
16+
COCOAPODS: 1.16.2

Sources/CoreAPI/HTTPClient.swift

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -70,40 +70,46 @@ extension URLSession {
7070
assert(parentProgress.cancellationHandler == nil, "The progress instance's cancellationHandler property must be nil")
7171
}
7272

73-
return await withCheckedContinuation { continuation in
74-
let completion: @Sendable (Data?, URLResponse?, Error?) -> Void = { data, response, error in
75-
let result: WordPressAPIResult<HTTPAPIResponse<Data>, E> = Self.parseResponse(
76-
data: data,
77-
response: response,
78-
error: error,
79-
acceptableStatusCodes: acceptableStatusCodes
80-
)
81-
82-
continuation.resume(returning: result)
83-
}
73+
let taskHolder = TaskHolder()
74+
return await withTaskCancellationHandler {
75+
await withCheckedContinuation { continuation in
76+
let completion: @Sendable (Data?, URLResponse?, Error?) -> Void = { data, response, error in
77+
let result: WordPressAPIResult<HTTPAPIResponse<Data>, E> = Self.parseResponse(
78+
data: data,
79+
response: response,
80+
error: error,
81+
acceptableStatusCodes: acceptableStatusCodes
82+
)
83+
84+
continuation.resume(returning: result)
85+
}
8486

85-
let task: URLSessionTask
87+
let task: URLSessionTask
8688

87-
do {
88-
task = try self.task(for: builder, completion: completion)
89-
} catch {
90-
continuation.resume(returning: .failure(.requestEncodingFailure(underlyingError: error)))
91-
return
92-
}
89+
do {
90+
task = try self.task(for: builder, completion: completion)
91+
} catch {
92+
continuation.resume(returning: .failure(.requestEncodingFailure(underlyingError: error)))
93+
return
94+
}
9395

94-
task.resume()
95-
taskCreated?(task.taskIdentifier)
96+
task.resume()
97+
taskCreated?(task.taskIdentifier)
98+
Task { await taskHolder.assign(task) }
9699

97-
if let parentProgress, parentProgress.totalUnitCount > parentProgress.completedUnitCount {
98-
let pending = parentProgress.totalUnitCount - parentProgress.completedUnitCount
99-
// The Jetpack/WordPress app requires task progress updates to be delievered on the main queue.
100-
let progressUpdator = parentProgress.update(totalUnit: pending, with: task.progress, queue: .main)
100+
if let parentProgress, parentProgress.totalUnitCount > parentProgress.completedUnitCount {
101+
let pending = parentProgress.totalUnitCount - parentProgress.completedUnitCount
102+
// The Jetpack/WordPress app requires task progress updates to be delievered on the main queue.
103+
let progressUpdator = parentProgress.update(totalUnit: pending, with: task.progress, queue: .main)
101104

102-
parentProgress.cancellationHandler = { [weak task] in
103-
task?.cancel()
104-
progressUpdator.cancel()
105+
parentProgress.cancellationHandler = { [weak task] in
106+
task?.cancel()
107+
progressUpdator.cancel()
108+
}
105109
}
106110
}
111+
} onCancel: {
112+
Task { await taskHolder.cancel() }
107113
}
108114
}
109115

@@ -334,3 +340,15 @@ extension URLSession {
334340
self.taskData.count
335341
}
336342
}
343+
344+
private actor TaskHolder {
345+
weak var task: URLSessionTask?
346+
347+
func assign(_ task: URLSessionTask) {
348+
self.task = task
349+
}
350+
351+
func cancel() {
352+
task?.cancel()
353+
}
354+
}

Tests/WordPressKitTests/Tests/Utilities/URLSessionHelperTests.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,31 @@ class URLSessionHelperTests: XCTestCase {
172172
}
173173
}
174174

175+
func testTaskCancellation() async throws {
176+
// Give a slow HTTP request that takes 0.5 second to complete
177+
stub(condition: isPath("/hello")) { _ in
178+
let response = HTTPStubsResponse(data: "success".data(using: .utf8)!, statusCode: 200, headers: nil)
179+
response.responseTime = 0.5
180+
return response
181+
}
182+
183+
let task = Task {
184+
await session.perform(request: .init(url: URL(string: "https://wordpress.org/hello")!), errorType: TestError.self)
185+
}
186+
187+
// and cancelling it (in 0.1 second) before it completes
188+
try await Task.sleep(nanoseconds: 100_000_000)
189+
task.cancel()
190+
191+
// The result should be an cancellation result
192+
let result = await task.value
193+
if case let .failure(.connection(urlError)) = result, urlError.code == .cancelled {
194+
// Do nothing
195+
} else {
196+
XCTFail("Unexpected result: \(result)")
197+
}
198+
}
199+
175200
func testEncodingError() async {
176201
let underlyingError = NSError(domain: "test", code: 123)
177202
let builder = HTTPRequestBuilder(url: URL(string: "https://wordpress.org")!)

0 commit comments

Comments
 (0)