Skip to content

Commit 0adcfb7

Browse files
committed
Factor withCancellableCheckedThrowingContinuation into a separate function
1 parent 5dad0f2 commit 0adcfb7

File tree

5 files changed

+77
-80
lines changed

5 files changed

+77
-80
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: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -83,36 +83,16 @@ extension SourceKitD {
8383
public func send(_ req: SKDRequestDictionary) async throws -> SKDResponseDictionary {
8484
logRequest(req)
8585

86-
@Sendable
87-
func cancelSourcekitdRequest() {
88-
/// Take the request ID out of the box. This ensures that we only send the
89-
/// cancel notification once in case the `Task.isCancelled` and the
90-
/// `onCancel` check race.
91-
if let handle = handleWrapper.takeValue() {
92-
api.cancel_request(handle)
86+
let sourcekitdResponse: SKDResponse = try await withCancellableCheckedThrowingContinuation { continuation in
87+
var handle: sourcekitd_request_handle_t? = nil
88+
api.send_request(req.dict, &handle) { _resp in
89+
continuation.resume(returning: SKDResponse(_resp, sourcekitd: self))
9390
}
91+
return handle
92+
} cancel: { handle in
93+
api.cancel_request(handle)
9494
}
9595

96-
let handleWrapper = ThreadSafeBox<sourcekitd_request_handle_t?>(initialValue: nil)
97-
let sourcekitdResponse: SKDResponse = try await withTaskCancellationHandler(operation: {
98-
try Task.checkCancellation()
99-
return await withCheckedContinuation { continuation in
100-
var handle: sourcekitd_request_handle_t? = nil
101-
102-
api.send_request(req.dict, &handle) { _resp in
103-
continuation.resume(returning: SKDResponse(_resp, sourcekitd: self))
104-
}
105-
106-
handleWrapper.value = handle
107-
// Check if the task was cancelled. This ensures we send a
108-
// CancelNotification even if the task gets cancelled after we register
109-
// the cancellation handler but before we set the request handle.
110-
if Task.isCancelled {
111-
cancelSourcekitdRequest()
112-
}
113-
}
114-
}, onCancel: cancelSourcekitdRequest)
115-
11696
logResponse(sourcekitdResponse)
11797

11898
guard let dict = sourcekitdResponse.value else {

0 commit comments

Comments
 (0)