Skip to content

Commit 6e44fc1

Browse files
committed
Guard appending to outputData and errorData
Since the FileHandle readabilityHandlers execute asyncronously, possibly on multiple different queues, we need to guard the reads/writes of the outputData/errorData on a serial dispatch queue, and block until all writes have finished.
1 parent 0208caa commit 6e44fc1

File tree

1 file changed

+33
-16
lines changed

1 file changed

+33
-16
lines changed

Sources/ShellOut.swift

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import Foundation
8+
import Dispatch
89

910
// MARK: - API
1011

@@ -349,6 +350,12 @@ private extension Process {
349350
launchPath = "/bin/bash"
350351
arguments = ["-c", command]
351352

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+
352359
var outputData = Data()
353360
var errorData = Data()
354361

@@ -360,23 +367,29 @@ private extension Process {
360367

361368
#if !os(Linux)
362369
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+
}
366375
}
367376

368377
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+
}
372383
}
373384
#endif
374385

375386
launch()
376387

377388
#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+
}
380393
#endif
381394

382395
waitUntilExit()
@@ -389,15 +402,19 @@ private extension Process {
389402
errorPipe.fileHandleForReading.readabilityHandler = nil
390403
#endif
391404

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()
398417
}
399-
400-
return outputData.shellOutput()
401418
}
402419
}
403420

0 commit comments

Comments
 (0)