Skip to content

Worker execution

Use laneops/worker for large pipelines that should run off the main thread. Node uses a fixed worker_threads pool with native/Wasm selection; browsers use module Workers with Wasm. All eight numeric pipeline dtypes are supported.

Submit a pipeline

ts
import { createVectorExecutor } from 'laneops/worker'

const executor = await createVectorExecutor({ concurrency: 2, maxQueue: 8 })
try {
  const controller = new AbortController()
  const input = new Float32Array([1, 2, 3])
  const task = executor.submit(
    { dtype: 'f32', nodes: [{ op: 'mul', operand: 2 }] },
    input,
    [],
    { signal: controller.signal },
  )
  input.fill(100) // submit already copied the original values
  console.log(await task.result) // Float32Array [2, 4, 6]
  // task.cancel() or controller.abort() cancels unfinished work.
} finally {
  await executor.dispose()
}

submit(plan, input, operands?, { signal }?) accepts a definition or pipeline.plan. It validates and copies input views synchronously, then returns { id, state, result, cancel }. Inputs are never detached by the executor. Results are independent arrays transferred back from the worker. There is no async out, arena-handle input or public transfer mode. Snapshot cost still scales with input bytes; small tasks may be faster with synchronous operations.

Cancellation and errors

Tasks transition from queued to running, then completed, cancelled, failed or disposed. Invalid inputs, pre-aborted signals, exhausted budgets, full queues and unavailable executors throw during submission. Once accepted, failures reject task.result; always observe that Promise. Running cancellation rejects immediately and terminates the worker. Its byte reservation remains until termination finishes, which may wait for a synchronous native kernel to return. Workers are replaced automatically; failed replacement initialization rejects remaining tasks and fails the executor. dispose() cancels unfinished work, waits for termination and returns the same Promise on repeated calls.

Queue and memory limits

Defaults: concurrency: 1 (maximum 16), maxQueue: 32, maxTaskBytes: 64 MiB, maxBytes: 256 MiB, startupTimeoutMs: 10000, taskTimeoutMs: 30000. The per-task reservation is 2 × (inputCount + 1) × inputByteLength + 4096, covering snapshots/output and worst-case Wasm scratch payload. This bounds admitted task data, not process RSS, caller-owned outputs, GC or retained Wasm heap capacity. getStats() exposes queue/running/retiring counts and current/peak reservations. info reports the latest successful backend for each pool slot.

Browser and Vite setup

Vite consumers should configure both Worker and Wasm URLs:

ts
import { createVectorExecutor } from 'laneops/worker'
import workerUrl from 'laneops/worker/runtime?worker&url'
import scalarUrl from 'laneops/wasm/scalar?url'
import simdUrl from 'laneops/wasm/simd?url'

const executor = await createVectorExecutor({
  workerUrl: new URL(workerUrl, location.href),
  wasm: {
    scalar: new URL(scalarUrl, location.href),
    simd: new URL(simdUrl, location.href),
  },
})

The ?worker&url transform bundles the runtime's dependencies. Static ESM consumers can use laneops/worker/browser and the default adjacent assets. No shared memory or cross-origin isolation is required. Try pnpm example:vite with ?worker&simd=off or ?worker&simd=required. See the Worker contract for task states, error names and budget accounting, and performance guidance for deciding when to use an executor.

Released under the MIT License.