Files
mesh-delivery/modules/mesh-delivery-teller/cmd/mesh-delivery-teller/ends.go
T
jschoubben 17abe6af32
mesh/delivery merged: source.main moved to novox/mesh-delivery@af0fd83aec09
mesh/delivery-group group fix/merge-ask-ends-on-merge merged: every member is merged
mesh/check checking 17abe6af32ce on af0fd83aec09 | novox/mesh-delivery#124 in group fix/merge-ask-ends-on-merge
Teller: teller_end_ask ends one stale ask by hand, so an ask whose key is never taken away cannot hold the router's room for a day (issue 774)
On 2026-10-31 two merge asks of pull requests merged by another path held two of
the router's three places for the teller and a placement ask was refused for want
of room. The gate's fix ends such asks by itself; this tool is the recovery for any
ask that still outlives its question: it cancels the open ask at the router, or
drops one waiting for room, by its router id or its key, and logs who and why. The
tools bundle asks through the teller's new state ends, as indexer_take_in does.
2026-10-31 02:13:39 +01:00

93 lines
3.5 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"slices"
"time"
"github.com/nats-io/nats.go/jetstream"
"mesh-delivery-teller/internal/ends"
)
// EndAsk ends one ask of the teller's by hand (the tool teller_end_ask, issue 774): an ask open at the router is cancelled
// there and marked ended here, and one waiting for room is dropped from the wait, so it is asked no more under that router
// id. The ask is named by its router id or by the key of the gate's or the assigner's asks it was asked for (then its latest
// asking). An ask that is neither open nor waiting is said so and left: it holds no place at the router. The gate's or the
// assigner's key is not the teller's to take away: a later asking of it, under a new router id, is asked as any.
func (t *Teller) EndAsk(ask, why, by string) ends.Result {
t.mu.Lock()
defer t.mu.Unlock()
id, key := ask, ""
if s, rec, err := t.seenOf(ask); err != nil {
return ends.Result{Says: err.Error()}
} else if rec != nil {
key = s.Key
} else if g, grec, err := t.seenAt(gateRecordKey(ask)); err != nil {
return ends.Result{Says: err.Error()}
} else if grec != nil && g.ID != "" {
id, key = g.ID, ask
} else {
return ends.Result{Says: "the teller holds no ask under " + ask + ": name its router id, as operator-channel's asks show it, or the key of the gate's or the assigner's asks"}
}
r := ends.Result{ID: id, Key: key}
waited := false
if ws, rec, err := t.waiting(); err == nil && rec != nil {
if i := slices.IndexFunc(ws, func(x waitingAsk) bool { return x.ID == id }); i >= 0 {
if t.keepWaiting(slices.Delete(ws, i, i+1), rec) != nil {
r.Says = "the ask " + id + " waits for room, and the wait could not be written: it is not ended"
return r
}
waited = true
}
}
k, _, err := t.keptOf(id)
if err != nil {
r.Says = err.Error()
return r
}
if !open(k) {
r.Ended = waited
if waited {
r.Says = "the ask " + id + " waited for room at the router: it is dropped from the wait and asked no more"
} else {
r.Says = "the ask " + id + " is not open (answered, lapsed or ended already): it holds no place at the router, and nothing is done"
}
t.logf("%s: %s, by %s: %s", key, r.Says, by, why)
return r
}
t.cancelOpen(key, id)
if k, _, err := t.keptOf(id); err != nil || open(k) {
r.Says = "the ask " + id + " could not be cancelled at the router (see the teller's journal): it stays open"
return r
}
r.Ended = true
r.Says = "the ask " + id + " is cancelled at the router, which frees its place; the next ask waiting for room is asked when the router says it ended"
t.logf("%s: the ask %s is ended by hand, by %s: %s", key, id, by, why)
return r
}
// endAsked answers one value of the state ends: one not yet answered is carried out and written back, by compare-and-set at
// its revision, with its result. One answered already (the teller's own write) is left.
func endAsked(ctx context.Context, t *Teller, kv jetstream.KeyValue, key string, rev uint64, value []byte) {
e, err := ends.Decode(value)
if err != nil {
t.logf("%s.%s holds what is not an end, left: %v", ends.State, key, err)
return
}
if e.Done() {
return
}
r := t.EndAsk(e.Ask, e.Why, e.By)
e.Result, e.Answered = &r, time.Now().UTC()
data, err := json.Marshal(e)
if err != nil {
return
}
if _, err := kv.Update(ctx, key, data, rev); err != nil && !errors.Is(err, jetstream.ErrKeyExists) && !errors.Is(err, jetstream.ErrKeyRevisionMismatch) {
t.logf("%s.%s: the answer could not be written: %v", ends.State, key, err)
}
}