Repository navigation
[cuebot] Add extra measures to prevent double booking - #2506
DiegoTavares merged 5 commits into
Conversation
Patch another condition that may lead to double-booking on retried frames. retryFrame released the frame (WAITING, proc link cleared) before killing RQD, so it was instantly booked onto a second host. FrameCompleteHandler then applied the killed run's report without checking the reporting proc still owned the frame, stopping the new run and freeing the frame again — every kill produced a report and every report freed the frame, sustaining a kill→report→free→rebook loop across hosts and silently discarding successful renders. - FrameCompleteHandler: ownership fence — a report may only stop the frame while its proc still holds it; superseded reports divert to the stale path. The stale path no longer unbooks a proc re-dispatched the same frame, and unapplied reports (notably exit 0) are logged and counted. - JobManagerSupport.retryFrame: kill and confirm dead via isFrameRunning polling (dispatcher.retry_frame_kill_budget_ms, shared across a bulk retry) before releasing; fail closed (frame stays RUNNING) when death cannot be confirmed. Gated by dispatcher.kill_running_frame_before_release_enabled. - New counters: frame_complete_superseded, frame_complete_dropped, frame_retry_deferred, frame_zombie_render.
📝 WalkthroughWalkthroughThe change adds metrics for stale completions, deferred retries, and zombie renders. Completion handling validates processor ownership. Running-frame retries can confirm render termination before release by using shared configurable deadlines. ChangesCompletion and retry safety
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant RQD
participant FrameCompleteHandler
participant FrameDetail
participant PrometheusMetricsCollector
RQD->>FrameCompleteHandler: submit frame completion report
FrameCompleteHandler->>FrameDetail: verify current processor ownership
alt report is superseded or stale
FrameCompleteHandler->>PrometheusMetricsCollector: increment dropped completion metric
FrameCompleteHandler->>FrameDetail: preserve newer frame ownership
else report is current
FrameCompleteHandler->>FrameDetail: stop completed frame
end
sequenceDiagram
participant JobManagerSupport
participant RQD
participant FrameDetail
participant PrometheusMetricsCollector
JobManagerSupport->>RQD: kill running render
JobManagerSupport->>RQD: poll render status
alt render exits
JobManagerSupport->>FrameDetail: release frame
else deadline or confirmation failure
JobManagerSupport->>PrometheusMetricsCollector: increment deferred retry metric
JobManagerSupport->>FrameDetail: leave frame RUNNING
end
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
cuebot/src/main/java/com/imageworks/spcue/service/JobManagerSupport.java (1)
441-451: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winThe shared budget bounds sleeping, not RQD RPC time.
retryFramesshares one deadline across all frames, andkillAndConfirmDeadguarantees at least oneisFrameRunningpoll per frame even after the deadline passes. Each poll is a gRPC call to RQD with its own deadline (grpc.rqd_task_deadline=10seconds). A bulk retry of N running frames therefore performs N kill calls plus at least N confirmation calls on the calling thread, and the wall-clock cost scales with N regardless ofdispatcher.retry_frame_kill_budget_ms.Consider skipping the confirmation poll once the shared deadline is spent and deferring those frames directly, or bounding the number of frames confirmed per request.
♻️ Proposed change: skip the poll when the shared budget is already spent
- // do/while so the render gets at least one confirmation poll even when the shared budget - // was spent on earlier frames. - do { + if (System.currentTimeMillis() >= deadlineMs) { + // The shared budget is spent. Defer rather than spend another RQD round trip per + // remaining frame; the kill above is still best-effort delivered. + return false; + } + do {Also applies to: 582-601
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cuebot/src/main/java/com/imageworks/spcue/service/JobManagerSupport.java` around lines 441 - 451, Update retryFrame and its killAndConfirmDead path to check the shared killDeadlineMs before issuing any RQD confirmation poll. When the deadline has expired after the kill request, skip isFrameRunning confirmation and defer the frame directly; retain confirmation while budget remains and preserve existing retryFrames behavior for frames processed within the budget.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java`:
- Around line 304-317: Update handleFrameCompleteReport to re-read the proc
through hostManager.getVirtualProc for every ownership decision, regardless of
frameDetail.state. After the fresh read, drop the stale report whenever
currentProc.frameId represents any newer assignment, including a different
frame, before redirecting or unbooking; retain the missing-proc early return.
In `@cuebot/src/main/java/com/imageworks/spcue/service/JobManagerSupport.java`:
- Around line 76-77: Add annotation processing for JobManagerSupport by enabling
context:annotation-config in applicationContext-service.xml, or explicitly wire
the Environment dependency through XML. Ensure the `@Autowired` env field in
JobManagerSupport is populated so configuration properties are applied.
---
Nitpick comments:
In `@cuebot/src/main/java/com/imageworks/spcue/service/JobManagerSupport.java`:
- Around line 441-451: Update retryFrame and its killAndConfirmDead path to
check the shared killDeadlineMs before issuing any RQD confirmation poll. When
the deadline has expired after the kill request, skip isFrameRunning
confirmation and defer the frame directly; retain confirmation while budget
remains and preserve existing retryFrames behavior for frames processed within
the budget.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 78a85e4c-ec12-4e67-a79a-5afa53ec283b
📒 Files selected for processing (7)
cuebot/src/main/java/com/imageworks/spcue/PrometheusMetricsCollector.javacuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.javacuebot/src/main/java/com/imageworks/spcue/dispatcher/HostReportHandler.javacuebot/src/main/java/com/imageworks/spcue/service/JobManagerSupport.javacuebot/src/main/resources/opencue.propertiescuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerOwnershipTests.javacuebot/src/test/java/com/imageworks/spcue/test/service/JobManagerSupportRetryFrameTests.java
Both release paths end in deleteVirtualProc(pk_proc), which has no fence on the proc's current frame assignment, but the fresh proc re-read only ran when the reported frame was RUNNING — and even then only dropped the report when the fresh read showed the same frame. A proc reassigned to a different frame between the entry snapshot and the release (e.g. duplicate report handled while the dispatcher rebooks the proc) would fall through to redirect/unbook and delete the row under the live run, orphaning it. The fix at FrameCompleteHandler.java:305 makes the re-read unconditional before any redirect/unbook and drops the report whenever the fresh read shows any frame assigned (newer run of the same frame or a different frame); the missing-proc early return is retained, and only a proc confirmed idle is released. Updated the method javadoc, adjusted staleReportReleasesIdleProc to model the cleared assignment on the fresh read, and added a regression test staleReportDoesNotReleaseProcReassignedToAnotherFrame.
Signed-off-by: Diego Tavares <dtavares@imageworks.com>
ramonfigueiredo
left a comment
There was a problem hiding this comment.
Approved with some suggested changes
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@cuebot/src/test/java/com/imageworks/spcue/test/service/JobManagerSupportRetryFrameTests.java`:
- Around line 108-114: Update the test fixture setup in
JobManagerSupportRetryFrameTests to inject a PrometheusMetricsCollector mock
into the service under test. In the deferred-retry tests, including
exhausted-budget and unreachable-host scenarios, verify that
incrementFrameRetryDeferred() is called exactly once when kill confirmation
fails.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 5c0e0cf6-dd58-408a-9c0a-22c0e0c438b7
📒 Files selected for processing (4)
cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.javacuebot/src/main/java/com/imageworks/spcue/service/JobManagerSupport.javacuebot/src/main/resources/opencue.propertiescuebot/src/test/java/com/imageworks/spcue/test/service/JobManagerSupportRetryFrameTests.java
🚧 Files skipped from review as they are similar to previous changes (3)
- cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java
- cuebot/src/main/resources/opencue.properties
- cuebot/src/main/java/com/imageworks/spcue/service/JobManagerSupport.java
| // Kill-before-release enabled by default, with a full confirmation budget. Tests whose | ||
| // render is confirmed gone on the first poll never sleep; tests that need the budget to be | ||
| // spent set it explicitly. | ||
| when(env.getProperty(eq(KILL_BEFORE_RELEASE_PROPERTY), eq(Boolean.class), eq(true))) | ||
| .thenReturn(true); | ||
| when(env.getProperty(eq(RETRY_KILL_BUDGET_PROPERTY), eq(Long.class), eq(10000L))) | ||
| .thenReturn(10000L); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Test the frame_retry_deferred metric.
The fixture does not inject PrometheusMetricsCollector. The deferred-retry tests therefore cannot detect a missing incrementFrameRetryDeferred() call or missing collector wiring.
Inject a collector mock. Assert one increment when kill confirmation fails, including the exhausted-budget and unreachable-host paths.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@cuebot/src/test/java/com/imageworks/spcue/test/service/JobManagerSupportRetryFrameTests.java`
around lines 108 - 114, Update the test fixture setup in
JobManagerSupportRetryFrameTests to inject a PrometheusMetricsCollector mock
into the service under test. In the deferred-retry tests, including
exhausted-budget and unreachable-host scenarios, verify that
incrementFrameRetryDeferred() is called exactly once when kill confirmation
fails.
d9f5ba2
into
AcademySoftwareFoundation:master
Follow-up to #2506: a restarted RQD answers kill-before-release with NOT_FOUND, which was wrapped as a failure, so lostProc deferred provably dead frames indefinitely and re-logged the "Deferring release of lost proc" WARN every 30s. - RqdClientGrpc.killFrame now treats NOT_FOUND as confirmed-stopped (matching isFrameRunning); only unknown outcomes throw RqdClientException. - Bound the deferral: after dispatcher.lost_proc_max_defer_ms (default 20 min) without a ping proving the frame alive, release the proc and fail the frame closed to DEAD (manual retry) instead of deferring again. Negative disables the bound. - Boot-time fence: a host whose reported boot time is later than the proc's dispatch (plus a clock-skew margin) cannot be running the frame, so release it to WAITING even when the kill cannot be delivered. - Orphaned-proc reaper: order candidates oldest-ping first (deterministic batch turnover) and make the batch size configurable (maintenance.orphaned_proc_batch_size, default 100) so unconfirmable procs cannot starve releasable orphans. New unit tests cover the fail-closed bound, the boot fence, and killFrame status classification against a real localhost gRPC server; DAO tests cover the new queries. ## LLM usage disclosure Claude Opus was used for investigating the logs and implementing this fix. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved handling of lost processes by detecting stale pings and host reboots. - Automatically marks frames as **DEAD** when lost-process recovery cannot be confirmed within the configured time limit. - Treats already-missing frames as successfully stopped during kill operations. - Orphaned-process cleanup now processes oldest entries first. - **Configuration** - Added settings for lost-process deferral limits and orphaned-process cleanup batch size. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Close the remaining free-then-rebook path that survived #2506/#2508: the generic launch rollback released the booking first (proc deleted, frame back to WAITING) and fired a best-effort kill whose failure was swallowed. When a launchFrame RPC fails on the Cuebot side after the request was delivered (deadline expired waiting for the response, connection dropped mid-call), the render is alive on the host, the kill fails on the same broken network, and the freed frame is instantly re-booked by another Cuebot -- each failed launch becomes a double-booking under RPC instability. - RqdClientGrpc.launchFrame classifies failures: transport-level codes (DEADLINE_EXCEEDED, UNAVAILABLE, CANCELLED, INTERNAL, DATA_LOSS) throw the new RqdLaunchUnknownOutcomeException; application-level responses (RQD rejects with ABORTED/FAILED_PRECONDITION/INVALID_ARGUMENT) prove the frame never started and keep the plain RqdClientException. - Both copies of the release-first rollback route unknown-outcome launch failures through DispatchSupport.resolveUnknownLaunchOutcome instead: CoreUnitDispatcher's DispatchFrameTemplate (the production dispatch path, used by dispatchHost and dispatchProcToJob) and AbstractDispatcher.dispatchProc/dispatchHost (the LocalDispatcher and RedirectManager paths). The legacy rollback is preserved for proven rejections. - resolveUnknownLaunchOutcome confirms before releasing anything: a frame confirmed running keeps its booking and finishes through its own frame complete report; a frame confirmed not running (two consecutive isFrameRunning polls within dispatcher.launch_confirm_budget_ms) is released for re-dispatch; anything unconfirmable keeps the booking (fail closed) and is reclaimed by the orphaned-proc reaper's own kill-and-confirm path. A non-positive budget restores the legacy behavior. - New counter cue_frame_launch_outcome_unknown_total{resolution} plus DispatchSupport.unknownLaunchOutcomes. Unit tests cover the status classification against a real localhost gRPC server, the resolver's four resolutions, and the rollback routing in both dispatcher rollback sites. ## LLM usage disclosure Claude Opus was used for implementing the fix and unit tests. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Prevented frames from being double-booked when launch results are uncertain. * Verified frame status before releasing bookings after uncertain launch failures. * Preserved rollback behavior for confirmed launch failures. * Prevented rollback from clearing frames belonging to newer dispatches. * **New Features** * Added configurable launch-outcome confirmation with a 20-second default window and 7-second polling interval. * Added Prometheus metrics for launches with unknown outcomes. * Improved monitoring and handling of uncertain launch failures. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Brings in AcademySoftwareFoundation#2473, AcademySoftwareFoundation#2497, AcademySoftwareFoundation#2501, AcademySoftwareFoundation#2502, AcademySoftwareFoundation#2504, AcademySoftwareFoundation#2506, AcademySoftwareFoundation#2508, AcademySoftwareFoundation#2510, AcademySoftwareFoundation#2511 and the cueweb dependency bumps. Conflict resolutions: - applicationContext-service.xml, FrameDao: keep both sides. - DispatchQuery: keep the branch's layer.pk_layer fix plus master's ts_start_after predicate. - RqdClientGrpc: master's split catch and unknown-outcome throw, keeping the branch's warn log of the underlying launch failure on both paths. - FrameCompleteHandler: master refactored the class into small methods, so the scheduler hooks were re-applied on top of the new structure rather than merged line by line. The drain path (resolveForDrain) now also runs master's orphan-finalize and run-ownership fences, and handleStaleCompletion delegates to handleStaleReport so scheduler-owned shows get the same duplicate check and re-read guard as legacy ones; its Scheduler call site passes the report. The bookingOff guards became isBookingOff() calls inside the extracted booking methods, and the per-frame OOM bump moved into retryFrameWithRaisedMemory. FrameCompleteHandlerOwnershipTests builds the handler from a bare Environment mock; the constructor now unboxes numeric properties, so the mock returns the supplied defaults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0114CS5RrhDjTJse5faC4Pcb
Patch another condition that may lead to double-booking on retried frames.
retryFrame released the frame (WAITING, proc link cleared) before killing RQD, so it was instantly booked onto a second host. FrameCompleteHandler then applied the killed run's report without checking the reporting proc still owned the frame, stopping the new run and freeing the frame again — every kill produced a report and every report freed the frame, sustaining a kill→report→free→rebook loop across hosts and silently discarding successful renders.
LLM usage disclosure
Claude Opus was used for investigating the root cause and creating unit tests.
Summary by CodeRabbit
Bug Fixes
Monitoring