@@ -43,7 +43,7 @@ final class AsyncIO: @unchecked Sendable {
43
43
) rethrows -> ResultType
44
44
}
45
45
46
- private final class MonitorThreadContext {
46
+ private struct MonitorThreadContext : @ unchecked Sendable {
47
47
let ioCompletionPort : HANDLE
48
48
49
49
init ( ioCompletionPort: HANDLE ) {
@@ -60,11 +60,9 @@ final class AsyncIO: @unchecked Sendable {
60
60
internal init ( ) {
61
61
var maybeSetupError : SubprocessError ? = nil
62
62
// Create the the completion port
63
- guard
64
- let port = CreateIoCompletionPort (
65
- INVALID_HANDLE_VALUE, nil , 0 , 0
66
- ) , port != INVALID_HANDLE_VALUE
67
- else {
63
+ guard let ioCompletionPort = CreateIoCompletionPort (
64
+ INVALID_HANDLE_VALUE, nil , 0 , 0
65
+ ) , ioCompletionPort != INVALID_HANDLE_VALUE else {
68
66
let error = SubprocessError (
69
67
code: . init( . asyncIOFailed( " CreateIoCompletionPort failed " ) ) ,
70
68
underlyingError: . init( rawValue: GetLastError ( ) )
@@ -73,17 +71,12 @@ final class AsyncIO: @unchecked Sendable {
73
71
self . monitorThread = . failure( error)
74
72
return
75
73
}
76
- self . ioCompletionPort = . success( port )
74
+ self . ioCompletionPort = . success( ioCompletionPort )
77
75
// Create monitor thread
78
- let threadContext = MonitorThreadContext ( ioCompletionPort: port)
79
- let threadContextPtr = Unmanaged . passRetained ( threadContext)
80
- /// Microsoft documentation for `CreateThread` states:
81
- /// > A thread in an executable that calls the C run-time library (CRT)
82
- /// > should use the _beginthreadex and _endthreadex functions for
83
- /// > thread management rather than CreateThread and ExitThread
84
- let threadHandleValue = _beginthreadex (
85
- nil , 0 ,
86
- { args in
76
+ let context = MonitorThreadContext ( ioCompletionPort: ioCompletionPort)
77
+ let threadHandle : HANDLE
78
+ do {
79
+ threadHandle = try begin_thread_x {
87
80
func reportError( _ error: SubprocessError ) {
88
81
let continuations = _registration. withLock { store in
89
82
return store. values
@@ -93,15 +86,50 @@ final class AsyncIO: @unchecked Sendable {
93
86
}
94
87
}
95
88
96
- let unmanaged = Unmanaged< MonitorThreadContext> . fromOpaque( args!)
97
- let context = unmanaged. takeRetainedValue ( )
98
-
89
+ // Monitor loop
90
+ while true {
91
+ var bytesTransferred : DWORD = 0
92
+ var targetFileDescriptor : UInt64 = 0
93
+ var overlapped : LPOVERLAPPED ? = nil
99
94
// Monitor loop
100
95
while true {
101
96
var bytesTransferred : DWORD = 0
102
97
var targetFileDescriptor : UInt64 = 0
103
98
var overlapped : LPOVERLAPPED ? = nil
104
99
100
+ let monitorResult = GetQueuedCompletionStatus (
101
+ context. ioCompletionPort,
102
+ & bytesTransferred,
103
+ & targetFileDescriptor,
104
+ & overlapped,
105
+ INFINITE
106
+ )
107
+ if !monitorResult {
108
+ let lastError = GetLastError ( )
109
+ if lastError == ERROR_BROKEN_PIPE {
110
+ // We finished reading the handle. Signal EOF by
111
+ // finishing the stream.
112
+ // NOTE: here we deliberately leave now unused continuation
113
+ // in the store. Windows does not offer an API to remove a
114
+ // HANDLE from an IOCP port, therefore we leave the registration
115
+ // to signify the HANDLE has already been resisted.
116
+ let continuation = _registration. withLock { store -> SignalStream . Continuation ? in
117
+ if let continuation = store [ targetFileDescriptor] {
118
+ return continuation
119
+ }
120
+ return nil
121
+ }
122
+ continuation? . finish ( )
123
+ continue
124
+ } else {
125
+ let error = SubprocessError (
126
+ code: . init( . asyncIOFailed( " GetQueuedCompletionStatus failed " ) ) ,
127
+ underlyingError: . init( rawValue: lastError)
128
+ )
129
+ reportError ( error)
130
+ break
131
+ }
132
+ }
105
133
let monitorResult = GetQueuedCompletionStatus (
106
134
context. ioCompletionPort,
107
135
& bytesTransferred,
@@ -149,16 +177,13 @@ final class AsyncIO: @unchecked Sendable {
149
177
}
150
178
continuation? . yield ( bytesTransferred)
151
179
}
180
+
152
181
return 0
153
- } , threadContextPtr. toOpaque ( ) , 0 , nil )
154
- guard threadHandleValue > 0 ,
155
- let threadHandle = HANDLE ( bitPattern: threadHandleValue)
156
- else {
157
- // _beginthreadex uses errno instead of GetLastError()
158
- let capturedError = _subprocess_windows_get_errno ( )
182
+ }
183
+ } catch let underlyingError {
159
184
let error = SubprocessError (
160
- code: . init( . asyncIOFailed( " _beginthreadex failed " ) ) ,
161
- underlyingError: . init ( rawValue : capturedError )
185
+ code: . init( . asyncIOFailed( " Failed to create monitor thread " ) ) ,
186
+ underlyingError: underlyingError
162
187
)
163
188
self . monitorThread = . failure( error)
164
189
return
0 commit comments