package bus import ( "context" "fmt" "time" "github.com/nats-io/nats.go/jetstream" "git.novox.be/novox/mesh-sdk/go/pernode" ) // A state its owner keeps one bucket per node (novox/hq ADR 0593 §6: each node's engine writes its own `composed-`, // and the provisioner, the assigner and the view read them all) is served to a bundle as one state. The rules (a key's // first part names its node's bucket, keys and a watch over every bucket of the prefix, a bucket created later heard by // the bus's creation advisory) are mesh-sdk's pernode package, written once for a process and for this runtime; this // file adapts its entries to the runtime's shapes. A reader is granted the listing of stream names // (pernode.ListSubject) and the creation advisory (pernode.CreatedAdvisory) beside each bucket's read. func perNodeRefusal(module, name, what string) error { return fmt.Errorf("%s's state %s is kept one bucket per node and read only: no %s of it as one state (novox/hq ADR 0593 §6)", module, name, what) } func (c *Conn) family(s StateIssued) pernode.Family { return pernode.Family{Read: s.Name, Prefix: s.Bucket, Bus: pernode.OverNATS(c.nc)} } func (c *Conn) perNodeGet(s StateIssued, key string) (*StateEntry, error) { ctx, cancel := context.WithTimeout(context.Background(), StateTimeout) defer cancel() e, err := c.family(s).Get(ctx, key) if err != nil || e == nil { return nil, err } return &StateEntry{Key: e.Key(), Value: valueOf(e.Value()), Revision: e.Revision()}, nil } func (c *Conn) perNodeKeys(s StateIssued) ([]string, error) { ctx, cancel := context.WithTimeout(context.Background(), StateTimeout) defer cancel() return c.family(s).Keys(ctx) } // perNodeWatch is StateWatch over every bucket of the family, and over a bucket created later. As in a single bucket's // watch, StateTimeout bounds the time without progress (issue 1726), and a value the bundle does not take is said and // the watch goes on. func (c *Conn) perNodeWatch(module string, s StateIssued, pattern string, deliver func(StateChange) error) (func(), error) { ctx, cancel := context.WithCancel(context.Background()) progress := make(chan struct{}, 1) handle := func(ch pernode.Change) error { select { case progress <- struct{}{}: default: } e := ch.Entry change := StateChange{State: s.Name, Key: e.Key(), Revision: e.Revision(), Current: ch.Current} switch e.Operation() { case jetstream.KeyValuePut: change.Op = "put" change.Value = valueOf(e.Value()) default: if ch.Current { return nil } change.Op = "delete" if e.Operation() == jetstream.KeyValuePurge { change.Op = "expire" } } if ctx.Err() != nil { return nil } if err := deliver(change); err != nil { c.Logf("[mesh-tools] %s did not take %s %s of %s: %v; its next change, or its next start, reads it again", module, change.Op, change.Key, s.Name, err) } return nil } done := make(chan error, 1) go func() { done <- c.family(s).Watch(ctx, pattern, handle) }() bound := time.NewTimer(StateTimeout) defer bound.Stop() for { select { case err := <-done: if err != nil { cancel() return nil, err } return cancel, nil case <-progress: if !bound.Stop() { select { case <-bound.C: default: } } bound.Reset(StateTimeout) case <-bound.C: cancel() return nil, fmt.Errorf("the current values of %s did not arrive in %s: none was handed over in that time", s.Name, StateTimeout) } } }