Skip to content

Commit

Permalink
Guard appending to outputData and errorData
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
harlanhaskins committed Dec 9, 2017
1 parent 0208caa commit 6e44fc1
Showing 1 changed file with 33 additions and 16 deletions.
49 changes: 33 additions & 16 deletions Sources/ShellOut.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import Foundation
import Dispatch

// MARK: - API

Expand Down Expand Up @@ -349,6 +350,12 @@ private extension Process {
launchPath = "/bin/bash"
arguments = ["-c", command]

// Because FileHandle's readabilityHandler might be called from a
// different queue from the calling queue, avoid a data race by
// protecting reads and writes to outputData and errorData on
// a single dispatch queue.
let outputQueue = DispatchQueue(label: "bash-output-queue")

var outputData = Data()
var errorData = Data()

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

#if !os(Linux)
outputPipe.fileHandleForReading.readabilityHandler = { handler in
let data = handler.availableData
outputData.append(data)
outputHandle?.write(data)
outputQueue.async {
let data = handler.availableData
outputData.append(data)
outputHandle?.write(data)
}
}

errorPipe.fileHandleForReading.readabilityHandler = { handler in
let data = handler.availableData
errorData.append(data)
errorHandle?.write(data)
outputQueue.async {
let data = handler.availableData
errorData.append(data)
errorHandle?.write(data)
}
}
#endif

launch()

#if os(Linux)
outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
outputQueue.sync {
outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
}
#endif

waitUntilExit()
Expand All @@ -389,15 +402,19 @@ private extension Process {
errorPipe.fileHandleForReading.readabilityHandler = nil
#endif

if terminationStatus != 0 {
throw ShellOutError(
terminationStatus: terminationStatus,
errorData: errorData,
outputData: outputData
)
// Block until all writes have occurred to outputData and errorData,
// and then read the data back out.
return try outputQueue.sync {
if terminationStatus != 0 {
throw ShellOutError(
terminationStatus: terminationStatus,
errorData: errorData,
outputData: outputData
)
}

return outputData.shellOutput()
}

return outputData.shellOutput()
}
}

Expand Down

0 comments on commit 6e44fc1

Please sign in to comment.