Command-Dispatch

Der Command-Dispatch leitet Prozess-Yields an Handler und gibt korrelierte Ergebnisse über die Event-Queues der Prozesse zurück.

Diese Seite ist eine Erweiterungs- und Implementierungsreferenz. Die Ausschnitte für eigene Commands und Dispatcher setzen ein vorhandenes Go-Paket, einen Boot-Graphen, eine Command-API und dienstspezifische Fehlerbehandlung voraus.

Fluss

sequenceDiagram
    participant P as Process
    participant W as Worker
    participant R as Registry
    participant H as Handler

    P->>W: yield(command, tag)
    W->>R: getHandler(cmdID)
    R-->>W: handler
    W->>H: Handle(cmd, tag, receiver)
    H-->>H: async work
    H->>W: CompleteYield(tag, result)
    W->>P: queue event, wake
    P->>P: resume with result

Command-Registry

Die Registry speichert Handler in einer hybriden Struktur:

type Registry struct {
    handlers [256]Handler         // System commands: O(1) index
    extended map[CommandID]Handler // Extended commands: map lookup
    frozen   atomic.Bool          // Lock-free after boot
}

System-Commands (0-255) verwenden Array-Indexierung. Erweiterte Commands verwenden Map-Lookup. Nach Freeze() sind alle Lookups lock-frei.

Command-ID-Bereiche

Bereich Modul Beispiele
1-9 process Send, Spawn, Terminate, Cancel, Monitor, Unmonitor, Link, Unlink, Exec
10-29 clock Sleep, Ticker, Timer
30-39 socket Connect, Listen, Accept, Bind, Resolve
50-59 stream Read, Write, Close, Seek
60-69 http Request, RequestBatch
70-79 tty Terminal-E/A
80-89 websocket Connect, Send, Receive
90-99 event Subscribe, Send
100-119 sql Query, Execute, Prepare, Stmt, Tx ops
120-129 store Get, Set, Delete, Has
130-139 security ValidateToken, CreateToken
140-149 function Call, AsyncStart, AsyncCancel
150-159 exec ProcessWait
160-169, 173-174 cloudstorage Upload, Download, List, Presigned URLs, Multipart, OpenReader
170-171 eval Compile, Run
172 cdc Subscribe
180-189 workflow SideEffect, Exec, Version, UpsertAttrs
190-199 contract Open, Call, AsyncCall, AsyncCancel
200-211 pg (Prozessgruppe) Join, Leave, GetMembers, GetLocalMembers, WhichGroups, Broadcast, BroadcastLocal, WhichLocalGroups, Monitor, Events, JoinGroups, LeaveGroups
256+ custom Benutzerdefinierte Services

Pakete reservieren die Eigentümerschaft ihrer Command-IDs aus init() heraus mit MustRegisterCommands(). Kollisionen führen bereits bei der Paketinitialisierung zu einer Panic. Während des Ladens der Komponenten bindet jeder Dienst seine Handler über Registrar.Register. Erst nachdem diese Handler installiert sind, wird der Dispatcher eingefroren.

Commands definieren

Commands sind Datenstrukturen mit einer eindeutigen CommandID:

const MyCommand dispatcher.CommandID = 256

type MyCmd struct {
    Input  string
    Option int
}

func (c *MyCmd) CmdID() dispatcher.CommandID { return MyCommand }

Reservieren Sie die Command-ID bei der Paketinitialisierung:

func init() {
    dispatcher.MustRegisterCommands("myservice", MyCommand)
}

Dispatcher

Ein Dispatcher gruppiert verwandte Handler. Er implementiert RegisterAll um Handler zu registrieren und Lebenszyklus-Methoden für Setup/Teardown:

type Handler interface {
    Handle(ctx context.Context, cmd Command, tag uint64, receiver ResultReceiver) error
}

type ResultReceiver interface {
    CompleteYield(tag uint64, data any, err error)
}
type Dispatcher struct {
    // service state
}

func (d *Dispatcher) RegisterAll(register func(id dispatcher.CommandID, h dispatcher.Handler)) {
    register(myapi.MyCommand, dispatcher.HandlerFunc(d.handleMyCommand))
}

func (d *Dispatcher) handleMyCommand(ctx context.Context, cmd Command, tag uint64, receiver ResultReceiver) error {
    c := cmd.(*myapi.MyCmd)
    go func() {
        result := doWork(c)
        if ctx.Err() == nil {
            receiver.CompleteYield(tag, result, nil)
        }
    }()
    return nil
}

Als Boot-Komponente registrieren:

func MyDispatcher() boot.Component {
    return boot.New(boot.P{
        Name:      "dispatcher.myservice",
        DependsOn: []boot.Name{DispatcherName},
        Load: func(ctx context.Context) (context.Context, error) {
            reg := dispatcher.GetRegistrar(ctx)
            svc := myservice.NewDispatcher()
            svc.RegisterAll(reg.Register)
            return ctx, nil
        },
    })
}

Yields und Korrelation

Wenn ein Prozess asynchrone Arbeit benötigt, yieldet er einen Command mit einem Korrelationstag:

type Yield struct {
    Cmd Command
    Tag uint64    // Process-local counter for correlation
}

Der Worker extrahiert Yields aus StepOutput nach jedem Step und dispatcht sie an Handler. Jedes Tag identifiziert die Anfrage eindeutig, sodass Ergebnisse zurückgemappt werden können.

Siehe auch