@@ -94,6 +94,21 @@ final class OutputBuffer: @unchecked Sendable {
9494 }
9595}
9696
97+ /// Thread-safe helper to ensure a completion handler is called exactly once.
98+ final class OnceFlag : @unchecked Sendable {
99+ private let lock = NSLock ( )
100+ private var completed = false
101+
102+ /// Executes the given closure only on the first call; subsequent calls are no-ops.
103+ func callOnce( _ action: ( ) -> Void ) {
104+ lock. withLock {
105+ guard !completed else { return }
106+ completed = true
107+ action ( )
108+ }
109+ }
110+ }
111+
97112private func runCommand( _ args: [ String ] , input: String ? = nil ) throws -> CommandResult {
98113 let process = Process ( )
99114 process. executableURL = URL ( fileURLWithPath: " /usr/bin/env " )
@@ -117,12 +132,17 @@ private func runCommand(_ args: [String], input: String? = nil) throws -> Comman
117132 let stderrBuffer = OutputBuffer ( )
118133
119134 let group = DispatchGroup ( )
135+ let stdoutOnce = OnceFlag ( )
136+ let stderrOnce = OnceFlag ( )
137+
120138 group. enter ( )
121139 stdoutPipe. fileHandleForReading. readabilityHandler = { handle in
122140 let data = handle. availableData
123141 if data. isEmpty {
124- handle. readabilityHandler = nil
125- group. leave ( )
142+ stdoutOnce. callOnce {
143+ handle. readabilityHandler = nil
144+ group. leave ( )
145+ }
126146 return
127147 }
128148 stdoutBuffer. append ( data)
@@ -132,8 +152,10 @@ private func runCommand(_ args: [String], input: String? = nil) throws -> Comman
132152 stderrPipe. fileHandleForReading. readabilityHandler = { handle in
133153 let data = handle. availableData
134154 if data. isEmpty {
135- handle. readabilityHandler = nil
136- group. leave ( )
155+ stderrOnce. callOnce {
156+ handle. readabilityHandler = nil
157+ group. leave ( )
158+ }
137159 return
138160 }
139161 stderrBuffer. append ( data)
0 commit comments