F-tools: the runner's views consumer is proven on the real bus; the heartbeat is asked of the bus only when a bundle wants it, with no race; a seat record says when the bus wrote it; the beat test counts ticks, not sleeps
Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -1098,6 +1098,17 @@ type Beat struct {
|
||||
// the bus's own heartbeat to the loop stopped arriving, the bus ended the loop or no longer answers. A Beat that is
|
||||
// not Missed follows the first answer after one that was. A nil beat says nothing.
|
||||
func (c *Conn) ConsumeAsBeating(module string, deliver func(Envelope) error, beat func(Beat)) (func(), error) {
|
||||
return c.ConsumeAsBeatingWhen(module, deliver, beat, nil)
|
||||
}
|
||||
|
||||
// tickHook, when set before a consume starts, is called after each idle heartbeat interval was handled (tests only,
|
||||
// through SetTickHook), so a test counts beats instead of sleeping. A consume reads it once, when it starts.
|
||||
var tickHook func()
|
||||
|
||||
// ConsumeAsBeatingWhen is ConsumeAsBeating that asks the bus about the consumer, and says anything, only while wanted
|
||||
// answers true (nil is always): a module whose children asked for no heartbeat costs the bus no consumer-info request.
|
||||
func (c *Conn) ConsumeAsBeatingWhen(module string, deliver func(Envelope) error, beat func(Beat), wanted func() bool) (func(), error) {
|
||||
asked := func() bool { return beat != nil && (wanted == nil || wanted()) }
|
||||
durable := ConsumerOf(c.node, module)
|
||||
js, err := jetstream.New(c.nc)
|
||||
if err != nil {
|
||||
@@ -1106,7 +1117,7 @@ func (c *Conn) ConsumeAsBeating(module string, deliver func(Envelope) error, bea
|
||||
var lastEvent atomic.Int64
|
||||
var missed atomic.Bool
|
||||
say := func(why string) {
|
||||
if beat != nil && missed.CompareAndSwap(false, true) {
|
||||
if asked() && missed.CompareAndSwap(false, true) {
|
||||
beat(Beat{At: time.Now().UTC(), Missed: true, Why: why})
|
||||
}
|
||||
}
|
||||
@@ -1130,6 +1141,30 @@ func (c *Conn) ConsumeAsBeating(module string, deliver func(Envelope) error, bea
|
||||
h.start()
|
||||
if beat != nil {
|
||||
every := IdleHeartbeat
|
||||
hook := tickHook
|
||||
tickOnce := func() {
|
||||
select {
|
||||
case <-events.Closed():
|
||||
say("the bus ended reading the consumer " + durable)
|
||||
return
|
||||
default:
|
||||
}
|
||||
if !asked() {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), every)
|
||||
_, err := js.Consumer(ctx, EventsStream, durable)
|
||||
cancel()
|
||||
if err != nil {
|
||||
say("the bus does not answer about the consumer " + durable + ": " + err.Error())
|
||||
return
|
||||
}
|
||||
if time.Since(time.Unix(0, lastEvent.Load())) < every {
|
||||
return // an event arrived in this interval: it was the word
|
||||
}
|
||||
missed.Store(false)
|
||||
beat(Beat{At: time.Now().UTC()})
|
||||
}
|
||||
go func() {
|
||||
tick := time.NewTicker(every)
|
||||
defer tick.Stop()
|
||||
@@ -1139,24 +1174,10 @@ func (c *Conn) ConsumeAsBeating(module string, deliver func(Envelope) error, bea
|
||||
return
|
||||
case <-tick.C:
|
||||
}
|
||||
select {
|
||||
case <-events.Closed():
|
||||
say("the bus ended reading the consumer " + durable)
|
||||
continue
|
||||
default:
|
||||
tickOnce()
|
||||
if hook != nil {
|
||||
hook()
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), every)
|
||||
_, err := js.Consumer(ctx, EventsStream, durable)
|
||||
cancel()
|
||||
if err != nil {
|
||||
say("the bus does not answer about the consumer " + durable + ": " + err.Error())
|
||||
continue
|
||||
}
|
||||
if time.Since(time.Unix(0, lastEvent.Load())) < every {
|
||||
continue // an event arrived in this interval: it was the word
|
||||
}
|
||||
missed.Store(false)
|
||||
beat(Beat{At: time.Now().UTC()})
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user