Skip to content

feat(node): operator admin surface - #557

Merged
kartikeya-27 merged 10 commits into
masterfrom
feat/node-admin-surface
Jul 26, 2026
Merged

feat(node): operator admin surface#557
kartikeya-27 merged 10 commits into
masterfrom
feat/node-admin-surface

Conversation

@pratyush618

@pratyush618 pratyush618 commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator

Closes #523.

The core already had Storage::requeue_stuck, but no Node binding reached it — so a Node
operator had no recovery path for a job stuck Running (hung-but-heartbeating worker, huge
timeout, or a claim-less Running row). That is the functional gap here; the rest is wrapper
parity with the cross-SDK admin contract. predicateStats() was already shipped in #543, so it
needed no work.

What's new

API Behavior
queue.requeueJob(id) Forces a stuck running job back to pending, releasing its execution claim atomically and preserving the retry budget. false when the job is missing or isn't running.
queue.reloadResources(names?) Hot-reloads resources: dispose what is cached, rebuild on next use. No argument sweeps every reloadable: true resource; an unknown name reports false rather than throwing.
queue.analyzeArguments(taskName, args) Dry-runs the interceptor chain — a chain that would reject comes back as rejected instead of throwing, and the run leaves interceptionStats() untouched.
queue.shutdown() Stops every worker started from the queue — the programmatic SIGINT/SIGTERM, so a signal handler no longer has to hold each Worker.

requeueJob carries the double-execution caveat in its docstring, the API reference, and the
troubleshooting guide: only use it once the owning worker is confirmed dead or hung.

Notes for review

  • Worker.stop() was not idempotent. A second call released a second resource lease and could
    tear down a sibling worker's resources. It is now memoized. This is a prerequisite for a
    queue-level shutdown that may race a caller's own worker.stop(), so it rides in that commit;
    the added regression test fails without the fix.
  • Reload ordering. ResourceRuntime.workerCache insertion order is
    dependent-before-dependency — resolveWorker caches the pending promise before the factory
    body runs — so reversing it is only correct for chains, not multi-root graphs. reload() instead
    records dependency edges in the ctx.use closure and walks them post-order, rebuilding a
    dependency before the resources that resolved it.
  • Reload by scope. Worker scope recreates; pooled scope drops the pool so the next checkout
    builds fresh (idle instances disposed now, checked-out ones on release); task/request scopes are
    a successful no-op, since nothing is cached to replace.
  • Docs. The shared troubleshooting stuck-job block is two parallel <SdkOnly> blocks rather
    than a <CodeTabs>: SdkOnly matches exactly one SDK id, and every existing CodeTabs carries
    all three, so a two-tab one would render an empty panel for Java. It collapses into a CodeTabs
    once Java lands the same API.

Verification

  • pnpm test — 630 tests across 89 files green (16 new).
  • cargo test --workspace, cargo clippy --workspace, cargo fmt --check green.
  • pnpm run typecheck + pnpm run lint (biome) green; docs lint, typecheck, and full build green.

Summary by CodeRabbit

  • New Features
    • Added queue recovery to requeue stuck running jobs back to pending.
    • Added resource hot-reloading via reloadable resources with dependency-aware rebuild order.
    • Added analyzeArguments to dry-run enqueue interceptors/guards, including redirects and structured rejection results.
    • Added queue-level shutdown() to stop all queue workers together.
  • Documentation
    • Updated Node API references, changelog, and troubleshooting guide with the new recovery, reload, and analysis capabilities and usage notes/warnings.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The Node SDK adds APIs for requeuing stuck jobs, analyzing interceptor outcomes, reloading resources, and shutting down queue-owned workers. Native bindings, resource dependency tracking, worker lifecycle handling, exports, documentation, changelogs, and tests are updated.

Changes

Node queue administration

