コンテンツにスキップ

コマンドディスパッチ

ディスパッチシステムはプロセスからハンドラにコマンドをルーティングします。プロセスは相関タグ付きでコマンドをyieldし、ハンドラが非同期作業を実行し、結果がイベントキュー経由で戻ります。

sequenceDiagram
participant P as プロセス
participant W as ワーカー
participant R as レジストリ
participant H as ハンドラ
P->>W: yield(command, tag)
W->>R: getHandler(cmdID)
R-->>W: handler
W->>H: Handle(cmd, tag, receiver)
H-->>H: 非同期作業
H->>W: CompleteYield(tag, result)
W->>P: イベントをキュー、ウェイク
P->>P: 結果で再開

レジストリはハイブリッド構造でハンドラを格納:

type Registry struct {
handlers [256]Handler // システムコマンド: O(1)インデックス
extended map[CommandID]Handler // 拡張コマンド: マップルックアップ
frozen atomic.Bool // ブート後はロックフリー
}

システムコマンド(0-255)は配列インデックスを使用。拡張コマンドはマップルックアップを使用。Freeze()後、すべてのルックアップはロックフリー。

範囲モジュール
1-9processSend, Spawn, Terminate, Cancel, Monitor, Unmonitor, Link, Unlink, Exec
10-29clockSleep, Ticker, Timer
30-39socketDial, Listen, Accept, Close
50-59streamRead, Write, Close, Seek
60-69httpRequest, RequestBatch
70-79ttyターミナルI/O
80-89websocketConnect, Send, Receive
90-99eventSubscribe, Send
100-119sqlQuery, Execute, Prepare, Stmt, Tx ops
120-129storeGet, Set, Delete, Has
130-139securityValidateToken, CreateToken
140-149functionCall, AsyncStart, AsyncCancel
150-159execProcessWait
160-169cloudstorageUpload, Download, List, Presigned URLs
170-179evalCompile, Run
180-189workflowSideEffect, Call, Version, UpsertAttrs
190-199contractOpen, Call, AsyncCall, AsyncCancel
256+customユーザー定義サービス

登録はMustRegisterCommands()経由でブート時に行われます。衝突はスタートアップ時にパニック。

コマンドは一意のCommandIDを持つデータ構造です:

const MyCommand dispatcher.CommandID = 200
type MyCmd struct {
Input string
Option int
}
var myCmdPool = sync.Pool{New: func() any { return &MyCmd{} }}
func (c *MyCmd) CmdID() dispatcher.CommandID { return MyCommand }
func (c *MyCmd) Release() {
c.Input = ""
c.Option = 0
myCmdPool.Put(c)
}

プール再利用でホットパスでのアロケーションを排除。パッケージinitで登録:

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

ディスパッチャーは関連ハンドラをグループ化します。RegisterAllでハンドラを登録し、セットアップ/ティアダウン用のライフサイクルメソッドを実装:

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 {
// サービス状態
}
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
}

ブートコンポーネントとして登録:

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
},
})
}

プロセスが非同期作業を必要とする場合、相関タグ付きでコマンドをyield:

type Yield struct {
Cmd Command
Tag uint64 // 相関用のプロセスローカルカウンター
}

ワーカーは各ステップ後にStepOutputからyieldを抽出し、ハンドラにディスパッチします。各タグはリクエストを一意に識別し、結果をマッチバック可能にします。