Note: This is believed to be similar to cURL through NSTask not terminating if a pipe is present, on which I recently placed a 200 point bounty. I'm posting here in order to provide additional details of my own case.
When pasted into the swift
REPL (using Swift 3), the following code randomly hangs with the associated files present on the MacOS file system, with probability greater than 50% on my machine:
import Foundation
func test() {
func innerTest(filenames: String...) {
let task = Process()
let pipe = Pipe()
let choice = true ? 1 : Int(arc4random_uniform(2))
let dir = ["/tmp", "/tmp/a/b/c/d/e/f/g/"][choice]
print(dir)
task.launchPath = "/usr/bin/swiftc"
let inputPaths = filenames.map { dir + $0 + ".swift" }
let outputFile = "/tmp/" + filenames.joined() + ".ast"
task.arguments = ["-dump-ast"] + inputPaths
task.standardError = pipe
task.launch()
task.waitUntilExit()
try! String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8)!.write(toFile: outputFile, atomically: true, encoding: String.Encoding.utf8)
}
innerTest(filenames: "AST", "Token")
}
test()
As with the other stackoverflow question, the hang never occurs unless the pipe is set on the task/process.
The code for selecting between /tmp
and /tmp/a/b/c/d/e/f/g
as source of the input files is present because it will never hang when using /tmp
as input and will randomly hang when using the longer directory path. I left it in there to allow one to experiment.
The contents of AST.swift is:
struct AST {
let type: String
let implicit: Bool
let name: String?
let attributes: [String:String]
let elements: [AST]
init(type: String, implicit: Bool = false, name: String? = nil, attributes: [String:String] = [:], elements: [AST] = []) {
self.type = type
self.implicit = implicit
self.name = name
self.attributes = attributes
self.elements = elements
}
}
The contents of Token.swift is:
class Token: Equatable {
private class LeftParen: Token {}
static let leftParen: Token = LeftParen()
private class RightParen: Token {}
static let rightParen: Token = RightParen()
class String: Token {
let value: Swift.String
init(_ value: Swift.String) { self.value = value }
}
class Symbol: Token {
let value: Swift.String
init(_ value: Swift.String) { self.value = value }
}
class KeyValue: Token {
let key: Swift.String
let value: Swift.String
init(_ key: Swift.String, _ value: Swift.String) {
self.key = key
self.value = value
}
}
static func symbol(_ s: Swift.String) -> Symbol { return Symbol(s) }
static func string(_ s: Swift.String) -> String { return String(s) }
static func keyValue(_ k: Swift.String, _ v: Swift.String) -> KeyValue { return KeyValue(k, v) }
func toString() -> Swift.String {
switch self {
case Token.leftParen:
return "("
case Token.rightParen:
return ")"
case let token as Token.String:
return token.value
case let token as Token.Symbol:
return token.value
case let token as Token.KeyValue:
return "\(token.key)=\(token.value)"
default:
fatalError()
}
}
}
func ==(lhs: Token, rhs: Token) -> Bool {
if let lhs = lhs as? Token.String, let rhs = rhs as? Token.String {
return lhs.value == rhs.value
}
if let lhs = lhs as? Token.Symbol, let rhs = rhs as? Token.Symbol {
return lhs.value == rhs.value
}
if let lhs = lhs as? Token.KeyValue, let rhs = rhs as? Token.KeyValue {
return lhs.key == rhs.key && lhs.value == rhs.value
}
return lhs === rhs
}
The above two .swift files need to be placed into a /tmp/a/b/c/d/e/f/g
directory in order for the test to run. If you want to see it always succeed when using /tmp
as a source of input, they need to be copied there as well.