Layer / File(s) Summary
Stuck job recovery
crates/taskito-node/src/queue/admin.rs, sdks/node/src/queue.ts, sdks/node/test/core/requeue.test.ts, docs/content/docs/node/api-reference/queue/queues.mdx, docs/content/docs/shared/guides/operations/troubleshooting.mdx
Adds requeueJob(jobId) to move applicable running jobs back to pending while preserving retry budget.
Interceptor dry-run analysis
sdks/node/src/interception.ts, sdks/node/src/index.ts, sdks/node/src/queue.ts, sdks/node/test/core/interceptors.test.ts, docs/content/docs/node/api-reference/queue/resources.mdx
Adds InterceptionAnalysis and analyzeArguments() for reporting hypothetical interceptor results without enqueueing or changing stats.
Dependency-aware resource reload
sdks/node/src/resources/types.ts, sdks/node/src/resources/runtime.ts, sdks/node/src/queue.ts, sdks/node/test/resources/reload.test.ts, docs/content/docs/node/api-reference/queue/resources.mdx
Adds reloadable resource registration and dependency-ordered reloadResources() behavior across resource scopes.
Idempotent worker stop lifecycle
sdks/node/src/worker.ts
Adds stop callbacks and memoized teardown so repeated worker stops execute cleanup once.
Queue-level worker shutdown
sdks/node/src/queue.ts, sdks/node/test/worker/shutdown.test.ts, docs/content/docs/node/api-reference/queue/workers.mdx, CHANGELOG.md, docs/content/docs/resources/changelog.mdx
Tracks queue-started workers and makes shutdown() stop all active workers and await completion.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

Resource reload

sequenceDiagram
  participant Queue
  participant ResourceRuntime
  participant WorkerResource
  Queue->>ResourceRuntime: reloadResources(names?)
  ResourceRuntime->>ResourceRuntime: determine dependency order
  ResourceRuntime->>WorkerResource: recreate or evict cached resources
  WorkerResource-->>ResourceRuntime: reload result
  ResourceRuntime-->>Queue: return result map
Loading

Queue shutdown

sequenceDiagram
  participant Queue
  participant Worker
  participant ResourceRuntime
  Queue->>Worker: stop all tracked workers
  Worker->>ResourceRuntime: release worker leases
  ResourceRuntime-->>Worker: dispose scoped resources
  Worker-->>Queue: resolve stop promise
  Queue-->>Queue: resolve shutdown()
Loading

Possibly related PRs

Suggested labels: storage

Suggested reviewers: kartikeya-27

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements requeueJob, shutdown, reloadResources, and analyzeArguments for Node as requested, and predicateStats needed no code changes.
Out of Scope Changes check ✅ Passed All changes support the Node admin surface and related API docs/tests; no unrelated scope is evident.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a Node operator admin surface.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/node-admin-surface

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
sdks/node/src/worker.ts (1)

399-417: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Install the stop promise before invoking teardown callbacks.

On Line 400, stopped is assigned only after runStop() returns. runStop() synchronously invokes onStopped and emits worker.stopped; either can reenter stop() while stopped is still unset. That starts a second teardown (and can recursively re-emit worker.stopped), defeating idempotency and potentially releasing another worker’s resource lease.

Proposed fix
 stop(): Promise<void> {
-  this.stopped ??= this.runStop();
-  return this.stopped;
+  if (this.stopped) {
+    return this.stopped;
+  }
+
+  let resolve!: () => void;
+  const stopped = new Promise<void>((done) => {
+    resolve = done;
+  });
+  this.stopped = stopped;
+
+  try {
+    void this.runStop().then(resolve, (error) => {
+      log.debug(() => "worker stop failed", error);
+      resolve();
+    });
+  } catch (error) {
+    log.debug(() => "worker stop failed", error);
+    resolve();
+  }
+  return stopped;
 }
