turnqueue

package
v0.2.0-alpha.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

Documentation

Overview

Package turnqueue serializes the turns of one conversation.

Two paths reach it -- a WebSocket message and an HTTP post -- and a conversation may only be running one turn at a time whichever arrives. The registry is server-scoped rather than connection-scoped for that reason: a second browser tab is a second connection but the same conversation.

Index

Constants

MaxQueued caps how many turns may wait behind the one running in a conversation. Past that a submission is refused rather than silently accepted: a user who cannot see the backlog should not be able to grow it without limit.

Variables

View Source
var ErrDraining = errors.New("this server is shutting down; retry the turn")

ErrDraining is returned once the server has begun shutting down. A turn is a model call that writes message history, so starting one this process cannot finish is worse than refusing it: the caller retries against an instance that will still be here.

View Source
var ErrQueueFull = errors.New("too many turns are already queued for this conversation")

ErrQueueFull is returned by the registry when a conversation is at its cap.

Functions

This section is empty.

Types

type Job

type Job struct {

	// OnDequeue, when set, is called just before run for a job that had to wait.
	// A job that started immediately never sees it, which is what lets a surface
	// announce "this queued message is starting now" without a race.
	OnDequeue func()
	Done      chan struct{}
	Dropped   atomic.Bool
	// contains filtered or unexported fields
}

Job is one conversation turn waiting for its conversation to be free.

func NewJob

func NewJob(run func()) *Job

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry owns the per-conversation turn queues for the whole server.

It lives on the Handler rather than on a WebSocket connection because a conversation outlives any one connection: the same conversation is reachable from a reconnected socket, a second browser tab, the HTTP API, and a system turn reporting a finished task. Serialization anchored to a connection would let two of those interleave their reads and writes of the same message history.

func NewRegistry

func NewRegistry() *Registry

func (*Registry) Drain

func (r *Registry) Drain()

Drain refuses new turns. Turns already running are left alone — Wait is what gives them their moment to finish.

func (*Registry) RunSync

func (r *Registry) RunSync(ctx context.Context, conversationID string, run func()) error

RunSync submits a turn and waits for it to finish. It is what a request that has to stream its own turn back to the caller uses, where a fire-and-forget submit would return before there was anything to send.

A caller that goes away before its turn starts marks the job dropped and returns the context error; the queue moves on to the next turn.

func (*Registry) Submit

func (r *Registry) Submit(conversationID string, job *Job) (int, error)

Submit hands a turn to the conversation's queue. It returns 0 when the turn started immediately, or the job's 1-based position when it had to wait. Submit never blocks; the turn runs on a goroutine the registry owns.

func (*Registry) Wait

func (r *Registry) Wait(ctx context.Context) bool

Wait blocks until every running turn has finished or ctx expires, and reports whether they all finished. Call Drain first, or a new turn can keep it waiting indefinitely.

func (*Registry) Waiting

func (r *Registry) Waiting(conversationID string) int

Waiting reports how many turns are queued behind the conversation's current turn.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL