Carry a bundle's seat traffic under its own name and kind, and start a module as its own account (hq ADR 0259)
This commit is contained in:
@@ -458,6 +458,8 @@ type consumers struct {
|
||||
logf func(string, ...any)
|
||||
mu sync.Mutex
|
||||
of map[string]*moduleEvents
|
||||
// seatBound is each module's seat traffic bound once: a worker taken, a proof subject answered.
|
||||
seatBound map[string]func()
|
||||
}
|
||||
|
||||
type moduleEvents struct {
|
||||
@@ -475,6 +477,10 @@ func (c *consumers) stopAll() {
|
||||
}
|
||||
}
|
||||
c.of = map[string]*moduleEvents{}
|
||||
for _, stop := range c.seatBound {
|
||||
stop()
|
||||
}
|
||||
c.seatBound = map[string]func(){}
|
||||
}
|
||||
|
||||
// forModule is the bus one launched bundle of a module reaches the mesh through.
|
||||
@@ -604,9 +610,10 @@ func eventRef(env bus.Envelope) string {
|
||||
// stateAsked is what a bundle names when it reaches its state (ADR 0201): the state by the name its
|
||||
// module uses, a key, and for a put the value.
|
||||
type stateAsked struct {
|
||||
State string `json:"state"`
|
||||
Key string `json:"key"`
|
||||
Value json.RawMessage `json:"value"`
|
||||
State string `json:"state"`
|
||||
Key string `json:"key"`
|
||||
Value json.RawMessage `json:"value"`
|
||||
Revision uint64 `json:"revision"`
|
||||
}
|
||||
|
||||
// State answers a bundle's `get`, `put`, `delete` and `keys` on its module's state (ADR 0201). The
|
||||
@@ -645,6 +652,20 @@ func (b *moduleBus) State(verb string, params json.RawMessage) (json.RawMessage,
|
||||
return nil, err
|
||||
}
|
||||
answer = keys
|
||||
case "create":
|
||||
// Only when the key has no value: of two writers making it, one wins (novox/hq ADR 0259).
|
||||
revision, err := conn.StateCreate(b.module, asked.State, asked.Key, asked.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
answer = map[string]any{"revision": revision}
|
||||
case "update":
|
||||
// Only when the key is still at the revision read: a second answer to one ask loses (ADR 0259).
|
||||
revision, err := conn.StateUpdate(b.module, asked.State, asked.Key, asked.Value, asked.Revision)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
answer = map[string]any{"revision": revision}
|
||||
default:
|
||||
return nil, fmt.Errorf("the runtime answers no mesh/state.%s", verb)
|
||||
}
|
||||
@@ -666,3 +687,89 @@ func (b *moduleBus) Watch(params json.RawMessage, deliver func(json.RawMessage)
|
||||
return deliver(raw)
|
||||
})
|
||||
}
|
||||
|
||||
// seatAsked is what a bundle names for its seat traffic (novox/hq ADR 0259 §3).
|
||||
type seatAsked struct {
|
||||
Subject string `json:"subject"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
ID string `json:"id"`
|
||||
Worker string `json:"worker"`
|
||||
Bucket string `json:"bucket"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
// Seat answers a bundle's seat traffic, each as the module and only as far as its membership lists:
|
||||
// `publish` an accept or an event, `prove` (ask a proof), `record` (read a record kept for it), `take` a
|
||||
// worker's work and `answer` a proof subject — the last two bound once per module and subject, each
|
||||
// message or proof handed to the module's child that asked last (novox/hq ADR 0259 §3).
|
||||
func (b *moduleBus) Seat(verb string, params json.RawMessage, handOn func(string, any) (json.RawMessage, error)) (json.RawMessage, error) {
|
||||
var asked seatAsked
|
||||
if err := json.Unmarshal(params, &asked); err != nil {
|
||||
return nil, fmt.Errorf("mesh/seat.%s: %w", verb, err)
|
||||
}
|
||||
conn := b.all.conn
|
||||
switch verb {
|
||||
case "publish":
|
||||
seq, err := conn.SeatPublish(b.module, asked.Subject, asked.Body, asked.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(map[string]any{"sequence": seq})
|
||||
case "prove":
|
||||
return conn.SeatProve(b.module, asked.Subject, asked.Body)
|
||||
case "record":
|
||||
entry, err := conn.SeatRecord(b.module, asked.Bucket, asked.Key)
|
||||
if err != nil || entry == nil {
|
||||
return json.RawMessage("null"), err
|
||||
}
|
||||
return json.Marshal(entry)
|
||||
case "take":
|
||||
key := "take " + asked.Worker
|
||||
if b.bound(key) {
|
||||
return json.RawMessage(`{}`), nil
|
||||
}
|
||||
stop, err := conn.SeatTake(b.module, asked.Worker, func(w bus.Work) error {
|
||||
_, err := handOn("mesh/work", map[string]any{"work": w})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.bind(key, stop)
|
||||
b.all.logf("[mesh-tools] %s takes its work from %s", b.module, asked.Worker)
|
||||
return json.RawMessage(`{}`), nil
|
||||
case "answer":
|
||||
key := "answer " + asked.Subject
|
||||
if b.bound(key) {
|
||||
return json.RawMessage(`{}`), nil
|
||||
}
|
||||
stop, err := conn.SeatAnswer(b.module, asked.Subject, func(subject string, body json.RawMessage) (json.RawMessage, error) {
|
||||
return handOn("mesh/proof", map[string]any{"subject": subject, "body": body})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.bind(key, stop)
|
||||
b.all.logf("[mesh-tools] %s answers the proofs on %s", b.module, asked.Subject)
|
||||
return json.RawMessage(`{}`), nil
|
||||
}
|
||||
return nil, fmt.Errorf("the runtime answers no mesh/seat.%s", verb)
|
||||
}
|
||||
|
||||
// bound and bind keep, per module, what its seat traffic is bound to, so a child that starts again and
|
||||
// asks again is handed what is already bound instead of a second reader of it.
|
||||
func (b *moduleBus) bound(key string) bool {
|
||||
b.all.mu.Lock()
|
||||
defer b.all.mu.Unlock()
|
||||
_, ok := b.all.seatBound[b.module+" "+key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (b *moduleBus) bind(key string, stop func()) {
|
||||
b.all.mu.Lock()
|
||||
defer b.all.mu.Unlock()
|
||||
if b.all.seatBound == nil {
|
||||
b.all.seatBound = map[string]func(){}
|
||||
}
|
||||
b.all.seatBound[b.module+" "+key] = stop
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user