@@ -23,21 +23,30 @@ import _SubprocessCShims
23
23
24
24
// Windows specific implementation
25
25
extension Configuration {
26
+ // @unchecked Sendable because we need to capture UnsafePointers
27
+ // to send to another thread. While UnsafePointers are not
28
+ // Sendable, we are not mutating them -- we only need these type
29
+ // for C interface.
30
+ internal struct SpawnContext : @unchecked Sendable {
31
+ let startupInfo : UnsafeMutablePointer < STARTUPINFOEXW >
32
+ let createProcessFlags : DWORD
33
+ }
34
+
26
35
internal func spawn(
27
36
withInput inputPipe: consuming CreatedPipe ,
28
37
outputPipe: consuming CreatedPipe ,
29
38
errorPipe: consuming CreatedPipe
30
- ) throws -> SpawnResult {
39
+ ) async throws -> SpawnResult {
31
40
// Spawn differently depending on whether
32
41
// we need to spawn as a user
33
42
guard let userCredentials = self . platformOptions. userCredentials else {
34
- return try self . spawnDirect (
43
+ return try await self . spawnDirect (
35
44
withInput: inputPipe,
36
45
outputPipe: outputPipe,
37
46
errorPipe: errorPipe
38
47
)
39
48
}
40
- return try self . spawnAsUser (
49
+ return try await self . spawnAsUser (
41
50
withInput: inputPipe,
42
51
outputPipe: outputPipe,
43
52
errorPipe: errorPipe,
@@ -49,7 +58,7 @@ extension Configuration {
49
58
withInput inputPipe: consuming CreatedPipe ,
50
59
outputPipe: consuming CreatedPipe ,
51
60
errorPipe: consuming CreatedPipe
52
- ) throws -> SpawnResult {
61
+ ) async throws -> SpawnResult {
53
62
var inputReadFileDescriptor : IODescriptor ? = inputPipe. readFileDescriptor ( )
54
63
var inputWriteFileDescriptor : IODescriptor ? = inputPipe. writeFileDescriptor ( )
55
64
var outputReadFileDescriptor : IODescriptor ? = outputPipe. readFileDescriptor ( )
@@ -104,10 +113,9 @@ extension Configuration {
104
113
throw error
105
114
}
106
115
107
- var processInfo : PROCESS_INFORMATION = PROCESS_INFORMATION ( )
108
116
var createProcessFlags = self . generateCreateProcessFlag ( )
109
117
110
- let created = try self . withStartupInfoEx (
118
+ let ( created, processInfo , windowsError ) = try await self . withStartupInfoEx (
111
119
inputRead: inputReadFileDescriptor,
112
120
inputWrite: inputWriteFileDescriptor,
113
121
outputRead: outputReadFileDescriptor,
@@ -121,34 +129,41 @@ extension Configuration {
121
129
}
122
130
123
131
// Spawn!
124
- return try applicationName. withOptionalNTPathRepresentation { applicationNameW in
125
- try commandAndArgs. withCString (
126
- encodedAs: UTF16 . self
127
- ) { commandAndArgsW in
128
- try environment. withCString (
132
+ let spawnContext = SpawnContext (
133
+ startupInfo: startupInfo,
134
+ createProcessFlags: createProcessFlags
135
+ )
136
+ return try await runOnBackgroundThread {
137
+ return try applicationName. withOptionalNTPathRepresentation { applicationNameW in
138
+ try commandAndArgs. withCString (
129
139
encodedAs: UTF16 . self
130
- ) { environmentW in
131
- try intendedWorkingDir. withOptionalNTPathRepresentation { intendedWorkingDirW in
132
- CreateProcessW (
133
- applicationNameW,
134
- UnsafeMutablePointer < WCHAR > ( mutating: commandAndArgsW) ,
135
- nil , // lpProcessAttributes
136
- nil , // lpThreadAttributes
137
- true , // bInheritHandles
138
- createProcessFlags,
139
- UnsafeMutableRawPointer ( mutating: environmentW) ,
140
- intendedWorkingDirW,
141
- startupInfo. pointer ( to: \. StartupInfo) !,
142
- & processInfo
143
- )
140
+ ) { commandAndArgsW in
141
+ try environment. withCString (
142
+ encodedAs: UTF16 . self
143
+ ) { environmentW in
144
+ try intendedWorkingDir. withOptionalNTPathRepresentation { intendedWorkingDirW in
145
+ var processInfo = PROCESS_INFORMATION ( )
146
+ let result = CreateProcessW (
147
+ applicationNameW,
148
+ UnsafeMutablePointer < WCHAR > ( mutating: commandAndArgsW) ,
149
+ nil , // lpProcessAttributes
150
+ nil , // lpThreadAttributes
151
+ true , // bInheritHandles
152
+ spawnContext. createProcessFlags,
153
+ UnsafeMutableRawPointer ( mutating: environmentW) ,
154
+ intendedWorkingDirW,
155
+ spawnContext. startupInfo. pointer ( to: \. StartupInfo) !,
156
+ & processInfo
157
+ )
158
+ return ( result, processInfo, GetLastError ( ) )
159
+ }
144
160
}
145
161
}
146
162
}
147
163
}
148
164
}
149
165
150
166
guard created else {
151
- let windowsError = GetLastError ( )
152
167
if windowsError == ERROR_FILE_NOT_FOUND || windowsError == ERROR_PATH_NOT_FOUND {
153
168
// This execution path is not it. Try the next one
154
169
continue
@@ -233,7 +248,7 @@ extension Configuration {
233
248
outputPipe: consuming CreatedPipe ,
234
249
errorPipe: consuming CreatedPipe ,
235
250
userCredentials: PlatformOptions . UserCredentials
236
- ) throws -> SpawnResult {
251
+ ) async throws -> SpawnResult {
237
252
var inputPipeBox : CreatedPipe ? = consume inputPipe
238
253
var outputPipeBox : CreatedPipe ? = consume outputPipe
239
254
var errorPipeBox : CreatedPipe ? = consume errorPipe
@@ -295,10 +310,9 @@ extension Configuration {
295
310
throw error
296
311
}
297
312
298
- var processInfo : PROCESS_INFORMATION = PROCESS_INFORMATION ( )
299
313
var createProcessFlags = self . generateCreateProcessFlag ( )
300
314
301
- let created = try self . withStartupInfoEx (
315
+ let ( created, processInfo , windowsError ) = try await self . withStartupInfoEx (
302
316
inputRead: inputReadFileDescriptor,
303
317
inputWrite: inputWriteFileDescriptor,
304
318
outputRead: outputReadFileDescriptor,
@@ -311,37 +325,45 @@ extension Configuration {
311
325
try configurator ( & createProcessFlags, & startupInfo. pointer ( to: \. StartupInfo) !. pointee)
312
326
}
313
327
328
+ let spawnContext = SpawnContext (
329
+ startupInfo: startupInfo,
330
+ createProcessFlags: createProcessFlags
331
+ )
314
332
// Spawn (featuring pyramid!)
315
- return try userCredentials. username. withCString (
316
- encodedAs: UTF16 . self
317
- ) { usernameW in
318
- try userCredentials. password. withCString (
333
+ return try await runOnBackgroundThread {
334
+ return try userCredentials. username. withCString (
319
335
encodedAs: UTF16 . self
320
- ) { passwordW in
321
- try userCredentials. domain . withOptionalCString (
336
+ ) { usernameW in
337
+ try userCredentials. password . withCString (
322
338
encodedAs: UTF16 . self
323
- ) { domainW in
324
- try applicationName . withOptionalNTPathRepresentation { applicationNameW in
325
- try commandAndArgs . withCString (
326
- encodedAs : UTF16 . self
327
- ) { commandAndArgsW in
328
- try environment . withCString (
339
+ ) { passwordW in
340
+ try userCredentials . domain . withOptionalCString (
341
+ encodedAs : UTF16 . self
342
+ ) { domainW in
343
+ try applicationName . withOptionalNTPathRepresentation { applicationNameW in
344
+ try commandAndArgs . withCString (
329
345
encodedAs: UTF16 . self
330
- ) { environmentW in
331
- try intendedWorkingDir. withOptionalNTPathRepresentation { intendedWorkingDirW in
332
- CreateProcessWithLogonW (
333
- usernameW,
334
- domainW,
335
- passwordW,
336
- DWORD ( LOGON_WITH_PROFILE) ,
337
- applicationNameW,
338
- UnsafeMutablePointer < WCHAR > ( mutating: commandAndArgsW) ,
339
- createProcessFlags,
340
- UnsafeMutableRawPointer ( mutating: environmentW) ,
341
- intendedWorkingDirW,
342
- startupInfo. pointer ( to: \. StartupInfo) !,
343
- & processInfo
344
- )
346
+ ) { commandAndArgsW in
347
+ try environment. withCString (
348
+ encodedAs: UTF16 . self
349
+ ) { environmentW in
350
+ try intendedWorkingDir. withOptionalNTPathRepresentation { intendedWorkingDirW in
351
+ var processInfo = PROCESS_INFORMATION ( )
352
+ let created = CreateProcessWithLogonW (
353
+ usernameW,
354
+ domainW,
355
+ passwordW,
356
+ DWORD ( LOGON_WITH_PROFILE) ,
357
+ applicationNameW,
358
+ UnsafeMutablePointer < WCHAR > ( mutating: commandAndArgsW) ,
359
+ spawnContext. createProcessFlags,
360
+ UnsafeMutableRawPointer ( mutating: environmentW) ,
361
+ intendedWorkingDirW,
362
+ spawnContext. startupInfo. pointer ( to: \. StartupInfo) !,
363
+ & processInfo
364
+ )
365
+ return ( created, processInfo, GetLastError ( ) )
366
+ }
345
367
}
346
368
}
347
369
}
@@ -353,8 +375,6 @@ extension Configuration {
353
375
354
376
355
377
guard created else {
356
- let windowsError = GetLastError ( )
357
-
358
378
if windowsError == ERROR_FILE_NOT_FOUND || windowsError == ERROR_PATH_NOT_FOUND {
359
379
// This executable path is not it. Try the next one
360
380
continue
@@ -1044,8 +1064,8 @@ extension Configuration {
1044
1064
outputWrite outputWriteFileDescriptor: borrowing IODescriptor ? ,
1045
1065
errorRead errorReadFileDescriptor: borrowing IODescriptor ? ,
1046
1066
errorWrite errorWriteFileDescriptor: borrowing IODescriptor ? ,
1047
- _ body: ( UnsafeMutablePointer < STARTUPINFOEXW > ) throws -> Result
1048
- ) rethrows -> Result {
1067
+ _ body: ( UnsafeMutablePointer < STARTUPINFOEXW > ) async throws -> Result
1068
+ ) async throws -> Result {
1049
1069
var info : STARTUPINFOEXW = STARTUPINFOEXW ( )
1050
1070
info. StartupInfo. cb = DWORD ( MemoryLayout . size ( ofValue: info) )
1051
1071
info. StartupInfo. dwFlags |= DWORD ( STARTF_USESTDHANDLES)
@@ -1111,35 +1131,41 @@ extension Configuration {
1111
1131
let alignment = 16
1112
1132
var attributeListByteCount = SIZE_T ( 0 )
1113
1133
_ = InitializeProcThreadAttributeList ( nil , 1 , 0 , & attributeListByteCount)
1114
- return try withUnsafeTemporaryAllocation ( byteCount: Int ( attributeListByteCount) , alignment: alignment) { attributeListPtr in
1115
- let attributeList = LPPROC_THREAD_ATTRIBUTE_LIST ( attributeListPtr. baseAddress!)
1116
- guard InitializeProcThreadAttributeList ( attributeList, 1 , 0 , & attributeListByteCount) else {
1117
- throw SubprocessError (
1118
- code: . init( . spawnFailed) ,
1119
- underlyingError: . init( rawValue: GetLastError ( ) )
1120
- )
1121
- }
1122
- defer {
1123
- DeleteProcThreadAttributeList ( attributeList)
1124
- }
1134
+ // We can't use withUnsafeTemporaryAllocation here because body is async
1135
+ let attributeListPtr : UnsafeMutableRawBufferPointer = . allocate(
1136
+ byteCount: Int ( attributeListByteCount) ,
1137
+ alignment: alignment
1138
+ )
1139
+ defer {
1140
+ attributeListPtr. deallocate ( )
1141
+ }
1125
1142
1126
- var handles = Array ( inheritedHandles)
1127
- return try handles. withUnsafeMutableBufferPointer { inheritedHandlesPtr in
1128
- _ = UpdateProcThreadAttribute (
1129
- attributeList,
1130
- 0 ,
1131
- _subprocess_PROC_THREAD_ATTRIBUTE_HANDLE_LIST ( ) ,
1132
- inheritedHandlesPtr. baseAddress!,
1133
- SIZE_T ( MemoryLayout < HANDLE > . stride * inheritedHandlesPtr. count) ,
1134
- nil ,
1135
- nil
1136
- )
1143
+ let attributeList = LPPROC_THREAD_ATTRIBUTE_LIST ( attributeListPtr. baseAddress!)
1144
+ guard InitializeProcThreadAttributeList ( attributeList, 1 , 0 , & attributeListByteCount) else {
1145
+ throw SubprocessError (
1146
+ code: . init( . spawnFailed) ,
1147
+ underlyingError: . init( rawValue: GetLastError ( ) )
1148
+ )
1149
+ }
1150
+ defer {
1151
+ DeleteProcThreadAttributeList ( attributeList)
1152
+ }
1137
1153
1138
- info. lpAttributeList = attributeList
1154
+ var handles = Array ( inheritedHandles)
1155
+ handles. withUnsafeMutableBufferPointer { inheritedHandlesPtr in
1156
+ _ = UpdateProcThreadAttribute (
1157
+ attributeList,
1158
+ 0 ,
1159
+ _subprocess_PROC_THREAD_ATTRIBUTE_HANDLE_LIST ( ) ,
1160
+ inheritedHandlesPtr. baseAddress!,
1161
+ SIZE_T ( MemoryLayout < HANDLE > . stride * inheritedHandlesPtr. count) ,
1162
+ nil ,
1163
+ nil
1164
+ )
1139
1165
1140
- return try body ( & info)
1141
- }
1166
+ info. lpAttributeList = attributeList
1142
1167
}
1168
+ return try await body ( & info)
1143
1169
}
1144
1170
1145
1171
private func generateWindowsCommandAndArguments(
0 commit comments