mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-04 14:41:40 +00:00
* fix(macos): bound discovery subprocess execution * refactor(macos): migrate tailscale discovery runner * refactor(macos): make wide-area discovery asynchronous * test(macos): prove discovery subprocess cleanup * chore(macos): keep release notes in the PR
69 lines
2.4 KiB
Swift
69 lines
2.4 KiB
Swift
import Foundation
|
|
import Subprocess
|
|
|
|
enum BoundedCommand {
|
|
private static let defaultOutputLimit = 8 * 1024 * 1024
|
|
|
|
static func run(
|
|
path: String,
|
|
arguments: [String],
|
|
environment: [String: String]? = nil,
|
|
timeout: TimeInterval,
|
|
outputLimit: Int = defaultOutputLimit) async -> String?
|
|
{
|
|
guard timeout > 0, outputLimit > 0 else { return nil }
|
|
|
|
let executable: Executable = path.contains("/")
|
|
? .path(.init(path))
|
|
: .name(path)
|
|
let subprocessEnvironment = environment.map(self.environment(from:)) ?? .inherit
|
|
|
|
do {
|
|
return try await withThrowingTaskGroup(
|
|
of: String?.self,
|
|
returning: String?.self)
|
|
{ group in
|
|
group.addTask {
|
|
let result = try await Subprocess.run(
|
|
executable,
|
|
arguments: Arguments(arguments),
|
|
environment: subprocessEnvironment,
|
|
output: .string(limit: outputLimit))
|
|
guard result.terminationStatus.isSuccess,
|
|
let output = result.standardOutput?
|
|
.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
!output.isEmpty
|
|
else {
|
|
return nil
|
|
}
|
|
return output
|
|
}
|
|
group.addTask {
|
|
try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
|
|
return nil
|
|
}
|
|
|
|
// Keep timeout structured so swift-subprocess kills and reaps a canceled
|
|
// child before callers can start the next discovery probe.
|
|
defer { group.cancelAll() }
|
|
guard let result = try await group.next() else {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
} catch {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private static func environment(from values: [String: String]) -> Environment {
|
|
var converted: [Environment.Key: String] = [:]
|
|
converted.reserveCapacity(values.count)
|
|
for (key, value) in values {
|
|
guard let environmentKey = Environment.Key(rawValue: key) else { continue }
|
|
converted[environmentKey] = value
|
|
}
|
|
return .custom(converted)
|
|
}
|
|
}
|