11
11
12
12
#if canImport(Darwin)
13
13
import Darwin
14
+ import os
14
15
#elseif canImport(Glibc)
15
16
import Glibc
16
17
#elseif canImport(Bionic)
@@ -28,24 +29,6 @@ import _SubprocessCShims
28
29
import Synchronization
29
30
#endif
30
31
31
- #if canImport(Darwin)
32
- internal func runOnBackgroundThread< Result> (
33
- _ body: @Sendable @escaping ( ) throws -> Result
34
- ) async throws -> Result {
35
- let result = try await withCheckedThrowingContinuation { continuation in
36
- // On Darwin, use DispatchQueue directly
37
- DispatchQueue . global ( ) . async {
38
- do {
39
- let result = try body ( )
40
- continuation. resume ( returning: result)
41
- } catch {
42
- continuation. resume ( throwing: error)
43
- }
44
- }
45
- }
46
- return result
47
- }
48
- #else
49
32
50
33
#if canImport(WinSDK)
51
34
private typealias MutexType = CRITICAL_SECTION
@@ -57,6 +40,19 @@ private typealias ConditionType = pthread_cond_t
57
40
private typealias ThreadType = pthread_t
58
41
#endif
59
42
43
+ internal func runOnBackgroundThread< Result> (
44
+ _ body: @Sendable @escaping ( ) throws -> Result
45
+ ) async throws -> Result {
46
+ // Only executed once
47
+ _setupWorkerThread
48
+
49
+ let result = try await withCheckedThrowingContinuation { ( continuation: CheckedContinuation < Result , any Error > ) in
50
+ let workItem = BackgroundWorkItem ( body, continuation: continuation)
51
+ _workQueue. enqueue ( workItem)
52
+ }
53
+ return result
54
+ }
55
+
60
56
private struct BackgroundWorkItem {
61
57
private let work : @Sendable ( ) -> Void
62
58
@@ -165,12 +161,12 @@ private final class WorkQueue: Sendable {
165
161
}
166
162
167
163
private let _workQueue = WorkQueue ( )
168
- private let _workQueueShutdownFlag : Atomic < UInt8 > = Atomic ( 0 )
164
+ private let _workQueueShutdownFlag = AtomicCounter ( )
169
165
170
166
// Okay to be unlocked global mutable because this value is only set once like dispatch_once
171
167
private nonisolated ( unsafe) var _workerThread: Result < ThreadType , any Error > = . failure( SubprocessError ( code: . init( . spawnFailed) , underlyingError: nil ) )
172
168
173
- private let setupWorkerThread : ( ) = {
169
+ private let _setupWorkerThread : ( ) = {
174
170
do {
175
171
#if canImport(WinSDK)
176
172
let workerThread = try begin_thread_x {
@@ -208,7 +204,7 @@ private func _shutdownWorkerThread() {
208
204
guard case . success( let thread) = _workerThread else {
209
205
return
210
206
}
211
- guard _workQueueShutdownFlag. add ( 1 , ordering : . sequentiallyConsistent ) . newValue == 1 else {
207
+ guard _workQueueShutdownFlag. addOne ( ) == 1 else {
212
208
// We already shutdown this thread
213
209
return
214
210
}
@@ -227,65 +223,41 @@ private func _shutdownWorkerThread() {
227
223
_workQueue. waitCondition. deallocate ( )
228
224
}
229
225
230
- internal func runOnBackgroundThread< Result> (
231
- _ body: @Sendable @escaping ( ) throws -> Result
232
- ) async throws -> Result {
233
- // Only executed once
234
- setupWorkerThread
226
+ // MARK: - AtomicCounter
235
227
236
- let result = try await withCheckedThrowingContinuation { ( continuation: CheckedContinuation < Result , any Error > ) in
237
- let workItem = BackgroundWorkItem ( body, continuation: continuation)
238
- _workQueue. enqueue ( workItem)
239
- }
240
- return result
241
- }
228
+ #if canImport(Darwin)
229
+ // Unfortunately on Darwin we can unconditionally use Atomic since it requires macOS 15
230
+ internal struct AtomicCounter : ~ Copyable {
231
+ private let storage : OSAllocatedUnfairLock < UInt8 >
242
232
243
- #endif
233
+ internal init ( ) {
234
+ self . storage = . init( initialState: 0 )
235
+ }
244
236
245
- // MARK: - Thread Creation Primitives
246
- #if canImport(Glibc) || canImport(Bionic) || canImport(Musl)
247
- internal func pthread_create(
248
- _ body: @Sendable @escaping ( ) -> ( )
249
- ) throws ( SubprocessError. UnderlyingError) -> pthread_t {
250
- final class Context {
251
- let body : @Sendable ( ) -> ( )
252
- init ( body: @Sendable @escaping ( ) -> Void ) {
253
- self . body = body
237
+ internal func addOne( ) -> UInt8 {
238
+ return self . storage. withLock {
239
+ $0 += 1
240
+ return $0
254
241
}
255
242
}
256
- #if canImport(Glibc) || canImport(Musl)
257
- func proc( _ context: UnsafeMutableRawPointer ? ) -> UnsafeMutableRawPointer ? {
258
- ( Unmanaged < AnyObject > . fromOpaque ( context!) . takeRetainedValue ( ) as! Context ) . body ( )
259
- return nil
260
- }
261
- #elseif canImport(Bionic)
262
- func proc( _ context: UnsafeMutableRawPointer ) -> UnsafeMutableRawPointer {
263
- ( Unmanaged < AnyObject > . fromOpaque ( context) . takeRetainedValue ( ) as! Context ) . body ( )
264
- return context
243
+ }
244
+ #else
245
+ internal struct AtomicCounter : ~ Copyable {
246
+
247
+ private let storage : Atomic < UInt8 >
248
+
249
+ internal init ( ) {
250
+ self . storage = Atomic ( 0 )
265
251
}
266
- #endif
267
- #if canImport(Glibc) || canImport(Bionic)
268
- var thread = pthread_t ( )
269
- #else
270
- var thread : pthread_t ?
271
- #endif
272
- let rc = pthread_create (
273
- & thread,
274
- nil ,
275
- proc,
276
- Unmanaged . passRetained ( Context ( body: body) ) . toOpaque ( )
277
- )
278
- if rc != 0 {
279
- throw SubprocessError . UnderlyingError ( rawValue: rc)
252
+
253
+ internal func addOne( ) -> UInt8 {
254
+ return self . storage. add ( 1 , ordering: . sequentiallyConsistent) . newValue
280
255
}
281
- #if canImport(Glibc) || canImport(Bionic)
282
- return thread
283
- #else
284
- return thread!
285
- #endif
286
256
}
257
+ #endif
287
258
288
- #elseif canImport(WinSDK)
259
+ // MARK: - Thread Creation Primitives
260
+ #if canImport(WinSDK)
289
261
/// Microsoft documentation for `CreateThread` states:
290
262
/// > A thread in an executable that calls the C run-time library (CRT)
291
263
/// > should use the _beginthreadex and _endthreadex functions for
@@ -321,5 +293,54 @@ internal func begin_thread_x(
321
293
322
294
return threadHandle
323
295
}
296
+ #else
297
+
298
+ internal func pthread_create(
299
+ _ body: @Sendable @escaping ( ) -> ( )
300
+ ) throws ( SubprocessError. UnderlyingError) -> pthread_t {
301
+ final class Context {
302
+ let body : @Sendable ( ) -> ( )
303
+ init ( body: @Sendable @escaping ( ) -> Void ) {
304
+ self . body = body
305
+ }
306
+ }
307
+ #if canImport(Darwin)
308
+ func proc( _ context: UnsafeMutableRawPointer ) -> UnsafeMutableRawPointer ? {
309
+ ( Unmanaged < AnyObject > . fromOpaque ( context) . takeRetainedValue ( ) as! Context ) . body ( )
310
+ return context
311
+ }
312
+ #elseif canImport(Glibc) || canImport(Musl)
313
+ func proc( _ context: UnsafeMutableRawPointer ? ) -> UnsafeMutableRawPointer ? {
314
+ ( Unmanaged < AnyObject > . fromOpaque ( context!) . takeRetainedValue ( ) as! Context ) . body ( )
315
+ return context
316
+ }
317
+ #elseif canImport(Bionic)
318
+ func proc( _ context: UnsafeMutableRawPointer ) -> UnsafeMutableRawPointer {
319
+ ( Unmanaged < AnyObject > . fromOpaque ( context) . takeRetainedValue ( ) as! Context ) . body ( )
320
+ return context
321
+ }
322
+ #endif
323
+
324
+ #if canImport(Glibc) || canImport(Bionic)
325
+ var thread = pthread_t ( )
326
+ #else
327
+ var thread : pthread_t ?
324
328
#endif
329
+ let rc = pthread_create (
330
+ & thread,
331
+ nil ,
332
+ proc,
333
+ Unmanaged . passRetained ( Context ( body: body) ) . toOpaque ( )
334
+ )
335
+ if rc != 0 {
336
+ throw SubprocessError . UnderlyingError ( rawValue: rc)
337
+ }
338
+ #if canImport(Glibc) || canImport(Bionic)
339
+ return thread
340
+ #else
341
+ return thread!
342
+ #endif
343
+ }
344
+
345
+ #endif // canImport(WinSDK)
325
346
0 commit comments