mesh/merge-gate out of date: judged against trunk f692fe7da89d, the trunk is now 238440d6f8c4; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk f692fe7da89d, the trunk is now 238440d6f8c4; judged again on a recheck or when it is merged
mesh/delivery delivered
ADR 0433 §2, §3: the watchman writes what stopped moving and the controller's self-check raises it, so the key and value live in the SDK (ADR 0429 §2).
66 lines
2.3 KiB
Go
66 lines
2.3 KiB
Go
package delivery
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// WatchmanModule is the watchman's module: its buckets are its own state, `<module>_<name>`, and the controller's
|
|
// broker grants it alone the bus's connect and disconnect events (novox/hq ADR 0431).
|
|
const WatchmanModule = "mesh-delivery-watchman"
|
|
|
|
// StuckBucket holds what stopped moving (novox/hq ADR 0433 §2): one key per key whose pulse expired, written by the
|
|
// watchman, read by the controller's self-check, which raises each as a condition until it says moved. No key of it
|
|
// is removed: a key that moves again is written as moved.
|
|
const StuckBucket = WatchmanModule + "_stuck"
|
|
|
|
// StuckKey is the key, in StuckBucket, of the key that stopped moving: `<bucket>.<key>`, formed as PulseKey forms a
|
|
// pulse's, so a node or part gone offline is `mesh-delivery-watchman_presence.<presence key>`.
|
|
func StuckKey(bucket, key string) (string, error) {
|
|
stuck, err := PulseKey(bucket, key)
|
|
if err != nil {
|
|
return "", fmt.Errorf("delivery: no stuck key: %w", err)
|
|
}
|
|
return stuck, nil
|
|
}
|
|
|
|
// ParseStuckKey is the inverse of StuckKey, splitting at the first dot.
|
|
func ParseStuckKey(stuckKey string) (bucket, key string, err error) {
|
|
bucket, key, err = ParsePulseKey(stuckKey)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("delivery: %q is not a stuck key: %w", stuckKey, err)
|
|
}
|
|
return bucket, key, nil
|
|
}
|
|
|
|
// Stuck is the value of a stuck key: the key that stopped moving, what it waited for in words, since when, and
|
|
// whether it has moved again since. The watchman writes it; the controller reads it.
|
|
type Stuck struct {
|
|
V int `json:"v"`
|
|
Bucket string `json:"bucket"`
|
|
Key string `json:"key"`
|
|
Waited string `json:"waited,omitempty"`
|
|
Since time.Time `json:"since"`
|
|
Moved bool `json:"moved,omitempty"`
|
|
}
|
|
|
|
// EncodeStuck is s as JSON at Version.
|
|
func EncodeStuck(s Stuck) ([]byte, error) {
|
|
s.V = Version
|
|
return json.Marshal(s)
|
|
}
|
|
|
|
// DecodeStuck reads a stuck value; it refuses an unknown version (ErrUnknownVersion).
|
|
func DecodeStuck(data []byte) (Stuck, error) {
|
|
var s Stuck
|
|
err := decode(data, &s)
|
|
return s, err
|
|
}
|
|
|
|
// SameStuck says whether two stuck values are the same, so a writer writes only when they differ.
|
|
func SameStuck(a, b Stuck) bool {
|
|
return a.V == b.V && a.Bucket == b.Bucket && a.Key == b.Key && a.Waited == b.Waited && a.Since.Equal(b.Since) &&
|
|
a.Moved == b.Moved
|
|
}
|