The node's runtime is its modules' bus: it binds each module's consumer and hands its events to the bundle (hq ADR 0198)
A launched bundle's mesh/subscribe binds the module's own durable consumer — EVENTS, <node>_<module>, by name as the module's own runtime bound it, so nothing is lost or replayed in the move — and every event goes to each child of the module that subscribed as mesh/event, acknowledged only when all answered, negatively acknowledged after a short delay when one failed or died, terminated when it is not an event. mesh/ask calls a tool as the module. Every launched bundle is started again when it exits, with backoff, since long-running code waits for no call. Requires SDK 0.1.6.
This commit is contained in:
@@ -157,6 +157,10 @@ func Run(conn *bus.Conn, served []Served, envs map[string]map[string]string, log
|
||||
return out
|
||||
}
|
||||
|
||||
// The module's events, for every child of it that subscribes (ADR 0198): one consumer per module,
|
||||
// bound the first time any of its children subscribes, each event handed to every child that did.
|
||||
events := &consumers{conn: conn, logf: logf, of: map[string]*moduleEvents{}}
|
||||
|
||||
failed := map[string]string{}
|
||||
var registrations []registration
|
||||
var stops []func()
|
||||
@@ -172,13 +176,7 @@ func Run(conn *bus.Conn, served []Served, envs map[string]map[string]string, log
|
||||
fail(path + " is not executable; a bundle the runtime serves is started, never imported, and its build makes it executable (novox/hq ADR 0193)")
|
||||
continue
|
||||
}
|
||||
child, err := launch.Start(module, path, envFor(module), func(params json.RawMessage) error {
|
||||
var env bus.Envelope
|
||||
if err := json.Unmarshal(params, &env); err != nil {
|
||||
return fmt.Errorf("not an event envelope: %w", err)
|
||||
}
|
||||
return conn.PublishAs(module, env)
|
||||
}, logf)
|
||||
child, err := launch.Start(module, path, envFor(module), events.forModule(module), logf)
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
continue
|
||||
@@ -209,6 +207,7 @@ func Run(conn *bus.Conn, served []Served, envs map[string]map[string]string, log
|
||||
}
|
||||
}
|
||||
stopAll := func() {
|
||||
events.stopAll()
|
||||
for i := len(stops) - 1; i >= 0; i-- {
|
||||
stops[i]()
|
||||
}
|
||||
@@ -450,3 +449,132 @@ func serveSeats(conn *bus.Conn, modules []string, registrations []registration,
|
||||
stops = nil
|
||||
}
|
||||
}
|
||||
|
||||
// consumers holds, per module the runtime serves, the one durable consumer its events arrive on and
|
||||
// the children its events are handed to (novox/hq ADR 0198).
|
||||
type consumers struct {
|
||||
conn *bus.Conn
|
||||
logf func(string, ...any)
|
||||
mu sync.Mutex
|
||||
of map[string]*moduleEvents
|
||||
}
|
||||
|
||||
type moduleEvents struct {
|
||||
stop func()
|
||||
delivers []*func(json.RawMessage) error
|
||||
}
|
||||
|
||||
// stopAll unbinds every module's consumer.
|
||||
func (c *consumers) stopAll() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for _, m := range c.of {
|
||||
if m.stop != nil {
|
||||
m.stop()
|
||||
}
|
||||
}
|
||||
c.of = map[string]*moduleEvents{}
|
||||
}
|
||||
|
||||
// forModule is the bus one launched bundle of a module reaches the mesh through.
|
||||
func (c *consumers) forModule(module string) launch.Bus {
|
||||
return &moduleBus{all: c, module: module}
|
||||
}
|
||||
|
||||
type moduleBus struct {
|
||||
all *consumers
|
||||
module string
|
||||
mu sync.Mutex
|
||||
deliver *func(json.RawMessage) error
|
||||
}
|
||||
|
||||
// Publish emits an event as the module (ADR 0193).
|
||||
func (b *moduleBus) Publish(params json.RawMessage) error {
|
||||
var env bus.Envelope
|
||||
if err := json.Unmarshal(params, &env); err != nil {
|
||||
return fmt.Errorf("not an event envelope: %w", err)
|
||||
}
|
||||
return b.all.conn.PublishAs(b.module, env)
|
||||
}
|
||||
|
||||
// Ask calls a tool as the module: `{key, body}`, answered with the tool's result (ADR 0198).
|
||||
func (b *moduleBus) Ask(params json.RawMessage) (json.RawMessage, error) {
|
||||
var asked struct {
|
||||
Key string `json:"key"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
}
|
||||
if err := json.Unmarshal(params, &asked); err != nil || asked.Key == "" {
|
||||
return nil, fmt.Errorf("mesh/ask names no tool: {key, body}")
|
||||
}
|
||||
body := any(asked.Body)
|
||||
if len(asked.Body) == 0 {
|
||||
body = map[string]any{}
|
||||
}
|
||||
answered, err := b.all.conn.AskAs(b.module, asked.Key, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(answered.Result) == 0 {
|
||||
return json.RawMessage("null"), nil
|
||||
}
|
||||
return answered.Result, nil
|
||||
}
|
||||
|
||||
// Subscribe hands this bundle the module's events. The consumer is bound once per module; each of
|
||||
// the module's children that subscribed is handed every event, and the event is acknowledged only
|
||||
// when all of them took it — one consumer split between two readers would give each half.
|
||||
func (b *moduleBus) Subscribe(deliver func(json.RawMessage) error) error {
|
||||
b.mu.Lock()
|
||||
if b.deliver == nil {
|
||||
d := deliver
|
||||
b.deliver = &d
|
||||
} else {
|
||||
*b.deliver = deliver
|
||||
}
|
||||
mine := b.deliver
|
||||
b.mu.Unlock()
|
||||
|
||||
c := b.all
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
m := c.of[b.module]
|
||||
if m == nil {
|
||||
m = &moduleEvents{}
|
||||
c.of[b.module] = m
|
||||
}
|
||||
listed := false
|
||||
for _, d := range m.delivers {
|
||||
if d == mine {
|
||||
listed = true
|
||||
}
|
||||
}
|
||||
if !listed {
|
||||
m.delivers = append(m.delivers, mine)
|
||||
}
|
||||
if m.stop != nil {
|
||||
return nil
|
||||
}
|
||||
module := b.module
|
||||
stop, err := c.conn.ConsumeAs(module, func(env bus.Envelope) error {
|
||||
raw, err := json.Marshal(env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.mu.Lock()
|
||||
targets := append([]*func(json.RawMessage) error(nil), c.of[module].delivers...)
|
||||
c.mu.Unlock()
|
||||
for _, d := range targets {
|
||||
if err := (*d)(raw); err != nil {
|
||||
c.logf("[mesh-tools] %s did not take %s: %v; offered again", module, env.Key, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.stop = stop
|
||||
c.logf("[mesh-tools] %s's events arrive on its consumer %s", module, bus.ConsumerOf(c.conn.Node(), module))
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user