Skip to content

Commit d7ade55

Browse files
committed
Factor withCancellableCheckedThrowingContinuation into a separate function
1 parent 3f6c937 commit d7ade55

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
@@ -156,33 +156,12 @@ extension Connection {
156156
/// guarantees. If you need to gurantee that messages are sent in-order
157157
/// use the version with a completion handler.
158158
public func send<R: RequestType>(_ request: R) async throws -> R.Response {
159-
let requestIDWrapper = ThreadSafeBox<RequestID?>(initialValue: nil)
160-
161-
@Sendable
162-
func sendCancelNotification() {
163-
/// Take the request ID out of the box. This ensures that we only send the
164-
/// cancel notification once in case the `Task.isCancelled` and the
165-
/// `onCancel` check race.
166-
if let requestID = requestIDWrapper.takeValue() {
167-
self.send(CancelRequestNotification(id: requestID))
159+
return try await withCancellableCheckedThrowingContinuation { continuation in
160+
return self.send(request) { result in
161+
continuation.resume(with: result)
168162
}
163+
} cancel: { requestID in
164+
self.send(CancelRequestNotification(id: requestID))
169165
}
170-
171-
return try await withTaskCancellationHandler(operation: {
172-
try Task.checkCancellation()
173-
return try await withCheckedThrowingContinuation { continuation in
174-
let requestID = self.send(request) { result in
175-
continuation.resume(with: result)
176-
}
177-
requestIDWrapper.value = requestID
178-
179-
// Check if the task was cancelled. This ensures we send a
180-
// CancelNotification even if the task gets cancelled after we register
181-
// the cancellation handler but before we set the `requestID`.
182-
if Task.isCancelled {
183-
sendCancelNotification()
184-
}
185-
}
186-
}, onCancel: sendCancelNotification)
187166
}
188167
}

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
@@ -83,45 +83,26 @@ extension SourceKitD {
8383
public func send(_ req: SKDRequestDictionary) async throws -> SKDResponseDictionary {
8484
logRequest(req)
8585

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

107-
logResponse(resp)
91+
let resp = SKDResponse(_resp, sourcekitd: self)
10892

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

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

0 commit comments

Comments
 (0)