Documentation
¶
Overview ¶
Package bus is the pub/sub messaging that connects workers to each other and to the runner that manages them.
Each subscriber receives messages independently, through a queue of its own, so one that is slow to handle a message never holds up another. System messages are delivered ahead of the data messages already queued, which is what lets a cancel reach a worker before the work it is calling off.
A Bus is the transport-independent core. AsyncQueueBus delivers in process; a networked one embeds Bus, overrides Publish for its transport, and calls OnMessageReceived as messages arrive.
Index ¶
- Variables
- type ActivateWorkerMessage
- type AddWorkerMessage
- type AsyncQueueBus
- type BaseDataMessage
- type BaseMessage
- type BaseSystemMessage
- type BridgeConfig
- type BridgeProcessor
- func (p *BridgeProcessor) Cleanup(ctx context.Context) error
- func (p *BridgeProcessor) OnBusMessage(ctx context.Context, m Message)
- func (p *BridgeProcessor) ProcessFrame(ctx context.Context, f frames.Frame, dir processor.Direction) error
- func (p *BridgeProcessor) Setup(ctx context.Context, s processor.Setup) error
- type BridgedWorker
- type Bus
- type CancelMessage
- type CancelWorkerMessage
- type DeactivateWorkerMessage
- type DeserializeFunc
- type EdgeConfig
- type EdgeProcessor
- func (p *EdgeProcessor) Cleanup(ctx context.Context) error
- func (p *EdgeProcessor) OnBusMessage(ctx context.Context, m Message)
- func (p *EdgeProcessor) ProcessFrame(ctx context.Context, f frames.Frame, dir processor.Direction) error
- func (p *EdgeProcessor) Setup(ctx context.Context, s processor.Setup) error
- type EndMessage
- type EndWorkerMessage
- type FlushProgressMessage
- type FrameMessage
- type JSONSerializer
- type JobCancelMessage
- type JobProgress
- type JobRequestMessage
- type JobResponse
- type JobResponseMessage
- type JobResponseUrgentMessage
- type JobResult
- type JobStreamDataMessage
- type JobStreamEndMessage
- type JobStreamStartMessage
- type JobUpdate
- type JobUpdateMessage
- type JobUpdateRequestMessage
- type JobUpdateUrgentMessage
- type LocalMessage
- type Message
- type MessageFilter
- type MessageSerializer
- type Publisher
- type SerializeFunc
- type Subscriber
- type SystemMessage
- type TTSSpeakMessage
- type TypeAdapter
- type TypeRegistry
- type WorkerErrorMessage
- type WorkerLocalErrorMessage
- type WorkerReadyMessage
- type WorkerRegistryMessage
Constants ¶
This section is empty.
Variables ¶
var ( // ErrWrongAdapter reports an adapter handed a value of another type. ErrWrongAdapter = errors.New("bus: the adapter was given the wrong type") // ErrUnnamedType reports a type with an adapter but no registered name, so // there is nothing to tag it with. ErrUnnamedType = errors.New("bus: the type has an adapter but no registered name") // ErrWrongShape reports a value that arrived as one JSON shape where // another was expected. ErrWrongShape = errors.New("bus: the value has the wrong shape") // ErrNotJSON reports raw JSON that will not parse. ErrNotJSON = errors.New("bus: the raw JSON is not JSON") )
The failures serialization reports. Each names a shape that arrived where another was expected, which on a bus means the two ends disagree about what they are carrying.
var ErrNotAMessage = errors.New("bus: the payload is not a bus message")
ErrNotAMessage reports bytes that carried something other than a bus message.
var ErrUnknownType = errors.New("bus: no type registered for that name")
ErrUnknownType reports a wire name this process has no type registered for, which is what a peer carrying something this one does not know looks like.
var Types = NewTypeRegistry()
Types is the registry every built-in bus message and frame is registered in, and the one a serializer uses unless it is given another.
A type registers under its own name, without its package, because that name is all the far end has to go on. Registering here is what makes a value able to cross a bus that leaves the process: anything not registered is refused on arrival rather than silently arriving as something else.
Register a type of your own with Types.Register at startup.
Functions ¶
This section is empty.
Types ¶
type ActivateWorkerMessage ¶
type ActivateWorkerMessage struct {
BaseDataMessage
// Args are the activation arguments, and may be nil.
Args map[string]any
}
ActivateWorkerMessage asks a worker to become active.
type AddWorkerMessage ¶
type AddWorkerMessage struct {
BaseSystemMessage
// Worker is the worker to manage.
Worker any
// contains filtered or unexported fields
}
AddWorkerMessage hands a worker to the runner to manage. It never leaves the process, because it carries the worker itself rather than a description of one.
Worker is typed as any because the workers are built on this package and so cannot be named here. Set it to a worker; anything else is ignored by the runner that receives it.
type AsyncQueueBus ¶
type AsyncQueueBus struct {
*Bus
}
AsyncQueueBus delivers messages in process, straight to the local subscribers. It is the bus a runner and its workers use when they all live in one process, and the only one the rest of the framework needs.
func NewAsyncQueueBus ¶
func NewAsyncQueueBus() *AsyncQueueBus
NewAsyncQueueBus builds an in-process bus.
type BaseDataMessage ¶
type BaseDataMessage struct{ BaseMessage }
BaseDataMessage is embedded by every message delivered in send order.
type BaseMessage ¶
type BaseMessage struct {
// From is the name of the sender.
From string
// To is the name of the intended recipient, or "" to broadcast.
To string
}
BaseMessage carries what every message has. Embed BaseDataMessage or BaseSystemMessage rather than this directly.
type BaseSystemMessage ¶
type BaseSystemMessage struct{ BaseMessage }
BaseSystemMessage is embedded by every message delivered ahead of the queued data messages.
type BridgeConfig ¶
type BridgeConfig struct {
// Bus is the bus to exchange frames over. Required.
Bus *Bus
// WorkerName is the owning worker's name, carried as the source of
// everything this bridge sends.
WorkerName string
// TargetWorker, when set, is the only worker this bridge accepts frames
// from.
TargetWorker string
// Bridge, when set, names this bridge for routing: what it sends is tagged
// with the name, and it accepts only frames tagged with the same one.
Bridge string
// ExcludeFrames are frame types that never cross the bus, on top of the
// lifecycle frames, which never do. A frame matches when it is of the same
// concrete type as one given here.
ExcludeFrames []frames.Frame
}
BridgeConfig configures a BridgeProcessor.
type BridgeProcessor ¶
BridgeProcessor exchanges frames with other workers over the bus, from the middle of a pipeline.
A frame reaching it is taken off the local pipeline and sent to the bus; a frame arriving from the bus is pushed on locally. Lifecycle frames, urgent transport messages and any excluded types pass straight through instead.
func NewBridgeProcessor ¶
func NewBridgeProcessor(cfg BridgeConfig) *BridgeProcessor
NewBridgeProcessor builds a mid-pipeline bridge onto the bus.
func (*BridgeProcessor) Cleanup ¶
func (p *BridgeProcessor) Cleanup(ctx context.Context) error
Cleanup unsubscribes the bridge from the bus.
func (*BridgeProcessor) OnBusMessage ¶
func (p *BridgeProcessor) OnBusMessage(ctx context.Context, m Message)
OnBusMessage pushes a frame arriving from another worker into this pipeline.
func (*BridgeProcessor) ProcessFrame ¶
func (p *BridgeProcessor) ProcessFrame(ctx context.Context, f frames.Frame, dir processor.Direction) error
ProcessFrame sends a frame across the bus, or forwards it locally when it is one that must not cross.
type BridgedWorker ¶
type BridgedWorker interface {
// Bus is the bus the worker is attached to. It is read when the edge is set
// up rather than when it is built, so the worker need only be attached by
// then.
Bus() *Bus
// Name is the worker's name, carried as the source of what the edge sends.
Name() string
// Active reports whether the worker is active. An inactive worker takes no
// frames off the bus.
Active() bool
// QueueFrame hands a frame to the worker's own queue.
QueueFrame(f frames.Frame, dir ...processor.Direction)
}
BridgedWorker is the part of a pipeline worker an EdgeProcessor reads. It is named here rather than imported because the workers are built on this package; a pipeline worker satisfies it.
type Bus ¶
type Bus struct {
// contains filtered or unexported fields
}
Bus is the transport-independent core of a worker bus. Embed it in a concrete bus and implement Publisher:
type MyBus struct{ *bus.Bus }
func (b *MyBus) Publish(ctx context.Context, m bus.Message) { … }
The zero value is not usable; build one with New.
func New ¶
New builds a Bus. self is the embedding bus, whose Publish carries a message on its transport; pass nil for a bus that only delivers locally.
func (*Bus) OnMessageReceived ¶
OnMessageReceived hands a message to every local subscriber. A concrete bus calls it when a message arrives, whether from a local Send or off the network.
func (*Bus) Send ¶
Send puts a message on the bus. A local-only message is delivered straight to the subscribers here; everything else goes to the transport, which delivers it back through OnMessageReceived.
func (*Bus) Start ¶
Start begins dispatching to every subscriber registered so far. Messages sent before it are queued, not lost, and are delivered once it runs.
func (*Bus) Stop ¶
func (b *Bus) Stop()
Stop ends dispatch and waits for the goroutines to return. Messages sent afterwards are queued but not delivered until Start runs again.
func (*Bus) Subscribe ¶
func (b *Bus) Subscribe(s Subscriber)
Subscribe registers a subscriber. It is idempotent: registering one already registered, by name, changes nothing.
func (*Bus) Unsubscribe ¶
func (b *Bus) Unsubscribe(s Subscriber)
Unsubscribe removes a subscriber and stops delivering to it.
type CancelMessage ¶
type CancelMessage struct {
BaseSystemMessage
// Reason describes why, and may be empty.
Reason string
}
CancelMessage stops everything at once, without flushing queued work.
type CancelWorkerMessage ¶
type CancelWorkerMessage struct {
BaseSystemMessage
// Reason describes why, and may be empty.
Reason string
}
CancelWorkerMessage stops one worker at once.
type DeactivateWorkerMessage ¶
type DeactivateWorkerMessage struct{ BaseDataMessage }
DeactivateWorkerMessage asks a worker to become inactive.
type DeserializeFunc ¶
DeserializeFunc reconstructs one value from its wire form, and is the counterpart handed to a TypeAdapter.
type EdgeConfig ¶
type EdgeConfig struct {
// Worker is the worker that owns the pipeline. Required.
Worker BridgedWorker
// Direction is the direction this edge copies to the bus. Frames arriving
// from the bus traveling the other way are injected here.
Direction processor.Direction
// Bridges are the bridge names this edge accepts frames from. Empty accepts
// every bridge.
Bridges []string
// ExcludeFrames are frame types that never cross the bus, on top of the
// lifecycle frames.
ExcludeFrames []frames.Frame
}
EdgeConfig configures an EdgeProcessor.
type EdgeProcessor ¶
EdgeProcessor tees a pipeline's edge onto the bus: frames carry on locally and matching ones are copied across.
It is placed by a pipeline worker at the source and sink of a bridged pipeline. Upstream keeps it private to the worker; Go's package boundary makes that impossible, so it is exported and documented as the worker's own.
func NewEdgeProcessor ¶
func NewEdgeProcessor(cfg EdgeConfig) *EdgeProcessor
NewEdgeProcessor builds a pipeline-edge tee onto the bus.
func (*EdgeProcessor) Cleanup ¶
func (p *EdgeProcessor) Cleanup(ctx context.Context) error
Cleanup unsubscribes the edge from the worker's bus.
func (*EdgeProcessor) OnBusMessage ¶
func (p *EdgeProcessor) OnBusMessage(ctx context.Context, m Message)
OnBusMessage injects a frame arriving from another worker into the pipeline.
func (*EdgeProcessor) ProcessFrame ¶
func (p *EdgeProcessor) ProcessFrame(ctx context.Context, f frames.Frame, dir processor.Direction) error
ProcessFrame forwards every frame locally, and copies the ones traveling this edge's direction to the bus.
type EndMessage ¶
type EndMessage struct {
BaseDataMessage
// Reason describes why, and may be empty.
Reason string
}
EndMessage asks everything to shut down gracefully.
type EndWorkerMessage ¶
type EndWorkerMessage struct {
BaseDataMessage
// Reason describes why, and may be empty.
Reason string
}
EndWorkerMessage asks one worker to shut down gracefully.
type FlushProgressMessage ¶
type FlushProgressMessage struct {
BaseDataMessage
// FlushID is the id of the probe being reported on.
FlushID uint64
}
FlushProgressMessage reports that a flush probe from another worker is still making progress. A probe that crosses into another pipeline is answered there, so the worker waiting on it cannot see whether anything is happening. The pipeline holding the probe says so, and the wait stays alive for as long as it keeps saying it.
type FrameMessage ¶
type FrameMessage struct {
BaseDataMessage
// Frame is the frame being carried.
Frame frames.Frame
// Direction is the direction it should travel in on arrival.
Direction processor.Direction
// Bridge names the bridge it came through, or "" when it did not come
// through one.
Bridge string
}
FrameMessage carries a pipeline frame between workers, which is how a worker bridged into another's pipeline exchanges work with it.
type JSONSerializer ¶
type JSONSerializer struct {
// contains filtered or unexported fields
}
JSONSerializer writes bus messages as JSON, tagging every value whose type cannot be read back from the JSON alone.
A value is written as it stands when JSON already describes it: a string, a number, a bool. Anything else is written as {"__type__":…,"__data__":…}, and the name is what the far end looks up to know what to build. Raw bytes are tagged too, base64 inside, since JSON has no way to carry them.
A type whose state the serializer cannot reach, because it is private or because its wire form is deliberately not its field layout, is handled by a TypeAdapter registered against it. The adapters for a conversation and its toolset are registered by default.
func NewJSONSerializer ¶
func NewJSONSerializer(types *TypeRegistry) *JSONSerializer
NewJSONSerializer builds a serializer over types, with the default adapters registered. A nil registry uses the package's own, which every built-in bus message and frame is registered in.
func (*JSONSerializer) Deserialize ¶
func (s *JSONSerializer) Deserialize(data []byte) (Message, error)
Deserialize reconstructs the message those bytes carried.
func (*JSONSerializer) RegisterAdapter ¶
func (s *JSONSerializer) RegisterAdapter(sample any, a TypeAdapter)
RegisterAdapter records the adapter that converts values of the type sample points at. sample is a zero value given as a pointer, as for the registry.
type JobCancelMessage ¶
type JobCancelMessage struct {
BaseSystemMessage
// JobID identifies the job.
JobID string
// Reason describes why, and may be empty.
Reason string
}
JobCancelMessage calls a job off. It is a system message, so it reaches the worker ahead of the work it is calling off.
type JobProgress ¶
type JobProgress struct {
// JobID identifies the job.
JobID string
// Update is the progress being reported, and may be nil.
Update map[string]any
}
JobProgress is the body of a message reporting progress on a running job.
func (*JobProgress) Progress ¶
func (p *JobProgress) Progress() *JobProgress
Progress is the body, and is what makes a message a JobUpdate.
type JobRequestMessage ¶
type JobRequestMessage struct {
BaseDataMessage
// JobID identifies the job, and is what every later message about it
// carries.
JobID string
// JobName names the kind of work, and may be empty.
JobName string
// Payload is the job's input, and may be nil.
Payload map[string]any
}
JobRequestMessage asks a worker to carry out a job.
type JobResponse ¶
JobResponse reports how a job ended, whichever band it travels in.
type JobResponseMessage ¶
type JobResponseMessage struct {
BaseDataMessage
JobResult
}
JobResponseMessage reports how a job ended.
type JobResponseUrgentMessage ¶
type JobResponseUrgentMessage struct {
BaseSystemMessage
JobResult
}
JobResponseUrgentMessage reports how a job ended, ahead of the queued work.
type JobResult ¶
type JobResult struct {
// JobID identifies the job.
JobID string
// Status is how it ended.
Status jobcontext.JobStatus
// Response is the job's output, and may be nil.
Response map[string]any
}
JobResult is the body of a message reporting how a job ended.
type JobStreamDataMessage ¶
type JobStreamDataMessage struct {
BaseDataMessage
// JobID identifies the job.
JobID string
// Data is the item, and may be nil.
Data map[string]any
}
JobStreamDataMessage carries one item of a job's result stream.
type JobStreamEndMessage ¶
type JobStreamEndMessage struct {
BaseDataMessage
// JobID identifies the job.
JobID string
// Data is what closes the stream, and may be nil.
Data map[string]any
}
JobStreamEndMessage closes a job's result stream.
type JobStreamStartMessage ¶
type JobStreamStartMessage struct {
BaseDataMessage
// JobID identifies the job.
JobID string
// Data is what opens the stream, and may be nil.
Data map[string]any
}
JobStreamStartMessage opens a stream of results for a job.
type JobUpdate ¶
type JobUpdate interface {
Message
// Progress is what the message reports.
Progress() *JobProgress
}
JobUpdate reports progress on a running job, whichever band it travels in.
type JobUpdateMessage ¶
type JobUpdateMessage struct {
BaseDataMessage
JobProgress
}
JobUpdateMessage reports progress on a job still running.
type JobUpdateRequestMessage ¶
type JobUpdateRequestMessage struct {
BaseDataMessage
// JobID identifies the job.
JobID string
}
JobUpdateRequestMessage asks for the current progress of a job.
type JobUpdateUrgentMessage ¶
type JobUpdateUrgentMessage struct {
BaseSystemMessage
JobProgress
}
JobUpdateUrgentMessage reports progress ahead of the queued work.
type LocalMessage ¶
type LocalMessage interface {
Message
// contains filtered or unexported methods
}
LocalMessage is a message that stays on this bus and is never forwarded to a remote one.
type Message ¶
type Message interface {
// Source is the name of the worker or component that sent it.
Source() string
// Target is the name of the worker it is for, or "" to broadcast.
Target() string
// contains filtered or unexported methods
}
Message is one message carried by the bus.
type MessageFilter ¶
type MessageFilter interface {
// AcceptsBusMessage reports whether this subscriber should be handed this
// message. Returning false drops it for this subscriber alone; the others
// still receive it.
AcceptsBusMessage(m Message) bool
}
MessageFilter is implemented by a Subscriber that does not take every message. The bus asks before every delivery, and a subscriber that does not implement it takes everything.
type MessageSerializer ¶
type MessageSerializer interface {
// Serialize converts a message to the bytes to send.
Serialize(m Message) ([]byte, error)
// Deserialize reconstructs the message those bytes carried.
Deserialize(data []byte) (Message, error)
}
MessageSerializer converts bus messages to bytes and back. A bus that leaves the process holds one and uses it at each edge.
type Publisher ¶
Publisher is what a concrete bus implements to carry a message on its transport. Bus.Send calls it for everything that is not local-only.
type SerializeFunc ¶
SerializeFunc converts one value to its wire form. A TypeAdapter is handed one so it can serialize what its own fields hold without knowing how.
type Subscriber ¶
type Subscriber interface {
// Name identifies this subscriber on the bus. Subscribing twice under the
// same name is a no-op.
Name() string
// OnBusMessage handles one message. A panic in it is contained: the
// subscriber keeps receiving.
OnBusMessage(ctx context.Context, m Message)
}
Subscriber receives messages from a bus.
type SystemMessage ¶
type SystemMessage interface {
Message
// contains filtered or unexported methods
}
SystemMessage is a message delivered ahead of the queued data messages.
type TTSSpeakMessage ¶
type TTSSpeakMessage struct {
BaseDataMessage
// Text is what to say.
Text string
// AppendToContext reports whether what is said joins the conversation.
AppendToContext bool
}
TTSSpeakMessage asks a worker to say something.
type TypeAdapter ¶
type TypeAdapter interface {
// Serialize converts obj to a map, using serialize for whatever its own
// fields hold that it cannot convert itself.
Serialize(obj any, serialize SerializeFunc) (map[string]any, error)
// Deserialize rebuilds the value from a map serialize produced.
Deserialize(data map[string]any, deserialize DeserializeFunc) (any, error)
}
TypeAdapter converts values of one type to and from a plain map, for a type the serializer cannot take apart itself: one whose state is private, or whose wire form is deliberately not its field layout.
Register one with JSONSerializer.RegisterAdapter. Unlike upstream, the adapter is not told which type to build: it is registered against exactly one type and is the only thing that builds it.
type TypeRegistry ¶
type TypeRegistry struct {
// contains filtered or unexported fields
}
TypeRegistry maps the type names on the wire to the values they name.
It is what stands in for reading a type out of a module path at run time, which is how the same machinery works in a dynamic language and is not available here. A type that may cross a bus registers itself, once, and the registry answers in both directions: the name to write for a value, and a fresh value to fill for a name.
A name is the type's own, without its package: "FrameMessage", "TranscriptionFrame". Two registered types may not share one, since the name is all the far end has to go on.
func NewTypeRegistry ¶
func NewTypeRegistry() *TypeRegistry
NewTypeRegistry returns an empty registry.
func (*TypeRegistry) NameOf ¶
func (r *TypeRegistry) NameOf(v any) (string, bool)
NameOf is the wire name for the value's type, reporting false for a type that never registered one.
func (*TypeRegistry) Names ¶
func (r *TypeRegistry) Names() []string
Names lists every registered wire name, for reporting what a process can carry.
func (*TypeRegistry) New ¶
func (r *TypeRegistry) New(name string) (any, bool)
New builds a fresh value of the type name refers to, as a pointer to it. It reports false for a name nothing registered, which is what a message from a peer that knows a type this one does not looks like.
func (*TypeRegistry) Register ¶
func (r *TypeRegistry) Register(name string, sample any)
Register records that values of the type sample points at travel as name.
sample is a zero value of the type, given as a pointer to it (&FrameMessage{}), which is both what identifies the type and what the registry copies to build a fresh one. Registering the same name twice panics: it is a mistake made once, at startup, and a wire name that resolves to two different things silently corrupts every message carrying it.
type WorkerErrorMessage ¶
type WorkerErrorMessage struct {
BaseSystemMessage
// Error describes the failure.
Error string
}
WorkerErrorMessage reports a worker that failed, to everyone.
type WorkerLocalErrorMessage ¶
type WorkerLocalErrorMessage struct {
BaseSystemMessage
// Error describes the failure.
Error string
// contains filtered or unexported fields
}
WorkerLocalErrorMessage reports a worker that failed, to this process only.
type WorkerReadyMessage ¶
type WorkerReadyMessage struct {
BaseDataMessage
// Runner is the runner managing the worker.
Runner string
// Parent is the worker's parent, or "" for a root worker.
Parent string
// Active reports whether it is currently active.
Active bool
// Bridged reports whether it is bridged.
Bridged bool
// StartedAt is when it became ready, as a Unix timestamp, and zero when
// unset.
StartedAt float64
}
WorkerReadyMessage announces that a worker has started and can be addressed.
type WorkerRegistryMessage ¶
type WorkerRegistryMessage struct {
BaseSystemMessage
// Runner is the runner the snapshot belongs to.
Runner string
// Workers is the snapshot.
Workers []registry.WorkerRegistryEntry
}
WorkerRegistryMessage announces which workers a runner knows about, so the runners on a bus can learn of each other's workers.