Repository navigation
[rqd/cuebot] Add a retry mechanism for FrameCompleteReport - #2473
DiegoTavares merged 16 commits into
Conversation
RQD silently drops FrameCompleteReports In rust/crates/rqd/src/system/machine.rs: 1. monitor_running_frames evicts the finished frame from the cache before the report is confirmed (line 405-420): the retain() closure returns false for FrameState::Finished, removing it, then calls handle_finished_frames. 2. handle_finished_frames is fire-and-forget (line 573-588): if send_frame_complete_report returns Err, it just error!(...) and moves on. No requeue, no retry on the next monitor cycle, no persistence. The frame is already gone from the cache. The completion is lost forever. 3. The retry layer cannot save it (report/retry/backoff_policy.rs:74-103): it inspects the HTTP status and retries only on transport errors or HTTP 500/502/503. But a gRPC application error — which is exactly what Cuebot returns when handleFrameCompleteReport throws RqdRetryReportException — arrives as HTTP 200 with a grpc-status trailer. The policy sees HTTP 200 → Outcome::Return → not retried; send_frame_complete_report then surfaces it as Err (report_client.rs:226-231) → dropped. With this a completed frame under a failing rqd (caused by memory pressure or cpu eviction for exemple) could lead to a completed frame not having its state synchronized to Cuebot.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds bounded orphan-frame recovery, RQD liveness checks, proc-independent completion handling, expanded completion-state tests, and retryable RQD completion delivery. ChangesCuebot orphaned-frame recovery
RQD retryable completion delivery
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant MaintenanceManagerSupport
participant FrameDao
participant RqdClientGrpc
participant FrameCompleteHandler
MaintenanceManagerSupport->>FrameDao: getOrphanedFrames(limit)
FrameDao-->>MaintenanceManagerSupport: FrameDetail with lastResource
MaintenanceManagerSupport->>RqdClientGrpc: killFrame and isFrameRunning
RqdClientGrpc-->>MaintenanceManagerSupport: termination result
MaintenanceManagerSupport->>FrameCompleteHandler: process orphaned completion
FrameCompleteHandler->>FrameCompleteHandler: validate and finalize frame
sequenceDiagram
participant MachineMonitor
participant pending_completions
participant Cuebot
MachineMonitor->>pending_completions: enqueue finished frame and release cores
pending_completions->>Cuebot: send completion report
alt Cuebot acknowledges
Cuebot-->>pending_completions: success
pending_completions->>pending_completions: remove frame
else delivery fails
Cuebot-->>pending_completions: error
pending_completions->>pending_completions: retry later
end
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1⚔️ Resolve merge conflicts 💡
🧪 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 |
An orphaned frame (RUNNING with no proc, because its proc was removed while RQD was still rendering) was reset straight to WAITING by the maintenance reaper. The dispatcher could then rebook it onto a second host while the original RQD process was still alive -- a double booking. Redesign the reaper to kill-then-confirm-dead before clearing: - Add RqdClient.isFrameRunning(host, frameId), backed by RQD's GetRunningFrameStatus. A NOT_FOUND response means the render has been reaped (confirmed dead); any other failure is raised as an RqdClientException so callers can tell "confirmed gone" from "could not reach". - clearOrphanedFrames() now kills each orphan on its last-known host and polls until death is confirmed. Only then is the frame reset to WAITING (safe auto-retry). If death cannot be confirmed -- the per-pass kill budget is exhausted or the host is unreachable -- the frame is failed (DEAD) so it needs a manual retry instead of risking a rebook. Fail closed: a stuck frame is acceptable, a silent double booking is not. - The whole pass is bounded by maintenance.orphaned_frame_kill_budget_ms (default 30s) so a batch of unreachable hosts cannot stall maintenance. - getOrphanedFrames() now returns List<FrameDetail> (mapped via the existing FRAME_DETAIL_MAPPER) so the reaper reads the last host from lastResource directly, dropping a second per-frame DB lookup. Also document why FrameCompleteHandler's orphan-finalize path intentionally skips the layer min-memory bump (it is proc-specific and there is no proc on that path). Tests: add MaintenanceManagerOrphanFrameTests (DB-free) covering the confirmed-dead -> WAITING, host-unreachable -> DEAD, budget-exhausted -> DEAD, and never-ran -> WAITING branches; extend FrameDaoTests.testgetOrphanedFrames to assert lastResource is populated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
abeec3f to
bd98c03
Compare
Signed-off-by: Diego Tavares <dtavares@imageworks.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java (2)
604-653: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExit-status retouch logic duplicated verbatim from
handleFrameCompleteReport.The
int exitStatus = report.getExitStatus(); if (frameDetail.exitStatus == Dispatcher.EXIT_STATUS_MEMORY_FAILURE) { exitStatus = frameDetail.exitStatus; }block at Lines 650-653 is an exact copy of Lines 187-190. Extracting a small private static helper (e.g.resolveExitStatus(FrameCompleteReport, FrameDetail)) shared by both call sites would remove the duplication and prevent the two paths from silently drifting apart if this logic changes later.♻️ Proposed refactor
+ private static int resolveExitStatus(FrameCompleteReport report, FrameDetail frameDetail) { + int exitStatus = report.getExitStatus(); + if (frameDetail.exitStatus == Dispatcher.EXIT_STATUS_MEMORY_FAILURE) { + exitStatus = frameDetail.exitStatus; + } + return exitStatus; + }Then replace both inline blocks with
resolveExitStatus(report, frameDetail).🤖 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/dispatcher/FrameCompleteHandler.java` around lines 604 - 653, Extract the duplicated exit-status selection from handleFrameCompleteReport and finalizeOrphanedFrameComplete into a private static helper such as resolveExitStatus(FrameCompleteReport, FrameDetail). Have the helper return the report exit status unless FrameDetail.exitStatus is Dispatcher.EXIT_STATUS_MEMORY_FAILURE, in which case return that stored status, and replace both inline blocks with the helper call.
685-734: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFactor out the shared post-completion steps.
handleOrphanedPostFrameCompletestill duplicates the dependency, layer-complete, job-complete, and layer-optimization logic fromhandlePostFrameCompleteOperations; extracting that shared block would keep the two paths in sync. The orphaned path can keep skipping proc-dependent Kafka publishing, sincebuildFrameCompleteEventstill requires aVirtualProc.🤖 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/dispatcher/FrameCompleteHandler.java` around lines 685 - 734, Extract the shared dependency satisfaction, layer-completion, layer optimization, and job-completion logic from handlePostFrameCompleteOperations and handleOrphanedPostFrameComplete into a common helper, preserving each path’s existing state conditions and arguments. Keep proc-dependent actions, including Kafka event publishing and memory updates, in handlePostFrameCompleteOperations only; have the orphaned path invoke the shared helper without requiring a VirtualProc.rust/crates/rqd/src/system/machine.rs (1)
554-649: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo visibility/backpressure on
pending_completionsduring extended Cuebot outages.Every
Errfromsend_frame_complete_reportis retried forever with no cap, exponential backoff, or metric on queue size/age (Lines 640-646). Cuebot'shandleFrameCompleteReportis designed so permanently-stale/dropped cases return normally (noErr) rather than throw, so in the common case this queue should stay small — but during a genuine Cuebot outage, cores keep getting released and reused for new frames while old completions pile up inpending_completionsunbounded, with no signal to operators that a backlog is building. Consider emitting a periodic warning/metric keyed offpending_completions.len()(and/or oldest-entry age) when non-empty across cycles, so a growing backlog is observable before it becomes a bigger memory or duplicate-delivery concern.🤖 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 `@rust/crates/rqd/src/system/machine.rs` around lines 554 - 649, Add backlog observability to flush_pending_completions by emitting a periodic warning or metric when pending_completions remains non-empty across monitor cycles, including its current size and, if available, oldest-entry age. Keep the existing retry and at-least-once delivery behavior unchanged, and avoid logging on every individual retry.
🤖 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/service/MaintenanceManagerSupport.java`:
- Around line 192-227: Update clearOrphanedFrames so frames encountered after
phaseDeadlineMs still receive a best-effort, non-blocking kill attempt before
being marked DEAD. Reuse the existing kill mechanism or its non-blocking variant
rather than calling killAndConfirmDead, which may exceed the exhausted budget;
preserve the current failedUnconfirmed accounting and status updates.
---
Nitpick comments:
In
`@cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java`:
- Around line 604-653: Extract the duplicated exit-status selection from
handleFrameCompleteReport and finalizeOrphanedFrameComplete into a private
static helper such as resolveExitStatus(FrameCompleteReport, FrameDetail). Have
the helper return the report exit status unless FrameDetail.exitStatus is
Dispatcher.EXIT_STATUS_MEMORY_FAILURE, in which case return that stored status,
and replace both inline blocks with the helper call.
- Around line 685-734: Extract the shared dependency satisfaction,
layer-completion, layer optimization, and job-completion logic from
handlePostFrameCompleteOperations and handleOrphanedPostFrameComplete into a
common helper, preserving each path’s existing state conditions and arguments.
Keep proc-dependent actions, including Kafka event publishing and memory
updates, in handlePostFrameCompleteOperations only; have the orphaned path
invoke the shared helper without requiring a VirtualProc.
In `@rust/crates/rqd/src/system/machine.rs`:
- Around line 554-649: Add backlog observability to flush_pending_completions by
emitting a periodic warning or metric when pending_completions remains non-empty
across monitor cycles, including its current size and, if available,
oldest-entry age. Keep the existing retry and at-least-once delivery behavior
unchanged, and avoid logging on every individual retry.
🪄 Autofix (Beta)
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
Run ID: 6936da85-5488-4e29-9fc9-1304f8b9aadc
📒 Files selected for processing (10)
cuebot/src/main/java/com/imageworks/spcue/dao/FrameDao.javacuebot/src/main/java/com/imageworks/spcue/dao/postgres/FrameDaoJdbc.javacuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.javacuebot/src/main/java/com/imageworks/spcue/rqd/RqdClient.javacuebot/src/main/java/com/imageworks/spcue/rqd/RqdClientGrpc.javacuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.javacuebot/src/main/resources/conf/spring/applicationContext-service.xmlcuebot/src/main/resources/opencue.propertiescuebot/src/test/java/com/imageworks/spcue/test/dao/postgres/FrameDaoTests.javarust/crates/rqd/src/system/machine.rs
Signed-off-by: Diego Tavares <dtavares@imageworks.com>
Refactor FrameCompleteHandler for readability with no behavior change: - Split handlePostFrameCompleteOperations into named steps: evaluateProcHealth (explicit KEEP/UNBOOK/REDIRECTED result), retryFrameWithRaisedMemory, releaseProcFromUndispatchableJob, maybeTransferProcToNeedierJob, bookNextFrameOnProc and publishLayerCompletedTelemetry. - Deduplicate the four anonymous KeyRunnable blocks into a single queueDispatchTask helper. - Flatten determineFrameState into early returns and drop the no-op FrameCompleteReport rebuilds and unreachable isLocalDispatch checks. - Remove an obsolete comment and unused import, fix log message typos, rename jsmMover to jmsMover, and make resolveExitStatus public so tests can cover it. Test coverage added ahead of the refactor to pin behavior: - New FrameCompleteHandlerFrameStateTests: 25 pure unit tests covering every determineFrameState branch and resolveExitStatus. - FrameCompleteHandlerTests: 20 new integration tests for the orphaned frame finalization paths, every unbook decision branch, stale-report redirect/unbook queuing, redirect-vs-memory-failure precedence, stranded cores, finished-job JMS notification, and shutdown rejection. Consolidated the duplicated min-mem helpers (the Docker variant was dead code) and added the missing Docker memory-failure test.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
cuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerTests.java (1)
624-661: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMake
HandlerMocksinstall/restore inseparable.
frameCompleteHandleris a shared singleton bean in the Spring test context.install()andrestore()are two separate calls, so a leaked restore contaminates every later test in the context. Every current call site pairs them correctly, so there is no defect today. The pattern is fragile: one statement added betweeninstall()and thetryblock would leak the mocks.Make the pairing structural. Implement
AutoCloseableso tests use try-with-resources, or add arunWith(Runnable)method that installs, runs, and restores in a finally block. This file already documents shared-state leakage risk forShowDaoJdbcat Lines 528-533, so the same protection is consistent here.♻️ Proposed refactor
- private class HandlerMocks { + private class HandlerMocks implements AutoCloseable { final DispatchSupport origDispatchSupport = frameCompleteHandler.getDispatchSupport(); @@ void restore() { @@ frameCompleteHandler.setRedirectManager(origRedirectManager); } + + `@Override` + public void close() { + restore(); + } + + void runWith(Runnable body) { + install(); + try { + body.run(); + } finally { + restore(); + } + } }🤖 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/dispatcher/FrameCompleteHandlerTests.java` around lines 624 - 661, Make HandlerMocks’ install/restore lifecycle structural by implementing AutoCloseable (or providing an equivalent runWith(Runnable) wrapper) so installation is always paired with restoration in a finally-backed scope. Update every existing HandlerMocks call site in FrameCompleteHandlerTests to use the new scoped pattern, preserving the original mock state after each test.cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java (1)
341-359: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueResolve
LayerDetailonce inpublishLayerCompletedTelemetry.The method calls
jobManager.getLayerDetail(frame.getLayerId())at Line 344 and again at Line 354. When both Prometheus and Kafka are enabled, this issues two identical reads per completed layer. Resolve the value lazily so it is read at most once and still skipped when both sinks are disabled.♻️ Proposed refactor
private void publishLayerCompletedTelemetry(DispatchFrame frame) { + boolean prometheusEnabled = prometheusMetrics != null; + boolean kafkaEnabled = kafkaEventPublisher != null && kafkaEventPublisher.isEnabled(); + if (!prometheusEnabled && !kafkaEnabled) { + return; + } + LayerDetail layerDetail = jobManager.getLayerDetail(frame.getLayerId()); + - if (prometheusMetrics != null) { + if (prometheusEnabled) { ExecutionSummary layerSummary = jobManager.getExecutionSummary((LayerInterface) frame); - LayerDetail layerDetail = jobManager.getLayerDetail(frame.getLayerId()); prometheusMetrics.recordLayerMaxRuntime(layerSummary.highFrameSec, frame.show, frame.shot, layerDetail.type.toString()); if (layerSummary.highMemoryKb > 0) { prometheusMetrics.recordLayerMaxMemory(layerSummary.highMemoryKb * 1024L, frame.show, frame.shot, layerDetail.type.toString()); } } - if (kafkaEventPublisher != null && kafkaEventPublisher.isEnabled()) { - LayerDetail layerDetail = jobManager.getLayerDetail(frame.getLayerId()); + if (kafkaEnabled) { LayerEvent layerEvent = monitoringEventBuilder.buildLayerEvent( EventType.LAYER_COMPLETED, layerDetail, frame.getName(), frame.show); kafkaEventPublisher.publishLayerEvent(layerEvent); } }🤖 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/dispatcher/FrameCompleteHandler.java` around lines 341 - 359, Update publishLayerCompletedTelemetry to resolve LayerDetail lazily and at most once: reuse a single local value across the Prometheus and Kafka branches, initializing it only when the corresponding sink is enabled. Preserve the existing behavior of skipping the lookup when both sinks are disabled.rust/crates/rqd/src/system/machine.rs (1)
86-92: 🩺 Stability & Availability | 🔵 TrivialConsider a bound and a metric for
pending_completions.
pending_completionshas no size or age bound. Entries leave the map only on a successful acknowledgment or on a non-terminal state. If Cuebot rejects one specific report permanently, that entry and itsArc<RunningFrame>stay for the process lifetime. The retained size is bounded by the host's frame concurrency, so this is not an outage risk, but it is silent.The current warning at Lines 618-627 is log-only. Export the backlog size and oldest-entry age as gauges so an operator can alert on a stuck report instead of reading logs. Also consider a configurable maximum age after which an entry is dropped with an error, so a poison report cannot persist forever.
[operational]🤖 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 `@rust/crates/rqd/src/system/machine.rs` around lines 86 - 92, Update the pending_completions monitoring around its insertion and retry logic to export gauges for current backlog size and oldest pending entry age, using the existing metrics mechanism and updating them as entries change. Also add a configurable maximum age for pending completions; when an entry exceeds it, remove the entry and emit an error, while preserving normal retry and acknowledgment behavior for entries within the limit.
🤖 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 727-738: The memory-failure retry exemption is inconsistent
because determineFrameState() and the maxRetries check ignore the Cuebot-stored
status from resolveExitStatus(). Pass frameDetail through determineFrameState()
and use the resolved exit status or corresponding frame detail when applying the
memory-failure exemption, so Cuebot-initiated memory kills past maxRetries
follow the same retry path as RQD-initiated OOMs.
In
`@cuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.java`:
- Around line 203-208: Update the budget-exhaustion branch in the method
containing killFrameBestEffort so dead is assigned from that helper’s result
instead of being forced false, preserving WAITING when no render exists and null
is returned. Add a regression test covering an empty lastResource with a zero
kill budget and verify the frame remains retryable in WAITING.
In `@rust/crates/rqd/src/system/machine.rs`:
- Around line 603-627: Update the backlog warning logic around
pending_completions and oldest_age so FailedBeforeStart entries and
clock-backward cases still emit a warning when no age is available. Preserve the
age-based warning for entries with a valid end_time, and add a backlog-size
fallback using the existing backlog value when oldest_age is None.
---
Nitpick comments:
In
`@cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java`:
- Around line 341-359: Update publishLayerCompletedTelemetry to resolve
LayerDetail lazily and at most once: reuse a single local value across the
Prometheus and Kafka branches, initializing it only when the corresponding sink
is enabled. Preserve the existing behavior of skipping the lookup when both
sinks are disabled.
In
`@cuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerTests.java`:
- Around line 624-661: Make HandlerMocks’ install/restore lifecycle structural
by implementing AutoCloseable (or providing an equivalent runWith(Runnable)
wrapper) so installation is always paired with restoration in a finally-backed
scope. Update every existing HandlerMocks call site in FrameCompleteHandlerTests
to use the new scoped pattern, preserving the original mock state after each
test.
In `@rust/crates/rqd/src/system/machine.rs`:
- Around line 86-92: Update the pending_completions monitoring around its
insertion and retry logic to export gauges for current backlog size and oldest
pending entry age, using the existing metrics mechanism and updating them as
entries change. Also add a configurable maximum age for pending completions;
when an entry exceeds it, remove the entry and emit an error, while preserving
normal retry and acknowledgment behavior for entries within the limit.
🪄 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: 5313165b-5983-4520-9c8e-ceb4ba96a748
📒 Files selected for processing (8)
cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.javacuebot/src/main/java/com/imageworks/spcue/dispatcher/commands/DispatchRqdKillFrameMemory.javacuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.javacuebot/src/main/resources/opencue.propertiescuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerFrameStateTests.javacuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerTests.javacuebot/src/test/java/com/imageworks/spcue/test/service/MaintenanceManagerOrphanFrameTests.javarust/crates/rqd/src/system/machine.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- cuebot/src/main/resources/opencue.properties
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java (1)
381-384: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winClear
FrameDetail.exitStatuswhen the frame retries.
updateFrameStartedsets the frame toRUNNINGandts_stopped = null, but it leavesint_exit_statusuntouched beforeFrameCompleteHandler.resolveExitStatusandisMemoryFailure(report, frameDetail)can pull it back from the stale memory-failure value. This lets a later successful report be treated as a memory failure and recorded as such. Add a regression test for a memory-killed frame that later succeeds.🤖 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/dispatcher/FrameCompleteHandler.java` around lines 381 - 384, Clear FrameDetail.exitStatus when updateFrameStarted retries a frame, before resolveExitStatus or isMemoryFailure can inspect it, while preserving the RUNNING and ts_stopped reset behavior. Add a regression test covering a memory-killed frame that retries and later completes successfully without being recorded as a memory failure.cuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.java (1)
245-264: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winEnforce the orphan cleanup budget on polling waits and RQD calls.
Thread.sleep(ORPHAN_KILL_POLL_INTERVAL_MS)waits the full 500 ms even when only a smaller remainder of the cleanup budget remains; sleep for the remaining budget or exit when the phase deadline has been crossed. The RQD stubs userqdTaskDeadlineSecondsviawithDeadlineAfter, so an RQD call may still block longer than the configured orphan-cleanup budget; cap RQD deadlines by the remaining cleanup budget instead.🤖 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/MaintenanceManagerSupport.java` around lines 245 - 264, Update the orphan cleanup loop around killFrameBestEffort and rqdClient.isFrameRunning to enforce phaseDeadlineMs: compute the remaining budget before each RQD call and pass a deadline capped to that remainder, and before Thread.sleep either exit when no budget remains or sleep only for the smaller of ORPHAN_KILL_POLL_INTERVAL_MS and the remaining time. Preserve the existing success and fail-closed handling.
🤖 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.
Outside diff comments:
In
`@cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java`:
- Around line 381-384: Clear FrameDetail.exitStatus when updateFrameStarted
retries a frame, before resolveExitStatus or isMemoryFailure can inspect it,
while preserving the RUNNING and ts_stopped reset behavior. Add a regression
test covering a memory-killed frame that retries and later completes
successfully without being recorded as a memory failure.
In
`@cuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.java`:
- Around line 245-264: Update the orphan cleanup loop around killFrameBestEffort
and rqdClient.isFrameRunning to enforce phaseDeadlineMs: compute the remaining
budget before each RQD call and pass a deadline capped to that remainder, and
before Thread.sleep either exit when no budget remains or sleep only for the
smaller of ORPHAN_KILL_POLL_INTERVAL_MS and the remaining time. Preserve the
existing success and fail-closed handling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a272c249-7df1-40bf-b27e-61c9ad998e05
📒 Files selected for processing (6)
cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.javacuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.javacuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerFrameStateTests.javacuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerTests.javacuebot/src/test/java/com/imageworks/spcue/test/service/MaintenanceManagerOrphanFrameTests.javarust/crates/rqd/src/system/machine.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- cuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerFrameStateTests.java
- cuebot/src/test/java/com/imageworks/spcue/test/service/MaintenanceManagerOrphanFrameTests.java
- rust/crates/rqd/src/system/machine.rs
- cuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerTests.java
ramonfigueiredo
left a comment
There was a problem hiding this comment.
Thanks, @DiegoTavares .
I will partially approve it after you address the suggested changes.
Delivery is at-least-once, not exactly-once. A report whose response is lost in transit is resent and Cuebot receives a duplicate. Cuebot tolerates duplicates rather than treating them as a strict no-op: the frame stop is version-fenced, so a resent report whose frame is no longer RUNNING at the reported version does not re-stop it, and if the proc has since been rebooked onto its next frame the duplicate is dropped instead of unbooking that proc. A duplicate therefore cannot re-complete a frame or orphan a running one. b
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
rust/crates/rqd/src/system/machine.rs (2)
574-583: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winRetry failed core releases or clear the finished reservation.
release_cores()removes the booking only on success, andsanitize_reservations()keeps the failed reservation while the monitor reports it as active and it has not yet reached the 60s grace period. Queue the frame for Cuebot completion separately and retryrelease_cores()for failed finished frames, or run a sanitization path that removes completed reservations outsiderunning_frames.🤖 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 `@rust/crates/rqd/src/system/machine.rs` around lines 574 - 583, Update the finished-frame handling around release_cores and pending_completions so a failed core release is retried or the completed reservation is explicitly removed from reservation tracking outside running_frames. Keep queuing the frame for Cuebot completion independently, and ensure failed releases do not remain indefinitely until the 60-second sanitization grace period.
696-707: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPreserve
run_timefor queued frame completions.
send_frame_complete_report(..., 0)serializes zero intoFrameCompleteReport.run_time, while frame runtime is used by Cuebot metrics and the Python RQD path reports the measured value. Capture the runtime when enqueueing or at flush time forFrameState::Finished; use0explicitly forFailedBeforeStartpaths.🤖 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 `@rust/crates/rqd/src/system/machine.rs` around lines 696 - 707, Update the frame-completion reporting flow around send_frame_complete_report to pass the measured run_time for queued FrameState::Finished completions instead of the hardcoded 0, preserving it through enqueueing or flush. Keep 0 only for FrameState::FailedBeforeStart reports, and ensure the serialized FrameCompleteReport retains the runtime used by metrics.
🤖 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.
Outside diff comments:
In `@rust/crates/rqd/src/system/machine.rs`:
- Around line 574-583: Update the finished-frame handling around release_cores
and pending_completions so a failed core release is retried or the completed
reservation is explicitly removed from reservation tracking outside
running_frames. Keep queuing the frame for Cuebot completion independently, and
ensure failed releases do not remain indefinitely until the 60-second
sanitization grace period.
- Around line 696-707: Update the frame-completion reporting flow around
send_frame_complete_report to pass the measured run_time for queued
FrameState::Finished completions instead of the hardcoded 0, preserving it
through enqueueing or flush. Keep 0 only for FrameState::FailedBeforeStart
reports, and ensure the serialized FrameCompleteReport retains the runtime used
by metrics.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: a0fa7e3a-94f8-4863-b823-44ec99a21f4e
📒 Files selected for processing (3)
cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.javacuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerTests.javarust/crates/rqd/src/system/machine.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- cuebot/src/main/java/com/imageworks/spcue/dispatcher/FrameCompleteHandler.java
- cuebot/src/test/java/com/imageworks/spcue/test/dispatcher/FrameCompleteHandlerTests.java
- Add new task_lock for LOCK_ORPHANED_FRAME_CHECK. - Make pending_completition loop async/timeout to prevent locking the monitor thread
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.java (1)
371-427: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftBound every RQD request to the remaining sweep budget.
deleteOrphanedFrames()uses per-frame and pass deadlines, butrqdClient.killFrame(...)andrqdClient.isFrameRunning(...)always use the defaultgrpc.rqd_task_deadlineof 10s. A frame can therefore be stuck in a long sync RQD request after the per-frame deadline, and remaining frames can block on best-effort kills after the pass deadline. EnforcerqdTaskDeadlinewith no more than the remainingdeadlineMsfor both calls, and use a non-blocking dispatch path when the pass deadline is already reached.🤖 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/MaintenanceManagerSupport.java` around lines 371 - 427, Update killFrameBestEffort and killAndConfirmDead to bound both rqdClient.killFrame and rqdClient.isFrameRunning calls by rqdTaskDeadline and the remaining deadlineMs, using the smaller duration. When the pass or per-frame deadline has already expired, dispatch the best-effort kill through the non-blocking path instead of issuing a blocking RQD request, while preserving confirmation behavior and result handling.
🤖 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/dao/postgres/FrameDaoJdbc.java`:
- Around line 447-448: Update the query built in FrameDaoJdbc to add
deterministic ordering before LIMIT: sort by the oldest frame.ts_updated first,
then use frame.pk_frame as the stable tie-breaker, while preserving the existing
filtering and limit behavior.
In
`@cuebot/src/main/resources/conf/ddl/postgres/migrations/V46__Add_orphaned_frame_check_task_lock.sql`:
- Around line 5-6: The orphan-frame task-lock timeout is specified in
milliseconds instead of seconds. Update the INSERT for LOCK_ORPHANED_FRAME_CHECK
in
cuebot/src/main/resources/conf/ddl/postgres/migrations/V46__Add_orphaned_frame_check_task_lock.sql:5-6
and the matching seed-data row in
cuebot/src/main/resources/conf/ddl/postgres/seed_data.sql:90 to use the intended
seconds value, such as 30, while keeping both rows consistent.
---
Outside diff comments:
In
`@cuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.java`:
- Around line 371-427: Update killFrameBestEffort and killAndConfirmDead to
bound both rqdClient.killFrame and rqdClient.isFrameRunning calls by
rqdTaskDeadline and the remaining deadlineMs, using the smaller duration. When
the pass or per-frame deadline has already expired, dispatch the best-effort
kill through the non-blocking path instead of issuing a blocking RQD request,
while preserving confirmation behavior and result handling.
🪄 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: f243808d-3160-4723-9b45-9e57990cdc2e
📒 Files selected for processing (11)
cuebot/src/main/java/com/imageworks/spcue/MaintenanceTask.javacuebot/src/main/java/com/imageworks/spcue/dao/FrameDao.javacuebot/src/main/java/com/imageworks/spcue/dao/postgres/FrameDaoJdbc.javacuebot/src/main/java/com/imageworks/spcue/service/MaintenanceManagerSupport.javacuebot/src/main/resources/conf/ddl/postgres/migrations/V46__Add_orphaned_frame_check_task_lock.sqlcuebot/src/main/resources/conf/ddl/postgres/seed_data.sqlcuebot/src/main/resources/conf/spring/applicationContext-service.xmlcuebot/src/main/resources/opencue.propertiescuebot/src/test/java/com/imageworks/spcue/test/dao/postgres/FrameDaoTests.javacuebot/src/test/java/com/imageworks/spcue/test/service/MaintenanceManagerOrphanFrameTests.javarust/crates/rqd/src/system/machine.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- cuebot/src/test/java/com/imageworks/spcue/test/dao/postgres/FrameDaoTests.java
- rust/crates/rqd/src/system/machine.rs
Resolves a conflict in rqd's machine.rs: master (AcademySoftwareFoundation#2501) added the frame run_time to the FrameCompleteReport, while this branch moved that report out of send_frame_complete_reports and into the retrying deliver_pending_completions loop. Kept this branch's pending-completion structure and ported master's run_time computation into it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
b768f51
into
AcademySoftwareFoundation:master
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
RQD silently drops FrameCompleteReports
In rust/crates/rqd/src/system/machine.rs:
(line 405-420): the retain() closure returns false for FrameState::Finished, removing it, then
calls handle_finished_frames.
Err, it just error!(...) and moves on. No re-queue, no retry on the next monitor cycle, no
persistence. The frame is already gone from the cache. The completion is lost forever.
status and retries only on transport errors or HTTP 500/502/503. But a gRPC application error —
which is exactly what Cuebot returns when handleFrameCompleteReport throws
RqdRetryReportException — arrives as HTTP 200 with a grpc-status trailer. The policy sees HTTP
200 → Outcome::Return → not retried; send_frame_complete_report then surfaces it as Err
(report_client.rs:226-231) → dropped.
With this a completed frame under a failing Rqd (caused by memory pressure or cpu eviction for
example) could lead to a completed frame not having its state synchronized to Cuebot.
An orphaned frame (RUNNING with no proc, because its proc was removed
while RQD was still rendering) was reset straight to WAITING by the
maintenance reaper. The dispatcher could then rebook it onto a second
host while the original RQD process was still alive, a double booking.
Redesign the reaper to kill-then-confirm-dead before clearing:
Add RqdClient.isFrameRunning(host, frameId), backed by RQD's
GetRunningFrameStatus. A NOT_FOUND response means the render has been
reaped (confirmed dead); any other failure is raised as an
RqdClientException so callers can tell "confirmed gone" from
"could not reach".
clearOrphanedFrames() now kills each orphan on its last-known host and
polls until death is confirmed. Only then is the frame reset to
WAITING (safe auto-retry). If death cannot be confirmed, the per-pass
kill budget is exhausted or the host is unreachable, the frame is
failed (DEAD) so it needs a manual retry instead of risking a rebook.
Fail closed: a stuck frame is acceptable, a silent double booking is
not.
The whole pass is bounded by maintenance.orphaned_frame_kill_budget_ms
(default 30s) so a batch of unreachable hosts cannot stall maintenance.
getOrphanedFrames() now returns List (mapped via the
existing FRAME_DETAIL_MAPPER) so the reaper reads the last host from
lastResource directly, dropping a second per-frame DB lookup.
Also document why FrameCompleteHandler's orphan-finalize path
intentionally skips the layer min-memory bump (it is proc-specific and
there is no proc on that path).
Tests: add MaintenanceManagerOrphanFrameTests (DB-free) covering the
confirmed-dead -> WAITING, host-unreachable -> DEAD, budget-exhausted ->
DEAD, and never-ran -> WAITING branches; extend
FrameDaoTests.testgetOrphanedFrames to assert lastResource is populated.
LLM Usage disclosure
Claude Opus was used to suggest the fix for this issue
Summary by CodeRabbit
Bug Fixes
Configuration