|
|
|
@@ -97,17 +97,19 @@ func takeInConsumers(ctx context.Context, js jetstream.JetStream) []takeInConsum
|
|
|
|
|
var out []takeInConsumer
|
|
|
|
|
for _, bucket := range broker.TakeInBuckets {
|
|
|
|
|
c, err := js.CreateOrUpdateConsumer(ctx, "KV_"+bucket, takeInConfig(bucket))
|
|
|
|
|
o := takeInNotConsumedObservation(bucket, err)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Loud: a consumer whose deliver policy differs cannot be updated, and nothing of its bucket is taken in.
|
|
|
|
|
o := takeInStalledObservation(takeInBacklog{}, time.Now())
|
|
|
|
|
o.Summary = fmt.Sprintf("the take-in's consumer on %s is not made, so its keys are not taken in until the "+
|
|
|
|
|
"next start: %v", bucket, err)
|
|
|
|
|
doneKeysSay("done keys: " + o.Summary)
|
|
|
|
|
if keeper := conditionsFrom; keeper != nil {
|
|
|
|
|
_, _ = keeper.Observe(ctx, o)
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// Made: a condition a controller before this one raised for it is cleared (a restart that works clears it).
|
|
|
|
|
if keeper := conditionsFrom; keeper != nil {
|
|
|
|
|
_, _ = keeper.Clear(ctx, o.Key(), "the take-in's consumer on "+bucket+" is made")
|
|
|
|
|
}
|
|
|
|
|
out = append(out, takeInConsumer{bucket: bucket, consumer: c})
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
@@ -127,9 +129,9 @@ func watchDoneKeys(ctx context.Context, open *stores) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
in := make(chan takeInMsg)
|
|
|
|
|
// acked counts the keys this process acknowledged: what tells a take-in that moves from one whose keys are only
|
|
|
|
|
// delivered again (the stall watch, ADR 0544 §6).
|
|
|
|
|
acked := &atomic.Uint64{}
|
|
|
|
|
// moving counts the keys handed to the loop and the keys it settled (acknowledged or refused, each as the bus took
|
|
|
|
|
// it): what tells a take-in that moves from one that holds work and does nothing (the stall watch, ADR 0544 §6).
|
|
|
|
|
moving := &takeInMoving{}
|
|
|
|
|
read := make([]jetstream.Consumer, 0, len(consumers))
|
|
|
|
|
for _, c := range consumers {
|
|
|
|
|
bucket := c.bucket
|
|
|
|
@@ -141,7 +143,7 @@ func watchDoneKeys(ctx context.Context, open *stores) {
|
|
|
|
|
}
|
|
|
|
|
goServing(ctx, func() {
|
|
|
|
|
<-ctx.Done()
|
|
|
|
|
it.Stop()
|
|
|
|
|
it.Drain() // the keys it holds are handed out and refused at once below, not lost to their ack wait
|
|
|
|
|
})
|
|
|
|
|
goServing(ctx, func() {
|
|
|
|
|
for {
|
|
|
|
@@ -158,16 +160,26 @@ func watchDoneKeys(ctx context.Context, open *stores) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
msg := takeInMsg{delivered: d, ack: func() {
|
|
|
|
|
if m.Ack() == nil {
|
|
|
|
|
acked.Add(1)
|
|
|
|
|
// Counted only when the bus took it: at a first start it refuses the acks until the bus node is composed.
|
|
|
|
|
if m.DoubleAck(ctx) == nil {
|
|
|
|
|
moving.settled.Add(1)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
nak: func(delay time.Duration) { _ = m.NakWithDelay(delay) }}
|
|
|
|
|
nak: func(delay time.Duration) {
|
|
|
|
|
if m.NakWithDelay(delay) == nil {
|
|
|
|
|
moving.settled.Add(1)
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
|
_ = m.Nak()
|
|
|
|
|
continue // drained: each key it held, refused at once
|
|
|
|
|
}
|
|
|
|
|
select {
|
|
|
|
|
case in <- msg:
|
|
|
|
|
moving.handed.Add(1)
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
_ = m.Nak() // delivered again at once to the controller after this one, not after the ack wait
|
|
|
|
|
return
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
@@ -175,7 +187,7 @@ func watchDoneKeys(ctx context.Context, open *stores) {
|
|
|
|
|
backlog := func(ctx context.Context) (takeInBacklog, error) { return backlogOf(ctx, read) }
|
|
|
|
|
steps := liveTakeInSteps(open, keys, keys.JS)
|
|
|
|
|
goServing(ctx, func() { takeInLoop(ctx, in, steps, backlog) })
|
|
|
|
|
goServing(ctx, func() { watchTakeInStall(ctx, backlog, acked.Load) })
|
|
|
|
|
goServing(ctx, func() { watchTakeInStall(ctx, backlog, moving) })
|
|
|
|
|
goServing(ctx, func() {
|
|
|
|
|
<-ctx.Done()
|
|
|
|
|
closeKeys()
|
|
|
|
@@ -275,7 +287,11 @@ func (t *takeInState) take(ctx context.Context, steps takeInSteps, batch []takeI
|
|
|
|
|
}
|
|
|
|
|
for _, m := range batch {
|
|
|
|
|
if verdicts[m.module] == refused {
|
|
|
|
|
m.nak(refusalDelay(m.deliveries))
|
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
|
m.nak(0) // cut by the stop: the next controller takes it at once
|
|
|
|
|
} else {
|
|
|
|
|
m.nak(refusalDelay(m.deliveries))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
m.ack()
|
|
|
|
|
}
|
|
|
|
@@ -326,45 +342,70 @@ func readTakeInBacklog(ctx context.Context) (takeInBacklog, error) {
|
|
|
|
|
return backlogOf(ctx, consumers)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// kindTakeInStalled is the condition of a take-in whose backlog stood still (ADR 0544 §6).
|
|
|
|
|
const kindTakeInStalled = "take-in-stalled"
|
|
|
|
|
// kindTakeInStalled is the condition of a take-in that holds work and settles none (ADR 0544 §6); kindTakeInNotConsumed
|
|
|
|
|
// that of a bucket whose consumer could not be made.
|
|
|
|
|
const (
|
|
|
|
|
kindTakeInStalled = "take-in-stalled"
|
|
|
|
|
kindTakeInNotConsumed = "take-in-not-consumed"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func takeInStalledObservation(b takeInBacklog, since time.Time) conditions.Observation {
|
|
|
|
|
return conditions.Observation{Scope: conditions.ScopeCore, ID: "take-in", Token: kindTakeInStalled,
|
|
|
|
|
Kind: kindTakeInStalled, Severity: conditions.Urgent, Resolver: conditions.ResolverOperator, Source: "done keys",
|
|
|
|
|
Summary: fmt.Sprintf("the take-in of done keys has %d key(s) waiting and has taken none in since %s",
|
|
|
|
|
Summary: fmt.Sprintf("the take-in of done keys has %d key(s) waiting and has settled none since %s",
|
|
|
|
|
b.Total(), since.UTC().Format(time.RFC3339))}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// stallWatch judges the backlog read at each look: stalled once it stood above zero, with no key acknowledged, for
|
|
|
|
|
// takeInStall. Acknowledgements, not deliveries: a key refused and delivered again moves the deliveries and takes
|
|
|
|
|
// nothing in (the review of ADR 0544).
|
|
|
|
|
type stallWatch struct {
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
acked uint64
|
|
|
|
|
movedAt time.Time
|
|
|
|
|
stalled bool
|
|
|
|
|
// takeInNotConsumedObservation is the condition of bucket's consumer not made, with its own id, so it is raised and
|
|
|
|
|
// cleared apart from a stall and from the other bucket's.
|
|
|
|
|
func takeInNotConsumedObservation(bucket string, err error) conditions.Observation {
|
|
|
|
|
summary := "the take-in's consumer on " + bucket + " is made"
|
|
|
|
|
if err != nil {
|
|
|
|
|
summary = fmt.Sprintf("the take-in's consumer on %s is not made, so its keys are not taken in until the next "+
|
|
|
|
|
"start: %v", bucket, err)
|
|
|
|
|
}
|
|
|
|
|
return conditions.Observation{Scope: conditions.ScopeCore, ID: "take-in-" + bucket, Token: kindTakeInNotConsumed,
|
|
|
|
|
Kind: kindTakeInNotConsumed, Severity: conditions.Urgent, Resolver: conditions.ResolverOperator,
|
|
|
|
|
Source: "done keys", Summary: summary}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// look takes a reading at now, with how many keys this process has acknowledged, and answers whether the take-in
|
|
|
|
|
// stalls and whether that changed.
|
|
|
|
|
func (w *stallWatch) look(b takeInBacklog, acked uint64, now time.Time) (stalled, changed bool) {
|
|
|
|
|
// takeInMoving counts the keys handed to the take-in loop and the keys it settled.
|
|
|
|
|
type takeInMoving struct {
|
|
|
|
|
handed, settled atomic.Uint64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// stallWatch judges each look: stalled once there was work to do (keys not delivered yet, or keys handed to the loop
|
|
|
|
|
// and not settled) and nothing settled for takeInStall. A refused key waiting for its delay is no work of the loop's:
|
|
|
|
|
// with nothing else, a quiet mesh is not a stall; a loop that hangs on its batch is (the review of ADR 0544). Its first
|
|
|
|
|
// look always says it changed, so a condition a controller before this one raised is cleared once this one moves.
|
|
|
|
|
type stallWatch struct {
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
settled uint64
|
|
|
|
|
movedAt time.Time
|
|
|
|
|
stalled bool
|
|
|
|
|
known bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// look takes a reading at now: the backlog, and how many keys were handed and settled; it answers whether the take-in
|
|
|
|
|
// stalls and whether that changed (always on the first look).
|
|
|
|
|
func (w *stallWatch) look(b takeInBacklog, handed, settled uint64, now time.Time) (stalled, changed bool) {
|
|
|
|
|
w.mu.Lock()
|
|
|
|
|
defer w.mu.Unlock()
|
|
|
|
|
if b.Total() == 0 || acked != w.acked || w.movedAt.IsZero() {
|
|
|
|
|
work := b.Pending > 0 || handed > settled
|
|
|
|
|
if !work || settled != w.settled || w.movedAt.IsZero() {
|
|
|
|
|
w.movedAt = now
|
|
|
|
|
}
|
|
|
|
|
w.acked = acked
|
|
|
|
|
nowStalled := b.Total() > 0 && now.Sub(w.movedAt) >= takeInStall
|
|
|
|
|
changed = nowStalled != w.stalled
|
|
|
|
|
w.stalled = nowStalled
|
|
|
|
|
w.settled = settled
|
|
|
|
|
nowStalled := work && now.Sub(w.movedAt) >= takeInStall
|
|
|
|
|
changed = !w.known || nowStalled != w.stalled
|
|
|
|
|
w.stalled, w.known = nowStalled, true
|
|
|
|
|
return nowStalled, changed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// watchTakeInStall reads the backlog every minute and raises take-in-stalled while it stands with nothing acknowledged,
|
|
|
|
|
// clearing it when it moves: a watchdog's timer, as research 072 §2 keeps them until the health part leaves the
|
|
|
|
|
// controller.
|
|
|
|
|
func watchTakeInStall(ctx context.Context, backlog func(context.Context) (takeInBacklog, error), acked func() uint64) {
|
|
|
|
|
// watchTakeInStall reads the backlog every minute and raises take-in-stalled while the take-in holds work and settles
|
|
|
|
|
// none, clearing it when it moves, and at its first look when it does not stall: a watchdog's timer, as research 072 §2
|
|
|
|
|
// keeps them until the health part leaves the controller.
|
|
|
|
|
func watchTakeInStall(ctx context.Context, backlog func(context.Context) (takeInBacklog, error), moving *takeInMoving) {
|
|
|
|
|
w := &stallWatch{}
|
|
|
|
|
tick := time.NewTicker(time.Minute)
|
|
|
|
|
defer tick.Stop()
|
|
|
|
@@ -378,7 +419,7 @@ func watchTakeInStall(ctx context.Context, backlog func(context.Context) (takeIn
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
stalled, changed := w.look(b, acked(), time.Now())
|
|
|
|
|
stalled, changed := w.look(b, moving.handed.Load(), moving.settled.Load(), time.Now())
|
|
|
|
|
keeper := conditionsFrom
|
|
|
|
|
if !changed || keeper == nil {
|
|
|
|
|
continue
|
|
|
|
@@ -391,7 +432,7 @@ func watchTakeInStall(ctx context.Context, backlog func(context.Context) (takeIn
|
|
|
|
|
doneKeysSay("done keys: " + o.Summary)
|
|
|
|
|
_, _ = keeper.Observe(ctx, o)
|
|
|
|
|
} else {
|
|
|
|
|
_, _ = keeper.Clear(ctx, o.Key(), "the take-in moves again")
|
|
|
|
|
_, _ = keeper.Clear(ctx, o.Key(), "the take-in moves")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|