diff --git a/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift b/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift index 8892f41584..4178a00829 100644 --- a/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +++ b/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift @@ -88,10 +88,15 @@ func makeRecordingExporter( return exporter } -/// Bounded asynchronous export: signals completion through a semaphore and cancels after 120s so a -/// wedged encoder cannot hang the recording pipeline past the caller's own timeout budget. +/// How long an export may run when the caller names no budget of its own. +let defaultRecordingExportTimeoutSeconds: Double = 120 + +/// Bounded asynchronous export: signals completion through a semaphore and cancels after +/// `timeoutSeconds` so a wedged or slow encoder cannot hang the recording pipeline past the +/// caller's own timeout budget. func runRecordingExport( _ exporter: AVAssetExportSession, + timeoutSeconds: Double = defaultRecordingExportTimeoutSeconds, timeoutMessage: String, failureMessage: String ) throws { @@ -99,7 +104,7 @@ func runRecordingExport( exporter.exportAsynchronously { semaphore.signal() } - if semaphore.wait(timeout: .now() + 120) == .timedOut { + if semaphore.wait(timeout: .now() + timeoutSeconds) == .timedOut { exporter.cancelExport() throw RecordingScriptError.exportFailed(timeoutMessage) } diff --git a/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift b/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift index 9c8077fb09..0c4a1bcec7 100644 --- a/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +++ b/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift @@ -145,6 +145,7 @@ func run() throws { ) try runRecordingExport( exporter, + timeoutSeconds: parsedArgs.timeoutSeconds, timeoutMessage: "Touch overlay export timed out.", failureMessage: "Touch overlay export failed." ) @@ -157,10 +158,11 @@ func run() throws { func parseArguments( _ arguments: [String] -) throws -> (inputPath: String, outputPath: String, eventsPath: String) { +) throws -> (inputPath: String, outputPath: String, eventsPath: String, timeoutSeconds: Double) { var inputPath: String? var outputPath: String? var eventsPath: String? + var timeoutSeconds = defaultRecordingExportTimeoutSeconds var index = 0 while index < arguments.count { @@ -185,6 +187,13 @@ func parseArguments( throw RecordingScriptError.invalidArgs("--quality must be one of: medium, high") } index += 2 + case "--timeout-ms": + let rawValue = try recordingOptionValue(arguments, nextIndex, "--timeout-ms") + guard let milliseconds = Double(rawValue), milliseconds > 0 else { + throw RecordingScriptError.invalidArgs("--timeout-ms must be a positive number") + } + timeoutSeconds = milliseconds / 1000 + index += 2 default: throw RecordingScriptError.invalidArgs("Unknown argument: \(argument)") } @@ -192,10 +201,10 @@ func parseArguments( guard let inputPath, let outputPath, let eventsPath else { throw RecordingScriptError.invalidArgs( - "Usage: recording-overlay.swift --input