Skip to content

Commit 1a3de70

Browse files
committed
Experimental non-overlapped handling for Windows AsyncIO via AsyncBufferSequence
1 parent ba08423 commit 1a3de70

File tree

1 file changed

+33
-14
lines changed

1 file changed

+33
-14
lines changed

Sources/Subprocess/IO/AsyncIO+Windows.swift

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -214,17 +214,25 @@ final class AsyncIO: @unchecked Sendable {
214214

215215
// Windows Documentation: The function returns the handle
216216
// of the existing I/O completion port if successful
217-
guard
218-
CreateIoCompletionPort(
219-
handle, ioPort, completionKey, 0
220-
) == ioPort
221-
else {
222-
let error = SubprocessError(
223-
code: .init(.asyncIOFailed("CreateIoCompletionPort failed")),
224-
underlyingError: .init(rawValue: GetLastError())
225-
)
226-
continuation.finish(throwing: error)
227-
return
217+
if CreateIoCompletionPort(
218+
handle, ioPort, completionKey, 0
219+
) != ioPort {
220+
let lastError = GetLastError()
221+
if lastError != ERROR_INVALID_PARAMETER {
222+
let error = SubprocessError(
223+
code: .init(.asyncIOFailed("CreateIoCompletionPort failed")),
224+
underlyingError: .init(rawValue: GetLastError())
225+
)
226+
continuation.finish(throwing: error)
227+
return
228+
} else {
229+
// Special Case:
230+
//
231+
// * ERROR_INVALID_PARAMETER - The handle likely doesn't have FILE_FLAG_OVERLAPPED, which might indicate that it isn't a pipe
232+
// so we can just signal that it is ready for reading right away.
233+
//
234+
continuation.yield(UInt32.max)
235+
}
228236
}
229237
// Now save the continuation
230238
_registration.withLock { storage in
@@ -271,6 +279,7 @@ final class AsyncIO: @unchecked Sendable {
271279
// We use an empty `_OVERLAPPED()` here because `ReadFile` below
272280
// only reads non-seekable files, aka pipes.
273281
var overlapped = _OVERLAPPED()
282+
var bytesReadIfSynchronous = UInt32(0)
274283
let succeed = try resultBuffer.withUnsafeMutableBufferPointer { bufferPointer in
275284
// Get a pointer to the memory at the specified offset
276285
// Windows ReadFile uses DWORD for target count, which means we can only
@@ -286,7 +295,7 @@ final class AsyncIO: @unchecked Sendable {
286295
handle,
287296
offsetAddress,
288297
targetCount,
289-
nil,
298+
&bytesReadIfSynchronous,
290299
&overlapped
291300
)
292301
}
@@ -312,8 +321,18 @@ final class AsyncIO: @unchecked Sendable {
312321
}
313322

314323
}
315-
// Now wait for read to finish
316-
let bytesRead = try await signalStream.next() ?? 0
324+
325+
let bytesRead =
326+
if bytesReadIfSynchronous == 0 {
327+
try await signalStream.next() ?? 0
328+
} else {
329+
bytesReadIfSynchronous
330+
}
331+
332+
// This handle doesn't support overlapped (asynchronous) I/O, so we must have read it synchronously above
333+
if bytesRead == UInt32.max {
334+
bytesRead = bytesReadIfSynchronous
335+
}
317336

318337
if bytesRead == 0 {
319338
// We reached EOF. Return whatever's left

0 commit comments

Comments
 (0)