package stateobjects import ( "encoding/json" "errors" "fmt" "time" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/buscalls" ) // MessageLimit is the most a message of the bus carries when the sender does not know its connection's own // (`nc.MaxPayload()`), and MessageHeaderRoom what the headers and the subject take of it. const ( MessageLimit = 1 << 20 MessageHeaderRoom = 8 << 10 ) // Sender sends the body of a verb's reply or an event: a body over the per-message limit is put in the sender's store of // sent objects and its reference is sent instead (ADR 0666 §16). The store is the bus module's `_sent-objects`, // granted to the sender alone and read by the callers its seat definition names and the consumers that declare the event; // a sent object lives as long as that store's age, which is transport and not kept data. type Sender struct { // Objects is the sender's store of sent objects, and Store its name as a reader asks for it. Objects Objects Store string // Limit is the connection's largest message; zero is MessageLimit. Limit int // Log is a foundation part's old `state` section `sent-log`, with Node the key of this node's sends in it and Now // the clock (zero is time.Now): the sender keeps the sequence of its own sends there and ages the store from it at // each send (design 47 G §410, §330). A sender with no Log ages nothing: the bus expires a template store's objects. Log KV Node string Now func() time.Time } // The sent store of a foundation part keeps an object SentMaxAge, and a send deletes at most SentAgeAtMost of the // objects past it (design 47 G §330, §59). const ( SentLogState = "sent-log" SentAgeAtMost = 20 ) type sentEntry struct { Object string `json:"object"` At time.Time `json:"at"` } // record writes this send into the sequence of the sender's sends by compare-and-set, and deletes the objects of the // oldest sends that are past SentMaxAge, at most SentAgeAtMost; two sends at once lose none, the one that lost the // compare reading the sequence again. func (s Sender) record(object string) error { now := time.Now if s.Now != nil { now = s.Now } for { e, err := s.Log.Get(s.Node) if err != nil { return err } var log []sentEntry if e != nil { if err := json.Unmarshal(e.Value, &log); err != nil { return fmt.Errorf("the %s of %s: %w", SentLogState, s.Node, err) } } var aged []sentEntry for len(aged) < SentAgeAtMost && len(log) > 0 && now().Sub(log[0].At) > SentMaxAge { aged, log = append(aged, log[0]), log[1:] } log = append(log, sentEntry{object, now()}) raw, _ := json.Marshal(log) if e == nil { err = s.Log.Create(s.Node, raw) } else { err = s.Log.Update(s.Node, raw, e.Revision) } if errors.Is(err, ErrChanged) { continue } if err != nil { return err } for _, a := range aged { if err := s.Objects.Delete(a.Object); err != nil { return fmt.Errorf("aging the sent object %s: %w", a.Object, err) } } return nil } } // Send is what to send for body: the body itself when it fits, else the reference of the object it was put in as // `