Handlers in their own thread.
CPU-bound work starves an event loop. SandboxedWorker moves the handler into a Bun Worker thread so the main thread can keep heartbeating, at the cost of using bunqueue’s experimental worker-pool implementation.
SandboxedWorker
Section titled “SandboxedWorker”SandboxedWorker runs a processor module in Bun Worker threads. The queue and
heartbeat loop stay in the parent thread. A per-job timeout terminates a stuck
thread, and a crashed thread is restarted only while autoRestart is enabled
and its restart budget remains.
This is execution separation, not a security sandbox. Threads share the same OS process and authority; do not use it to run untrusted code, and do not assume an out-of-memory failure is contained to one thread.
Availability
Section titled “Availability”import { SandboxedWorker } from 'bunqueue/client';
const worker = new SandboxedWorker('cpu-intensive', { processor: './processor.ts', // Path to processor file concurrency: 4, // 4 parallel worker threads timeout: 60000, // Per-job timeout (default: 30000, 0 = disabled) maxMemory: 256, // compatibility hint; <= 64 enables smol mode});
await worker.start();The network SDK does not export SandboxedWorker. Keep the standard Worker as
the queue owner and delegate CPU work to node:worker_threads, a Node child
process, or a Deno Web Worker:
const worker = new Worker('cpu-intensive', (job) => cpuPool.run(job.data), { concurrency: 4, heartbeatIntervalS: 10, lockTtlMs: 60_000,});The Python SDK does not export SandboxedWorker. Delegate the calculation to a
ProcessPoolExecutor; the normal Worker retains the lease and heartbeat loop:
pool = ProcessPoolExecutor(max_workers=4)worker = Worker("cpu-intensive", lambda job: pool.submit(run_cpu, job.data).result(), concurrency=4, heartbeat_interval_s=10.0, lock_ttl_ms=60_000)The PHP SDK does not export SandboxedWorker. Its Worker is sequential, so use
a supervised child process/service and renew the lease during long waits:
$worker = new Worker('cpu-intensive', function (Bunqueue\Job $job) { $job->extendLock(60000); return runInChildProcess($job->data());}, ['lockTtlMs' => 60000]);The Go SDK does not export SandboxedWorker. Processors already run in a
bounded goroutine pool and the heartbeat loop is separate:
worker := bunqueue.NewWorker("cpu-intensive", processor, bunqueue.WorkerOptions{ Concurrency: 4, LockTtlMs: 60_000, HeartbeatIntervalS: 10,})The Rust SDK does not export SandboxedWorker. Its standard Worker runs
processors on worker threads and heartbeats independently:
let worker = Worker::new("cpu-intensive", processor, WorkerOptions { concurrency: 4, lock_ttl_ms: 60_000, heartbeat_interval: Some(Duration::from_secs(10)), ..Default::default()});The Elixir SDK does not export SandboxedWorker. The standard Worker runs each
handler in a Task and heartbeats from another process; isolate blocking NIFs in
a dirty scheduler or external port:
worker = Bunqueue.Worker.new("cpu-intensive", handler, concurrency: 4, lock_ttl: 60_000, heartbeat_interval: 10_000)The remaining API on this page is Bun-only.
Bun processor module
Section titled “Bun processor module”Processor file (processor.ts):
export default async (job: { id: string; data: any; queue: string; attempts: number; parentId?: string; progress: (value: number) => void; log: (message: string) => void; fail: (error: string | Error) => void;}) => { job.progress(50); const result = await heavyComputation(job.data); job.progress(100); return result;};To connect to a remote server instead of running embedded, pass a connection
option (host, port, token); otherwise the shared embedded manager is used.
Bun lifecycle and local stats
Section titled “Bun lifecycle and local stats”await worker.start();worker.isRunning();const stats = worker.getStats(); // { total, busy, idle, recycled, restarts }await worker.stop(); // Graceful (waits for busy workers)await worker.stop(true); // ForcegetStats() reports pool bookkeeping in the current process. It is not a broker
metrics snapshot.
SandboxedWorker options
Section titled “SandboxedWorker options”| Option | Type | Default | Description |
|---|---|---|---|
processor | string | (required) | Path to processor file |
concurrency | number | 1 | Parallel worker threads |
maxMemory | number | 256 | Compatibility hint: values <= 64 enable Bun’s smol Worker mode. This implementation does not enforce an MB memory limit |
timeout | number | 30000 | Per-job timeout in ms (0 = disabled) |
autoRestart | boolean | true | Auto-restart crashed threads |
maxRestarts | number | 10 | Max restart attempts per thread |
pollInterval | number | 10 | Job poll interval in ms |
heartbeatInterval | number | 5000 (embedded) / 10000 (TCP) | Heartbeat for stall detection and lock renewal; non-positive disables it |
idleTimeout | number | 0 | Stop the pool after this many idle ms (0 = disabled) |
idleRecycleMs | number | 30000 | Recycle idle threads after this many ms (0 = disabled) |
autoStart | boolean | false | Restart the pool when new jobs arrive after an idle shutdown |
autoStartPollMs | number | 5000 | Poll interval while idle-stopped |
connection | ConnectionOptions | - | TCP connection (omit for embedded) |
SandboxedWorker emits eight local events: ready, active, completed,
failed, progress, log, error, and closed. It does not emit
stalled, drained, or cancelled. completed/failed describe the local
processor outcome; the implementation sends the broker ACK/FAIL asynchronously,
so observe queue state or QueueEvents when broker confirmation matters.
Worker vs SandboxedWorker
Section titled “Worker vs SandboxedWorker”| Worker | SandboxedWorker | |
|---|---|---|
| Production ready | ✅ Stable | ⚠️ Experimental bunqueue implementation |
| I/O-bound tasks (HTTP, DB, APIs) | ✅ Best choice | Overkill |
| CPU-intensive tasks | ⚠️ Blocks event loop | ✅ Runs in separate thread |
| Untrusted code | ❌ Not isolated | ❌ Thread separation is not a security boundary |
| Per-thread memory limit | ❌ | ❌ maxMemory does not enforce one |
| Events | 11 events | 8 events |
| Concurrency, retries, heartbeats | ✅ | ✅ Supported through a separate implementation |
Most workloads are I/O-bound (API calls, database queries, file operations); for
those, Worker is the right choice. For CPU-heavy work, see
CPU-Intensive Workers for the supported
offloading and lease-sizing patterns.
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 |
| Worker Lifecycle | Pause, resume and shut down without losing work |
| Heartbeats, Stall Detection and Lock Ownership | Heartbeats, stall recovery and lock ownership |
| WorkerOptions Reference | Every WorkerOptions field, with defaults |