5
5
*/
6
6
7
7
import Foundation
8
+ import Dispatch
8
9
9
10
// MARK: - API
10
11
@@ -349,6 +350,12 @@ private extension Process {
349
350
launchPath = " /bin/bash "
350
351
arguments = [ " -c " , command]
351
352
353
+ // Because FileHandle's readabilityHandler might be called from a
354
+ // different queue from the calling queue, avoid a data race by
355
+ // protecting reads and writes to outputData and errorData on
356
+ // a single dispatch queue.
357
+ let outputQueue = DispatchQueue ( label: " bash-output-queue " )
358
+
352
359
var outputData = Data ( )
353
360
var errorData = Data ( )
354
361
@@ -360,23 +367,29 @@ private extension Process {
360
367
361
368
#if !os(Linux)
362
369
outputPipe. fileHandleForReading. readabilityHandler = { handler in
363
- let data = handler. availableData
364
- outputData. append ( data)
365
- outputHandle? . write ( data)
370
+ outputQueue. async {
371
+ let data = handler. availableData
372
+ outputData. append ( data)
373
+ outputHandle? . write ( data)
374
+ }
366
375
}
367
376
368
377
errorPipe. fileHandleForReading. readabilityHandler = { handler in
369
- let data = handler. availableData
370
- errorData. append ( data)
371
- errorHandle? . write ( data)
378
+ outputQueue. async {
379
+ let data = handler. availableData
380
+ errorData. append ( data)
381
+ errorHandle? . write ( data)
382
+ }
372
383
}
373
384
#endif
374
385
375
386
launch ( )
376
387
377
388
#if os(Linux)
378
- outputData = outputPipe. fileHandleForReading. readDataToEndOfFile ( )
379
- errorData = errorPipe. fileHandleForReading. readDataToEndOfFile ( )
389
+ outputQueue. sync {
390
+ outputData = outputPipe. fileHandleForReading. readDataToEndOfFile ( )
391
+ errorData = errorPipe. fileHandleForReading. readDataToEndOfFile ( )
392
+ }
380
393
#endif
381
394
382
395
waitUntilExit ( )
@@ -389,15 +402,19 @@ private extension Process {
389
402
errorPipe. fileHandleForReading. readabilityHandler = nil
390
403
#endif
391
404
392
- if terminationStatus != 0 {
393
- throw ShellOutError (
394
- terminationStatus: terminationStatus,
395
- errorData: errorData,
396
- outputData: outputData
397
- )
405
+ // Block until all writes have occurred to outputData and errorData,
406
+ // and then read the data back out.
407
+ return try outputQueue. sync {
408
+ if terminationStatus != 0 {
409
+ throw ShellOutError (
410
+ terminationStatus: terminationStatus,
411
+ errorData: errorData,
412
+ outputData: outputData
413
+ )
414
+ }
415
+
416
+ return outputData. shellOutput ( )
398
417
}
399
-
400
- return outputData. shellOutput ( )
401
418
}
402
419
}
403
420
0 commit comments