5.4 KiB
Queues & Run Loops
Source:
src/content/docs/actors/queues.mdxCanonical URL: https://rivet.dev/docs/actors/queues Description: Use actor-local durable queues for serial run loops and request/response workflows.
What are queues?
- Realtime: messages are delivered to a live actor as soon as possible.
- Durable: messages are persisted and survive actor sleep/restart.
- Request/response: clients can wait for a queue completion response.
- Scalable: queues absorb large bursts and handle heavy backpressure safely.
- Local per actor: each actor instance has its own queue storage (scoped by actor key/id).
Queues are commonly referred to as "mailboxes" in other actor frameworks.
What are queues good for?
- Great for any task that changes actor state.
- Helps avoid race conditions by handling work in order.
- Makes complex behavior easier to organize.
Basic queue
This is the default pattern. Define queue names in queues, process them in run, and publish from the client with handle.send(...).
Completable messages
Use this when you want explicit completion/ack semantics but do not need to return data.
message.complete()resolves a sender waiting onwait: true(orenqueueAndWait). It does not change durability: messages are removed from queue storage when they are received, not when they are completed.- If processing fails before
message.complete(), the message is not redelivered, and any waiting sender times out instead of receiving a completion. status: "timedOut"means sender timeout elapsed beforemessage.complete(...).
Request/reply pattern
Use this when the sender needs data back from queued work.
Queue messages from within an actor
Queueing is useful from inside actor logic too, not just from clients.
- Use actions as entrypoints, then enqueue into the run loop to keep mutations serialized.
- You can also call
c.queue.send(...)from other parts ofrunwhen needed. c.queue.send(...)confirms durable enqueue. It does not wait for processing to finish.
Defining queue schemas
You can define queue types with queue() or with schema objects. Schema objects support Standard Schema validators, including Zod.
Pull messages with next and nextBatch
Use next when you want to wait for one queue message.
Use nextBatch when you want to wait for multiple queue messages.
- Waits until messages are available unless timeout is hit.
- Omit
timeoutto wait indefinitely.
Poll messages
Use tryNext when you need one non-blocking read.
Use tryNextBatch for non-blocking batch reads.
- Returns immediately and never waits.
Abort signals
Use signal when your receive loop needs external cancellation semantics in addition to actor shutdown behavior.
Multiple queues
Multiple queues let you separate message flows by purpose. By default, receive calls race across all queues when names is not specified. In this pattern, prompt messages run through a streaming loop while stop messages act as control signals on a separate receive path.
Use iter({ names: ["prompt"] }) as the main stream and next({ names: ["stop"] }) as a stop signal.
Sleeping behavior
If an actor has a run handler, it does not sleep while that handler is actively doing work. It only can sleep when the run loop is blocked waiting for queue entries (for example inside iter(...) or next(...)).
This means you can run normal code in run without worrying about sleep interrupting it mid-call.
Debugging
GET /inspector/queue?limit=50returns queue size and pending message metadata.GET /inspector/summaryincludesqueueSizefor quick queue health checks.POST /queue/:namewithwait: trueis useful to verify completable/request-response behavior.- In non-dev mode, inspector endpoints require authorization.
Recommendations
- Actions are for getting data, queue entries are for mutating data.
- Implement connection auth in
onBeforeConnect. See Authentication. - Route most state changes through one queue loop so ordering stays predictable.
- If you need more complex multi-step run loops, consider using workflows.
- Use
c.abortedandc.abortSignalfor actor shutdown. Use your ownAbortControllerfor earlier loop cancellation. - Add
timeoutwhen callers need bounded wait behavior. - Use
wait: trueonly when the caller actually needs a response.
Pitfalls
Avoid wait: true between actors
wait: true blocks the sender's run loop until the receiver finishes. Between actors, this adds unnecessary overhead and risks deadlocks, especially if the target actor needs to communicate back. If an actor sends a wait: true message to itself, it is a guaranteed deadlock because the run loop is already busy processing the current message.
Reserve wait: true for external callers (HTTP handlers, CLI tools, client apps). For actor-to-actor communication, send a queue message to the other actor without wait: true, then have that actor send a queue message back when the work is done.
Tips
Message TTL
Every queue message includes a createdAt timestamp. Use this to skip or discard stale messages in your run loop:
Delayed delivery
Use c.schedule to enqueue messages at a future time instead of processing them immediately:
See Schedule for the full scheduling API.
Source doc path: /docs/actors/queues