Skip to content

Commit 276a25a

Browse files
committed
Avoid blocking a Swift Task in the read() syscall
Add a DispatchIO-based helper which returns the data asynchronously.
1 parent 1bfdd81 commit 276a25a

File tree

3 files changed

+40
-6
lines changed

3 files changed

+40
-6
lines changed

Sources/SWBServiceCore/ServiceHostConnection.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,13 @@ final class ServiceHostConnection: @unchecked Sendable {
148148
#endif
149149

150150
// Read data.
151-
let result = read(self.inputFD.rawValue, tmp.baseAddress, numericCast(tmpBufferSize))
152-
if result < 0 {
153-
if errno == EINTR { continue }
154-
error = ServiceHostIOError(message: "read from client failed", cause: SWBUtil.POSIXError(errno, context: "read"))
151+
let result: Int
152+
do {
153+
let buf = try await DispatchFD(fileDescriptor: self.inputFD).readChunk(upToLength: tmpBufferSize)
154+
result = buf.count
155+
buf.copyBytes(to: tmp)
156+
} catch let readError {
157+
error = ServiceHostIOError(message: "read from client failed", cause: readError)
155158
break
156159
}
157160
if result == 0 {

Sources/SWBUtil/Dispatch+Async.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// This file contains helpers used to bridge GCD and Swift Concurrency.
1414
// In the long term, these ideally all go away.
1515

16-
private import Foundation
16+
import Foundation
1717

1818
/// Runs an async function and synchronously waits for the response.
1919
/// - warning: This function is extremely dangerous because it blocks the calling thread and may lead to deadlock, and should only be used as a temporary transitional aid.
@@ -41,6 +41,24 @@ public func runAsyncAndBlock<T: Sendable, E>(_ block: @Sendable @escaping () asy
4141
return try result.value!.get()
4242
}
4343

44+
extension DispatchFD {
45+
public func readChunk(upToLength maxLength: Int) async throws -> SWBDispatchData {
46+
return try await withCheckedThrowingContinuation { continuation in
47+
SWBDispatchIO.read(
48+
fromFileDescriptor: self,
49+
maxLength: maxLength,
50+
runningHandlerOn: .global()
51+
) { data, error in
52+
if error != 0 {
53+
continuation.resume(throwing: POSIXError(error))
54+
return
55+
}
56+
continuation.resume(returning: data)
57+
}
58+
}
59+
}
60+
}
61+
4462
extension AsyncThrowingStream where Element == UInt8, Failure == any Error {
4563
/// Returns an async stream which reads bytes from the specified file descriptor. Unlike `FileHandle.bytes`, it does not block the caller.
4664
@available(macOS, deprecated: 15.0, message: "Use the AsyncSequence-returning overload.")

Sources/SWBUtil/SWBDispatch.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
private import Dispatch
1717
public import SWBLibc
18-
import Foundation
18+
public import Foundation
1919

2020
#if canImport(System)
2121
public import System
@@ -123,6 +123,12 @@ extension SWBDispatchData: RandomAccessCollection {
123123
}
124124
}
125125

126+
extension SWBDispatchData: DataProtocol {
127+
public var regions: DispatchData.Regions {
128+
dispatchData.regions
129+
}
130+
}
131+
126132
/// Thin wrapper for `DispatchSemaphore` to isolate it from the rest of the codebase and help migration away from it.
127133
internal final class SWBDispatchSemaphore: Sendable {
128134
private let semaphore: DispatchSemaphore
@@ -183,6 +189,13 @@ public final class SWBDispatchIO: Sendable {
183189
io = DispatchIO(type: .stream, fileDescriptor: numericCast(fileDescriptor), queue: queue.queue, cleanupHandler: cleanupHandler)
184190
}
185191

192+
public static func read(fromFileDescriptor fileDescriptor: DispatchFD, maxLength: Int, runningHandlerOn queue: SWBQueue, handler: @escaping (SWBDispatchData, Int32) -> Void) {
193+
// Most of the dispatch APIs take a parameter called "fileDescriptor". On Windows (except makeReadSource and makeWriteSource) it is actually a HANDLE, so convert it accordingly.
194+
DispatchIO.read(fromFileDescriptor: numericCast(fileDescriptor.rawValue), maxLength: maxLength, runningHandlerOn: queue.queue) { data, error in
195+
handler(SWBDispatchData(data), error)
196+
}
197+
}
198+
186199
public static func stream(fileDescriptor: DispatchFD, queue: SWBQueue, cleanupHandler: @escaping (Int32) -> Void) -> SWBDispatchIO {
187200
// Most of the dispatch APIs take a parameter called "fileDescriptor". On Windows (except makeReadSource and makeWriteSource) it is actually a HANDLE, so convert it accordingly.
188201
SWBDispatchIO(fileDescriptor: numericCast(fileDescriptor.rawValue), queue: queue, cleanupHandler: cleanupHandler)

0 commit comments

Comments
 (0)