Repository navigation
fix(record): bound a cold video-validator compile by the stop's finalize budget #4150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
32e2b9a
679d0c2
361ac8e
2a1fefa
f791bb6
c4bc7d7
3884554
0d213c8
486aaaa
09431c2
a1af133
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,19 +117,15 @@ | |
| exportOverran = deadline.remainingMs() <= HELPER_EXIT_GRACE_MS; | ||
| throw error; | ||
| } | ||
| await waitForPlayableVideo(outputPath, { host, deadline }); | ||
| if (deadline.isExpired()) { | ||
| exportOverran = true; | ||
| throw new AppError( | ||
| 'COMMAND_FAILED', | ||
| 'The composited video was not verified within the budget', | ||
| ); | ||
| if (!(await waitForPlayableVideo(outputPath, { host, deadline }))) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🆕 🟡 Minor · The old code set Suggested change Pick one description for the not-verified path (either set exportOverran for it or drop the copied compile-timeout details) so the message and the details describe the same failure. DetailsAlso raised hereAlso raised at this location by javascript: Overlay's budget-overrun message now depends on the wait's false verdict coinciding with isExpired() — Also raised at this location by senior-developer: New persona: Reply Useful? Reply |
||
| exportOverran = deadline.isExpired(); | ||
| throw new AppError('COMMAND_FAILED', 'The composited video was not verified as playable'); | ||
| } | ||
| fs.renameSync(outputPath, videoPath); | ||
| } catch (error) { | ||
| const description = exportOverran | ||
| ? `${commandDescription}: the export did not finish within its ${OVERLAY_BUDGET_MS}ms budget; a lower --fps renders faster` | ||
| : commandDescription; | ||
|
Check warning on line 128 in packages/capture-kit/src/recording/overlay.ts
|
||
| const cause = | ||
| error instanceof AppError | ||
| ? error | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,9 @@ | |
|
|
||
| const SWIFT_CACHE_VERSION = '2'; | ||
| const LOCK_RETRY_DELAY_MS = 25; | ||
| // swift-driver runs each frontend job in its own process group. It stops them on SIGINT, but | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🆕 🔵 Nit · Comment on
Suggested change Extend the comment to state that the option applies to both the compile deadline and a caller's cancel signal, so any caller that forwards a signal keeps SIGINT. Detailspersona: Reply Useful? Reply |
||
| // SIGKILL or SIGTERM leaves them compiling with no parent. | ||
| const SWIFTC_STOP = { signal: 'SIGINT', graceMs: 2_000 } as const; | ||
|
Check notice on line 21 in packages/capture-kit/src/recording/swift-cache.ts
|
||
| const RECORDING_SCRIPT_SUBDIRECTORY = 'apple/runner/AgentDeviceRunner/RecordingScripts'; | ||
|
|
||
| /** | ||
|
|
@@ -130,6 +133,8 @@ | |
| source: string; | ||
| cacheName: string; | ||
| timeoutMs?: number; | ||
| /** Cancels the compile: kills `swiftc`, or stops waiting for another process's compile. */ | ||
| signal?: AbortSignal; | ||
| }): Promise<string> { | ||
| const cacheName = sanitizeCacheName(params.cacheName); | ||
| const key = hashParts([SWIFT_CACHE_VERSION, process.platform, process.arch, params.source]); | ||
|
|
@@ -143,6 +148,7 @@ | |
| executablePath, | ||
| sourceText: params.source, | ||
| timeoutMs: params.timeoutMs, | ||
| signal: params.signal, | ||
| }); | ||
| return executablePath; | ||
| } | ||
|
|
@@ -162,6 +168,7 @@ | |
| executablePath: string; | ||
| sourceText?: string; | ||
| timeoutMs?: number; | ||
| signal?: AbortSignal; | ||
| }): Promise<void> { | ||
| if (isExecutableFile(params.executablePath)) { | ||
| return; | ||
|
|
@@ -171,7 +178,7 @@ | |
| fs.mkdirSync(executableDir, { recursive: true }); | ||
| const timeoutMs = params.timeoutMs ?? 120_000; | ||
| await withProcessLock({ | ||
| acquire: () => acquireSwiftCacheLock(`${params.executablePath}.lock`, timeoutMs), | ||
| acquire: () => acquireSwiftCacheLock(`${params.executablePath}.lock`, timeoutMs, params.signal), | ||
| task: async () => { | ||
| // Another process may have published the executable while this one waited for the lock. | ||
| if (isExecutableFile(params.executablePath)) { | ||
|
|
@@ -193,6 +200,8 @@ | |
| } | ||
| await runCmd('xcrun', ['swiftc', ...params.sourcePaths, '-o', tempExecutablePath], { | ||
| timeoutMs, | ||
| signal: params.signal, | ||
| kill: SWIFTC_STOP, | ||
| env: buildSwiftToolEnv(params.host.env), | ||
| }); | ||
| fs.renameSync(tempExecutablePath, params.executablePath); | ||
|
|
@@ -206,6 +215,7 @@ | |
| async function acquireSwiftCacheLock( | ||
| lockDir: string, | ||
| timeoutMs: number, | ||
| signal: AbortSignal | undefined, | ||
| ): Promise<ProcessLockRelease> { | ||
| try { | ||
| return await acquireProcessLock({ | ||
|
|
@@ -217,6 +227,7 @@ | |
| }, | ||
| timeoutMs, | ||
| pollMs: LOCK_RETRY_DELAY_MS, | ||
| signal, | ||
| description: `Swift cache lock: ${lockDir} (${timeoutMs}ms)`, | ||
| }); | ||
| } catch (error) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer raised on
a1af133.🆕 🟡 Minor · Post-export playability check is now clamped to the 5 s exit grace, so a slow validator discards a good composite
Passing
deadlineintowaitForPlayableVideo(overlay.ts:120) now propagates it intoisPlayableVideo, where the validator run is clamped toMath.min(10_000, deadline.remainingMs())and a run that times out reads as not playable (video.ts:111-125). By construction the helper exits with aboutHELPER_EXIT_GRACE_MS= 5 000 ms left (overlay.ts:94-95, 114), so the very first validation attempt gets ≤5 s instead of the fixed 10 s the predecessor gave it, and every later poll gets less. On a loaded host where AVFoundation needs ~5.5 s to load the composited file, the attempt times out → false → 150 ms sleep → the deadline expires →waitForPlayableVideoreturns false →exportOverran = deadline.isExpired()is true → the verified-composite rename at overlay.ts:124 never happens and the user gets "the export did not finish within its 70000ms budget" with the touch overlay silently dropped, even though the composite was fine. The predecessor ran the same check with the full 10 s. Either give the post-export check its own floor (e.g.Math.max(1, deadline.remainingMs())with a reserved validation slice insideHELPER_EXIT_GRACE_MS), or document that the exit grace must cover the validator run.Suggested change
Reserve part of HELPER_EXIT_GRACE_MS for the post-export playability check (or keep that one check on the fixed VALIDATOR_RUN_TIMEOUT_MS) so a composite that is playable but slow to validate is not discarded at the end of the budget.
Details
persona:
senior-developerReply
@apex-sentinel-staging note ...to add context, or@apex-sentinel-staging pivot ...if this is wrong — I will re-check the cited code.Useful? Reply
@apex-sentinel-staging like, or@apex-sentinel-staging dislike <why>if it is not.