Skip to content

Commit a422359

Browse files
committed
Factor withCancellableCheckedThrowingContinuation into a separate function
1 parent 47339d4 commit a422359

File tree

5 files changed

+85
-87
lines changed

5 files changed

+85
-87
lines changed

Sources/LanguageServerProtocol/Connection.swift

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -148,33 +148,12 @@ extension Connection {
148148
/// guarantees. If you need to gurantee that messages are sent in-order
149149
/// use the version with a completion handler.
150150
public func send<R: RequestType>(_ request: R) async throws -> R.Response {
151-
let requestIDWrapper = ThreadSafeBox<RequestID?>(initialValue: nil)
152-
153-
@Sendable
154-
func sendCancelNotification() {
155-
/// Take the request ID out of the box. This ensures that we only send the
156-
/// cancel notification once in case the `Task.isCancelled` and the
157-
/// `onCancel` check race.
158-
if let requestID = requestIDWrapper.takeValue() {
159-
self.send(CancelRequestNotification(id: requestID))
151+
return try await withCancellableCheckedThrowingContinuation { continuation in
152+
return self.send(request) { result in
153+
continuation.resume(with: result)
160154
}
155+
} cancel: { requestID in
156+
self.send(CancelRequestNotification(id: requestID))
161157
}
162-
163-
return try await withTaskCancellationHandler(operation: {
164-
try Task.checkCancellation()
165-
return try await withCheckedThrowingContinuation { continuation in
166-
let requestID = self.send(request) { result in
167-
continuation.resume(with: result)
168-
}
169-
requestIDWrapper.value = requestID
170-
171-
// Check if the task was cancelled. This ensures we send a
172-
// CancelNotification even if the task gets cancelled after we register
173-
// the cancellation handler but before we set the `requestID`.
174-
if Task.isCancelled {
175-
sendCancelNotification()
176-
}
177-
}
178-
}, onCancel: sendCancelNotification)
179158
}
180159
}

Sources/SKSupport/AsyncUtils.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
public extension Task {
14+
/// Awaits the value of the result.
15+
///
16+
/// If the current task is cancelled, this will cancel the subtask as well.
17+
var valuePropagatingCancellation: Success {
18+
get async throws {
19+
try await withTaskCancellationHandler {
20+
return try await self.value
21+
} onCancel: {
22+
self.cancel()
23+
}
24+
}
25+
}
26+
}
27+
28+
/// Allows the execution of a cancellable operation that returns the results
29+
/// via a completion handler.
30+
///
31+
/// `operation` must invoke the continuation's `resume` method exactly once.
32+
///
33+
/// If the task executing `withCancellableCheckedThrowingContinuation` gets
34+
/// cancelled, `cancel` is invoked with the handle that `operation` provided.
35+
public func withCancellableCheckedThrowingContinuation<Handle, Result>(
36+
_ operation: (_ continuation: CheckedContinuation<Result, any Error>) -> Handle,
37+
cancel: (Handle) -> Void
38+
) async throws -> Result {
39+
let handleWrapper = ThreadSafeBox<Handle?>(initialValue: nil)
40+
41+
@Sendable
42+
func callCancel() {
43+
/// Take the request ID out of the box. This ensures that we only send the
44+
/// cancel notification once in case the `Task.isCancelled` and the
45+
/// `onCancel` check race.
46+
if let handle = handleWrapper.takeValue() {
47+
cancel(handle)
48+
}
49+
}
50+
51+
return try await withTaskCancellationHandler(operation: {
52+
try Task.checkCancellation()
53+
return try await withCheckedThrowingContinuation { continuation in
54+
handleWrapper.value = operation(continuation)
55+
56+
// Check if the task was cancelled. This ensures we send a
57+
// CancelNotification even if the task gets cancelled after we register
58+
// the cancellation handler but before we set the `requestID`.
59+
if Task.isCancelled {
60+
callCancel()
61+
}
62+
}
63+
}, onCancel: callCancel)
64+
}

Sources/SKSupport/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11

22
add_library(SKSupport STATIC
3+
AsyncUtils.swift
34
BuildConfiguration.swift
45
ByteString.swift
56
dlopen.swift
67
FileSystem.swift
78
LineTable.swift
89
Random.swift
910
Result.swift
10-
Task+ValuePropagatingCancellation.swift
1111
ThreadSafeBox.swift
1212
)
1313
set_target_properties(SKSupport PROPERTIES

Sources/SKSupport/Task+ValuePropagatingCancellation.swift

Lines changed: 0 additions & 26 deletions
This file was deleted.

Sources/SourceKitD/SourceKitD.swift

Lines changed: 15 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -84,45 +84,26 @@ extension SourceKitD {
8484
public func send(_ req: SKDRequestDictionary) async throws -> SKDResponseDictionary {
8585
logRequest(req)
8686

87-
let handleWrapper = ThreadSafeBox<sourcekitd_request_handle_t?>(initialValue: nil)
88-
89-
@Sendable
90-
func cancelSourcekitdRequest() {
91-
/// Take the request ID out of the box. This ensures that we only send the
92-
/// cancel notification once in case the `Task.isCancelled` and the
93-
/// `onCancel` check race.
94-
if let handle = handleWrapper.takeValue() {
95-
api.cancel_request(handle)
96-
}
97-
}
98-
99-
return try await withTaskCancellationHandler(operation: {
100-
try Task.checkCancellation()
101-
return try await withCheckedThrowingContinuation { continuation in
102-
var handle: sourcekitd_request_handle_t?
103-
api.send_request(req.dict, &handle) { [weak self] _resp in
104-
guard let self = self else { return }
105-
106-
let resp = SKDResponse(_resp, sourcekitd: self)
87+
return try await withCancellableCheckedThrowingContinuation { continuation in
88+
var handle: sourcekitd_request_handle_t?
89+
api.send_request(req.dict, &handle) { [weak self] _resp in
90+
guard let self = self else { return }
10791

108-
logResponse(resp)
92+
let resp = SKDResponse(_resp, sourcekitd: self)
10993

110-
guard let dict = resp.value else {
111-
continuation.resume(throwing: resp.error!)
112-
return
113-
}
94+
logResponse(resp)
11495

115-
continuation.resume(returning: dict)
116-
}
117-
handleWrapper.value = handle
118-
// Check if the task was cancelled. This ensures we send a
119-
// CancelNotification even if the task gets cancelled after we register
120-
// the cancellation handler but before we set the request handle.
121-
if Task.isCancelled {
122-
cancelSourcekitdRequest()
96+
guard let dict = resp.value else {
97+
continuation.resume(throwing: resp.error!)
98+
return
12399
}
100+
101+
continuation.resume(returning: dict)
124102
}
125-
}, onCancel: cancelSourcekitdRequest)
103+
return handle
104+
} cancel: { handle in
105+
api.cancel_request(handle)
106+
}
126107
}
127108
}
128109

0 commit comments

Comments
 (0)