Stopping without dropping work.
Deploys are the most common cause of stalled jobs. Closing a worker properly lets in-flight jobs finish and be acknowledged before the process exits.
Control and shut down
Section titled “Control and shut down”worker.run(); // Start processing (if created with autorun: false)worker.pause(); // Stop pulling new jobsworker.resume(); // Resume
await worker.close(); // Wait for active jobs, then stopawait worker.close(true); // Force close immediatelyworker.run(); // Start processing (if created with autorun: false)worker.pause(); // Stop pulling new jobsworker.resume(); // Resume
await worker.close(); // Stop pulling, flush batched ACKs, drain in-flight jobsawait worker.close(true); // Force: skip the in-flight drainworker.run() # Blocking loop (or autorun starts it in the background)worker.pause() # Stop pulling new jobsworker.resume() # Resume
worker.close() # Stop pulling, wait for in-flight jobs to drainworker.close(timeout=5) # Bound the wait; the drain continues in the background$worker->run(); // Blocking loop: pull, process, repeat$worker->runOnce(); // Pull and process one batch (cron-friendly)
$worker->stop(); // Finish the in-flight job, then return from run()$worker->close(); // Unregister and close the connectionworker.Run() // Blocking pull loopworker.Stop() // Stop pulling; in-flight jobs finishworker.Close() // Unregister and close the connectionworker.run()?; // Blocking pull loop; returns after stop()worker.run_once()?; // Pull and process one batchworker.stop(); // Ask the loop to exitworker.close(); // Stop, unregister and close the connectionBunqueue.Worker.run(worker) # Blocking pull loop; returns after stopBunqueue.Worker.run_once(worker) # Pull and process one batchBunqueue.Worker.stop(worker) # Drain, unregister and closePausing stops new pulls; it does not interrupt processors already running or
release jobs already buffered by the Worker. Lease-renewal and worker-registration
heartbeats therefore continue while paused. resume() reuses those heartbeat
loops, and repeated pause/resume cycles do not create additional timers.
close(true) stops owning the active delivery immediately; it does not forcibly
cancel JavaScript already running inside the processor. If that processor later
returns or throws, the Worker discards the late outcome without sending ACK or
FAIL. The broker can therefore recover the unfinished job through disconnect,
lock-expiry, or stall handling instead of accepting a result from a closed
worker. Use plain close() when the current result must be committed before
shutdown.
Processing timeouts follow the same generation rule during normal operation.
The broker owns the deadline and claims the active generation atomically. If a
processor returns or throws after that claim, the Worker receives an explicit
ignored outcome and suppresses its local completed, failed, and ACK/FAIL
error events. A retry’s newer lease is independent and can complete normally.
For a clean process exit, hook your runtime’s shutdown signal; in the Bun client, also shut down the shared machinery:
import { shutdownManager, closeSharedTcpClient } from 'bunqueue/client';
process.on('SIGINT', async () => { await worker.close(); shutdownManager(); // Embedded mode: flush writes, close SQLite closeSharedTcpClient(); // TCP mode: close the shared connection pool process.exit(0);});process.on('SIGINT', async () => { await worker.close(); // drains in-flight jobs and flushes batched ACKs process.exit(0);});try: worker.run()except KeyboardInterrupt: worker.close() # drains in-flight jobs$worker->installSignalHandlers(); // SIGTERM / SIGINT -> graceful stop$worker->run();go func() { sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig worker.Stop() // Run() drains in-flight jobs, then closes}()worker.Run()// `ctrlc` is the signal-hook crate used by this application.let shutdown_worker = worker.clone();ctrlc::set_handler(move || shutdown_worker.stop())?;
worker.run()?; // returns after the handler calls stop()worker.close();task = Task.async(fn -> Bunqueue.Worker.run(worker) end)
receive do :shutdown -> :ok = Bunqueue.Worker.stop(worker) # waits for the active run_once batch Task.await(task, :infinity)endIn Rust and Elixir, calling worker.stop() / Bunqueue.Worker.stop(worker)
from another thread or process makes the blocking run loop drain and return;
there is no shared client machinery to tear down.
Where to go next
Section titled “Where to go next”| Worker | Create a worker and process your first job |
| Worker Concurrency and Batch Pulling | Run jobs in parallel and pull them in batches |
| The Job Object Inside a Worker Processor | Everything the processor receives and can do |
| Worker Events | completed, failed, stalled and the rest |
| Worker Error Handling, Retries and Backoff | Retries, backoff, timeouts and giving up |
| Heartbeats, Stall Detection and Lock Ownership | Heartbeats, stall recovery and lock ownership |
| SandboxedWorker | Experimental isolation for CPU-heavy handlers |
| WorkerOptions Reference | Every WorkerOptions field, with defaults |