Background & Workers
Weld runs work off the request path with three primitives, plus the periodic every timer:
startup { }— a boot hook that runs once, before the server accepts traffic.worker [name] { }— a background thread that runs until it returns or the server shuts down. Paired with a topic, a worker is a job queue.spawn <call>— fire-and-forget a single upstream/database/gRPC call from a handler.
startup
Runs once at boot, synchronously, before the first request is served — seed data, warm a cache, log configuration. It runs single-threaded, so it needs no locking.
state cache { ready: bool = false }
startup {
cache.ready = true # no other thread is running yet
}worker — background loops and job queues
A worker body runs on its own thread at startup. On its own it can do periodic-free background work, but its real power is consuming a topic: handlers publish, the worker processes off the request path.
type Job = { id: int, url: string }
topic Jobs -> Job
worker {
for j in Jobs { # blocks for the next job; ends on shutdown
crawl(j.url) # do the slow work here, not in the handler
}
}
route POST "/crawl" (body j: Job) -> string {
Jobs.send(j) # returns instantly
respond "queued"
}A worker's topic loop is shutdown-aware (it ends when the server drains), reclaims each message's memory, and — because it runs alongside request handlers — locks shared stateper message, never across the whole loop. A single worker per topic processes each job once; running several workers on one topic would each see every job (topics are broadcast).
spawn — fire-and-forget a call
When you just want to fire one outbound call and not wait for it — a webhook, an email, an audit write — spawn runs it on a background thread and returns immediately:
route POST "/signup" (body u: User) -> string {
db.insertUser(u) # awaited: must succeed before we respond
spawn mailer.sendWelcome(u.email) # fire-and-forget: don't block the response on it
spawn analytics.track(u.id)
respond "created"
}spawn takes an upstream, database, or gRPC call. Its arguments are deep-copied before the thread starts (so they safely outlive the request), the call runs on a background thread, and its result is discarded. Failures are swallowed — if you need the result or retries, use a worker + topic instead.
Cancellation
startup completes before serving; worker loops end on shutdown (with the normal connection drain). spawn tasks are best-effort — in-flight ones may be cut off when the process exits. Don't spawn work that must not be lost; publish it to a topic a worker drains. Background blocks run on the threaded driver (not --green).