🤖 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/node/src/worker.ts` around lines 399 - 417, Update Worker.stop() and
runStop() so the shared stopped promise is created and assigned before any
teardown callbacks or events can reenter stop(). Preserve idempotent behavior by
having stop() return the already-installed promise, while moving synchronous
teardown execution behind that promise’s initialization.
🧹 Nitpick comments (1)
sdks/node/src/interception.ts (1)

49-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep InterceptionAnalysis as a single canonical type.

sdks/node/src/queue.ts:49-64 declares the same interface, while this file adds the exported copy. Queue.analyzeArguments() is therefore typed against a different declaration than the root SDK export, allowing the two shapes to diverge. Remove the duplicate from queue.ts and import this type there.

🤖 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/node/src/interception.ts` around lines 49 - 64, Remove the duplicate
InterceptionAnalysis interface declaration from Queue and update
Queue.analyzeArguments() to import and use the canonical exported
InterceptionAnalysis type from interception.ts, keeping the existing analysis
shape and behavior unchanged.
🤖 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/node/src/queue.ts`:
- Around line 1643-1652: The Queue.shutdown method currently resolves before
worker handler invocations finish, despite documenting that tasks drain. Update
the shutdown lifecycle around Queue.shutdown and Worker.stop to track active
handler invocations and await their completion before resolving, while
preserving the existing no-op and repeated-stop behavior.
- Around line 1597-1601: Update analyzeArguments to pass a shallow copy of the
caller-provided args array into applyInterceptors, matching the copying behavior
used by real enqueue. Preserve the existing analysis result and outcomes
handling while ensuring mutating interceptors cannot alter the original args.

In `@sdks/node/src/resources/runtime.ts`:
- Around line 415-484: Update reload to await any already-in-flight worker
builds for the requested targets before calling dependencyOrder. Ensure
resolveWorker’s dependency tracking has completed before ordering, so
dependencies are visited before dependents during the same reload sweep.
Preserve existing reloadOne behavior and result handling.

In `@sdks/node/test/core/requeue.test.ts`:
- Around line 9-12: Update the afterEach cleanup hook to be asynchronous and
await worker.stop() before clearing the worker reference, ensuring teardown
completes before the hook finishes.

---

Outside diff comments:
In `@sdks/node/src/worker.ts`:
- Around line 399-417: Update Worker.stop() and runStop() so the shared stopped
promise is created and assigned before any teardown callbacks or events can
reenter stop(). Preserve idempotent behavior by having stop() return the
already-installed promise, while moving synchronous teardown execution behind
that promise’s initialization.

---

Nitpick comments:
In `@sdks/node/src/interception.ts`:
- Around line 49-64: Remove the duplicate InterceptionAnalysis interface
declaration from Queue and update Queue.analyzeArguments() to import and use the
canonical exported InterceptionAnalysis type from interception.ts, keeping the
existing analysis shape and behavior unchanged.
🪄 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: 67bc4d49-f81d-4e87-8988-5642674bb96d

📥 Commits

Reviewing files that changed from the base of the PR and between 46acc59 and e8b686d.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • crates/taskito-node/src/queue/admin.rs
  • docs/content/docs/node/api-reference/queue/queues.mdx
  • docs/content/docs/node/api-reference/queue/resources.mdx
  • docs/content/docs/node/api-reference/queue/workers.mdx
  • docs/content/docs/resources/changelog.mdx
  • docs/content/docs/shared/guides/operations/troubleshooting.mdx
  • sdks/node/src/index.ts
  • sdks/node/src/interception.ts
  • sdks/node/src/queue.ts
  • sdks/node/src/resources/runtime.ts
  • sdks/node/src/resources/types.ts
  • sdks/node/src/worker.ts
  • sdks/node/test/core/interceptors.test.ts
  • sdks/node/test/core/requeue.test.ts
  • sdks/node/test/resources/reload.test.ts
  • sdks/node/test/worker/shutdown.test.ts

Comment thread sdks/node/src/queue.ts
Comment thread sdks/node/src/queue.ts
Comment thread sdks/node/src/resources/runtime.ts
Comment thread sdks/node/test/core/requeue.test.ts Outdated
onStopped and the worker.stopped listeners fire synchronously inside runStop, so a reentrant stop() saw no memoized promise and started a second teardown.
It stops dispatch and disposes worker resources; handlers already mid-flight are not awaited.
@pratyush618

Copy link
Copy Markdown
Collaborator Author

Addressed the review — 5 commits pushed (fd299ab).

Outside-diff, sdks/node/src/worker.ts:399-417 — reentrant stop(): valid, fixed in e6f681c. this.stopped ??= this.runStop() assigned only after runStop() returned, and runStop() fires onStopped plus the worker.stopped listeners synchronously — so a listener calling stop() started a second teardown and released a second resource lease. The shared promise is now installed before teardown runs, with a try/catch so a synchronous throw still settles it rather than leaving it pending. Added a regression test (a stop reentered from a worker.stopped listener tears down once) that fails against the old code.

Nitpick, duplicate InterceptionAnalysis: skipped — false positive. queue.ts imports the type (queue.ts:29) and never declares it; sdks/node/src/interception.ts is the single canonical declaration and the one re-exported from the package root.

The other four are answered on their threads: args copy (3b0ba6d), reload ordering race (5604b5c), shutdown drain wording (8075d3c), test teardown await (fd299ab).

631 tests / 89 files green, biome + tsc clean.

@kartikeya-27 kartikeya-27 changed the title feat(node): operator admin surface — requeueJob, reloadResources, analyzeArguments, shutdown feat(node): operator admin surface Jul 26, 2026
@kartikeya-27
kartikeya-27 merged commit 8f8a34f into master Jul 26, 2026
57 of 60 checks passed
@kartikeya-27
kartikeya-27 deleted the feat/node-admin-surface branch July 26, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Node: requeueJob and the admin surface

2 participants