feat(java): operator admin surface - #558
Conversation
requeueJob, predicateStats, analyzeArguments, reloadResources, and shutdown — the recovery and introspection APIs the other SDKs already expose.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe Java SDK adds administrative APIs for stuck-job recovery, predicate inspection, interceptor analysis, resource hot reload, and client-wide worker shutdown. JNI, resource runtime, lifecycle wiring, tests, and Java documentation are updated. ChangesJava operator administration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Taskito
participant JniQueueBackend
participant NativeQueue
participant QueueStorage
Taskito->>JniQueueBackend: requeueJob(jobId)
JniQueueBackend->>NativeQueue: requeueJob(handle, jobId)
NativeQueue->>QueueStorage: requeue_stuck(id, now)
QueueStorage-->>Taskito: return success or false
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
sdks/java/src/main/java/org/byteveda/taskito/resources/ResourceRuntime.java (1)
369-390: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winReload's dependency ordering doesn't cover
POOLEDresources that depend on aWORKERresource.
workerContext(Lines 375-390) records a dependency inworkerDepsonly when aWORKER-scoped factory resolves another resource. The pre-existingpooledContext.use()(unchanged, Lines 92-102) callsresolvePooledDependencywithout recording anything intoworkerDeps. So reloading aWORKERresource that aPOOLEDresource depends on won't rebuild/refresh already-checked-out or idle pooled instances — they keep the stale dependency until the pool itself is reloaded or a pooled instance'smaxLifetimeexpires. This is a real gap in the "dependency-first" guarantee for the pooled+worker combination; worth a short caveat in the reload Javadoc/docs, or extendingworkerDepstracking to pooled dependencies if that combination is expected to be common.🤖 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 `@sdks/java/src/main/java/org/byteveda/taskito/resources/ResourceRuntime.java` around lines 369 - 390, Extend pooled dependency tracking so pooled resources that resolve WORKER resources participate in reload ordering. Update pooledContext.use() and its resolvePooledDependency flow to record WORKER dependencies in the same tracking structure used by workerContext, while preserving existing handling for other scopes.
🤖 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 `@sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java`:
- Around line 1453-1474: Make worker lifecycle coordination in DefaultTaskito
atomic by adding a shutdown state guarded by the same synchronization mechanism
used when registering workers. Update WorkerLifecycle.started() to reject and
close workers once shutdown begins, while adding accepted workers to liveWorkers
atomically; update shutdown() to mark shutdown before draining the set and
continue closing workers until no accepted workers remain. Preserve normal
registration before shutdown and ensure workers starting concurrently cannot
remain running after shutdown completes.
In `@sdks/java/src/main/java/org/byteveda/taskito/resources/ResourceRuntime.java`:
- Around line 253-326: Coordinate worker reloads with terminal teardown in
recreateWorker, resolveWorker, and disposeWorker so cache and teardown mutations
cannot race. Add an atomically coordinated disposed state or shared monitor, and
re-check disposal while holding each worker’s lock before building or caching an
instance. Ensure disposeWorker marks the runtime disposed while clearing caches
and draining teardown entries, and make reload against a disposed runtime return
false without creating new resources.
In
`@sdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.java`:
- Around line 152-164: Update InMemoryQueueBackend and InMemoryWorker to
associate each worker token with a claim/attempt generation rather than only
jobId. Increment or invalidate the generation when requeueJob makes a running
job pending and on later claims, then ignore complete, fail, or cancel outcomes
whose token generation no longer matches the job’s current generation.
---
Nitpick comments:
In `@sdks/java/src/main/java/org/byteveda/taskito/resources/ResourceRuntime.java`:
- Around line 369-390: Extend pooled dependency tracking so pooled resources
that resolve WORKER resources participate in reload ordering. Update
pooledContext.use() and its resolvePooledDependency flow to record WORKER
dependencies in the same tracking structure used by workerContext, while
preserving existing handling for other scopes.
🪄 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: 05ed9743-91f7-4c69-a6ce-b7789bbfecbb
📒 Files selected for processing (26)
CHANGELOG.mdcrates/taskito-java/src/queue/admin.rsdocs/content/docs/java/api-reference/queue/queues.mdxdocs/content/docs/java/api-reference/queue/resources.mdxdocs/content/docs/java/api-reference/queue/workers.mdxdocs/content/docs/java/api-reference/resources.mdxdocs/content/docs/resources/changelog.mdxdocs/content/docs/shared/guides/operations/troubleshooting.mdxsdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.javasdks/java/src/main/java/org/byteveda/taskito/Taskito.javasdks/java/src/main/java/org/byteveda/taskito/interception/InterceptionAnalysis.javasdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.javasdks/java/src/main/java/org/byteveda/taskito/internal/NativeQueue.javasdks/java/src/main/java/org/byteveda/taskito/predicates/PredicateMetrics.javasdks/java/src/main/java/org/byteveda/taskito/predicates/PredicateStats.javasdks/java/src/main/java/org/byteveda/taskito/resources/ResourceDefinition.javasdks/java/src/main/java/org/byteveda/taskito/resources/ResourceRuntime.javasdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.javasdks/java/src/main/java/org/byteveda/taskito/worker/Worker.javasdks/java/src/main/java/org/byteveda/taskito/worker/WorkerLifecycle.javasdks/java/src/test/java/org/byteveda/taskito/core/InterceptionTest.javasdks/java/src/test/java/org/byteveda/taskito/core/PredicateTest.javasdks/java/src/test/java/org/byteveda/taskito/core/RequeueTest.javasdks/java/src/test/java/org/byteveda/taskito/resources/ResourceReloadTest.javasdks/java/src/test/java/org/byteveda/taskito/worker/ShutdownTest.javasdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.java
Addresses review: reload now runs under the runtime monitor teardown takes, shutdown drains rather than sweeping once, and the in-memory backend fences outcomes on status like the core does.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
sdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.java (1)
162-163: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winSerialize cancellation with requeue.
requestCancelcan observerunning, thenrequeueJobclearscancelRequested, after whichrequestCancelwrites it back totrue. The fresh attempt is then cancelled by a request intended for the stale one. SynchronizerequestCancelon this backend monitor as well.🤖 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 `@sdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.java` around lines 162 - 163, Synchronize requestCancel on the InMemoryQueueBackend monitor so checking running and updating cancelRequested is atomic with requeueJob. Preserve requeueJob’s clearing of cancelRequested, ensuring a stale cancellation cannot mark the fresh attempt for cancellation.
🤖 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
`@sdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.java`:
- Around line 1244-1286: Update InMemoryQueueBackend’s settleable, token
bookkeeping, and completeJob/failJob/cancelJob flow to fence outcomes by
execution attempt: store a unique claim generation with each token, then under
the backend monitor validate both the job’s running status and matching
generation while applying the outcome. Ensure requeue or reclaim invalidates the
original token so stale handlers cannot settle a pending or newer attempt, and
add coverage that reclaims a job before the original handler is released.
---
Outside diff comments:
In
`@sdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.java`:
- Around line 162-163: Synchronize requestCancel on the InMemoryQueueBackend
monitor so checking running and updating cancelRequested is atomic with
requeueJob. Preserve requeueJob’s clearing of cancelRequested, ensuring a stale
cancellation cannot mark the fresh attempt for cancellation.
🪄 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: d7dc12a5-67e6-476e-9eb8-54b338e338ef
📒 Files selected for processing (4)
sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.javasdks/java/src/main/java/org/byteveda/taskito/resources/ResourceRuntime.javasdks/java/test-support/src/main/java/org/byteveda/taskito/test/InMemoryQueueBackend.javasdks/java/test-support/src/test/java/org/byteveda/taskito/test/InMemoryQueueBackendTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
- sdks/java/src/main/java/org/byteveda/taskito/resources/ResourceRuntime.java
- sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java
The status fence was read outside the monitor requeueJob and claimNext hold, so a concurrent requeue could slip between the check and the transition.
Closes #524.
Ports the operator admin surface to the Java SDK. Java had none of the five APIs the other SDKs expose.
What's added
requeueJob(jobId)→booleanrunningjob back topending, releasing its execution claim. The only new native surface.predicateStats()→PredicateStatsallowed/skipped/deferred/rejected/errors. One count per gated enqueue — the decision that won, not per gate evaluated.analyzeArguments(taskName, payload)→InterceptionAnalysisrejectedinstead of throwing.reloadResources()/reloadResources(names)→Map<String, Boolean>shutdown()Implementation notes
requeueJobgoes over the existingStorage::requeue_stuck: a newJava_..._NativeQueue_requeueJobincrates/taskito-java/src/queue/admin.rs, thenNativeQueue→QueueBackend(defaults toUnsupportedOperationException) →JniQueueBackend→InMemoryQueueBackend. The double-execution caveat is surfaced in the API reference and the troubleshooting guide.reloadResourcesneeded real runtime work. Java'sResourceRuntimeis two-level: the client runtime holds definitions and counters, andforWorker()forks a child that holds the live caches. A client-levelreload()therefore fans out to the live children and ANDs their results — reloading on the client runtime itself would build orphan instances in a cache nothing tears down. Children publish themselves onacquireWorker()'s first lease and unpublish indisposeWorker(), so a builder that is never started leaves no reload target behind.Selective disposal required keying the shared LIFO teardown stack (
Deque<Runnable>→Deque<Teardown>), so one resource's entries can be pulled out without disturbing the rest. Dependency ordering comes from aworkerDepsedge map recorded by a per-buildworkerContext(name).Per scope:
WORKERdisposes and rebuilds eagerly under the resource's build lock (so a factory that now fails reportsfalserather than surfacing on some later task);THREADdrops every per-thread instance so each thread rebuilds on next use;POOLEDdrops the pool;TASK/REQUESTare a successful no-op since nothing is cached.Java has no resource options bag, so
reloadableis a fifthResourceDefinitionrecord component — the existing 4- and 3-arg constructors are kept, so this stays source- and binary-compatible — pluswithReloadable(boolean), reached through a newTaskito.resource(name, ResourceDefinition)escape hatch instead of anotherresource(...)overload.shutdownadds aWorkerLifecycleinterface (started/closed) wired byTaskito.worker();DefaultTaskitokeeps aSet<Worker>. It closes (drains) each worker rather than only halting dispatch, becauseWorker.close()is Java's full, already-idempotent teardown.analyzeArgumentstakes a payload rather than an args array, since Java interception is payload-level. The interceptor loop was extracted out ofdispatchEnqueueintoapplyInterceptors(taskName, payload, outcomes), so the dry run is literally the enqueue path; a rejection reports the original input plus the partial outcome list.Tests
12 new:
core/RequeueTest(3),resources/ResourceReloadTest(6 — rebuild, reloadable sweep, unknown name, no running worker, dependency ordering, failing factory),worker/ShutdownTest(3), plus new cases incore/PredicateTestandcore/InterceptionTest.Full Gradle suite green.
cargo check --workspace,cargo clippy -p taskito-java,cargo fmt --check, spotless, checkstyle, and javadoc all clean;pnpm --dir docs typecheckandlintclean.Summary by CodeRabbit