Publish a module's stream events on its stream's subject and bind the stream consumers its membership names, so ADR 0363's own streams work in the runtime (hq issue 725)
mesh/merge-gate head 9921486fd53e merged into trunk d0dcae53a8c0 — pass, with a note: builds mesh-tools, node-tools (+39 dependent(s)) → ace, g14, novox, s…
mesh/repo-check head 9921486fd53e merged into trunk d0dcae53a8c0 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 9921486fd53e merged into trunk d0dcae53a8c0 — pass, with a note: builds mesh-tools, node-tools (+39 dependent(s)) → ace, g14, novox, s…
mesh/repo-check head 9921486fd53e merged into trunk d0dcae53a8c0 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
This commit is contained in:
@@ -10,7 +10,6 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
@@ -71,6 +70,12 @@ type Membership struct {
|
||||
// SeatTraffic is what the module's code may submit, say, take, ask, answer and read on seats that name
|
||||
// their caller or their kind (novox/hq ADR 0259): the runtime does for it only what is listed here.
|
||||
SeatTraffic *SeatTraffic `json:"seat-traffic,omitempty"`
|
||||
// Streams are the module's own streams (novox/hq ADR 0363 §2): an event one of them carries is published
|
||||
// on that stream's subject, never on EVENTS (streams.go).
|
||||
Streams []StreamIssued `json:"streams,omitempty"`
|
||||
// Hears are the module streams the module hears, each through a durable consumer on that stream the
|
||||
// runtime binds beside the one on EVENTS (ADR 0363 §8).
|
||||
Hears []HeardIssued `json:"hears,omitempty"`
|
||||
}
|
||||
|
||||
// Served is an address a tool is answered on; `{tool}` stands for the tool's name.
|
||||
@@ -715,7 +720,9 @@ func (c *Conn) answerOf(subject string, data []byte) (Answered, bool, error) {
|
||||
// PublishAs emits an event as a module: published into JetStream and awaited, de-duplicated by its
|
||||
// own id (ADR 0042). The body is the payload; the metadata rides as headers.
|
||||
func (c *Conn) PublishAs(module string, env Envelope) error {
|
||||
msg := nats.NewMsg("mesh.mod." + module + ".event." + env.Key)
|
||||
// On the module's own stream when its membership names one carrying the event (ADR 0363 §2), on EVENTS
|
||||
// otherwise — and only there, as before, for a membership that names no stream.
|
||||
msg := nats.NewMsg(eventSubject(c.Membership(module), module, env.Key))
|
||||
for k, v := range env.Headers {
|
||||
msg.Header.Set(k, v)
|
||||
}
|
||||
@@ -937,64 +944,34 @@ var NakDelay = 5 * time.Second
|
||||
// handed over, and a burst would end in max-deliver with nothing failed (novox/hq issue 387).
|
||||
var EventHeartbeat = 10 * time.Second
|
||||
|
||||
// ConsumeAs reads a module's durable consumer and hands each event to deliver (novox/hq ADR 0198):
|
||||
// acknowledged when deliver returns nil, negatively acknowledged after NakDelay when it returns an
|
||||
// error, terminated when it is not an event at all. The consumer is the controller's to create; this
|
||||
// binds to it and never makes one. Runs until stopped.
|
||||
// ConsumeAs reads a module's durable consumer on EVENTS, and each one on a module's stream its
|
||||
// membership names (novox/hq ADR 0363 §8), and hands each event to deliver one at a time (novox/hq
|
||||
// ADR 0198): acknowledged when deliver returns nil, negatively acknowledged after NakDelay when it
|
||||
// returns an error, terminated when it is not an event at all. The consumers are the controller's to
|
||||
// create; this binds to them and never makes one. Runs until stopped.
|
||||
func (c *Conn) ConsumeAs(module string, deliver func(Envelope) error) (func(), error) {
|
||||
durable := ConsumerOf(c.node, module)
|
||||
js, err := jetstream.New(c.nc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
h := newHearing(c, js, module, deliver)
|
||||
// Bound by name, never created and never matched against a subject: the consumer and its
|
||||
// filters are the controller's (design 29 §3), exactly as the module's own runtime bound it.
|
||||
consumer, err := js.Consumer(ctx, EventsStream, durable)
|
||||
events, doing, err := h.bind(EventsStream, durable)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("binding %s's consumer %s: %w", module, durable, err)
|
||||
}
|
||||
reading, err := consumer.Consume(func(msg jetstream.Msg) {
|
||||
env, ok := envelopeOf(msg.Subject(), msg.Headers(), msg.Data())
|
||||
if !ok {
|
||||
// Unparseable: redelivering bytes no version of a handler can read is a loop.
|
||||
_ = msg.Term()
|
||||
return
|
||||
}
|
||||
// Kept alive while the bundle works on it, as a worker's work is (seat.go): the consumer's ack wait
|
||||
// (30 seconds) is for a runtime that died, not a handler that is slow — the runtime gives a handler
|
||||
// launch.EventTimeout. Without it an event handled longer than the ack wait was offered again while
|
||||
// it was handled, and counted toward max-deliver (novox/hq issue 387).
|
||||
done, stopped := make(chan struct{}), make(chan struct{})
|
||||
go func() {
|
||||
defer close(stopped)
|
||||
tick := time.NewTicker(EventHeartbeat)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-tick.C:
|
||||
_ = msg.InProgress()
|
||||
}
|
||||
}
|
||||
}()
|
||||
err := deliver(env)
|
||||
close(done)
|
||||
// No in-progress after the answer: one after a Nak would stretch its delay to the whole ack wait.
|
||||
<-stopped
|
||||
if err != nil {
|
||||
_ = msg.NakWithDelay(NakDelay)
|
||||
return
|
||||
}
|
||||
_ = msg.Ack()
|
||||
}, jetstream.PullMaxMessages(1))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s's consumer %s: %w", module, durable, err)
|
||||
return nil, fmt.Errorf("%s %s's consumer %s: %w", doing, module, durable, err)
|
||||
}
|
||||
// And every consumer on a module's stream its membership names (novox/hq ADR 0363 §8), followed as the
|
||||
// membership changes. A membership naming none binds nothing more: the module hears EVENTS alone.
|
||||
h.start()
|
||||
var once sync.Once
|
||||
return func() { once.Do(reading.Stop) }, nil
|
||||
return func() {
|
||||
once.Do(func() {
|
||||
h.stop()
|
||||
events.Stop()
|
||||
})
|
||||
}, nil
|
||||
}
|
||||
|
||||
// envelopeOf rebuilds the envelope a module sees from a delivered event: its key is the emitter and
|
||||
|
||||
Reference in New Issue
Block a user