Files
mesh-sdk/go/delivery/placed.go
T
jschoubben 4c54aec972 Keep the seat mesh-placement's state as one key per module and node, so a node watches its own keys and a change writes only the modules it moves (issue 774, research 072 row 37)
The node key <node>.node is a write's commit: it names each placed module with its value's digest, the
module keys carry the value they replace until it lands, so a reader never composes half a write. Every
reader goes through PlacementKeys/PlacementsOf, which read the legacy placement.<node> too while the
change is delivered.
2026-10-31 09:21:04 +01:00

158 lines
5.1 KiB
Go

package delivery
import (
"fmt"
"maps"
"slices"
"sync"
"time"
)
// Placed is where the placement (the seat mesh-placement's state `placement`, held by the assigner; ADR 0567, ADR 0549 §3) places each module, and since when:
// the one reader of that state for every part that asks "does module stand on node" (issue 774, design 47's third
// milestone), in place of each part reading the controller's assignments (`mesh-controller_assignments`, which goes
// with the controller's lease, ADR 0549 §6).
//
// Its input is the state's revisions in the order the bus wrote them, as a watch with the history (jetstream's
// IncludeHistory) hands them: Put takes each, in either form of the state's keys (placement_keys.go): a node's node key
// names its modules (a module key says nothing of which stand), and its legacy key `placement.<node>` does until a node
// key of it is heard. A module stands since the revision of its node's key that first named it
// in an unbroken run. A module in the first revision heard of a key stands since the zero time: that revision is the
// key's first (the seed of ADR 0549 §4 placed what already ran there) or the oldest the bus still keeps
// (PlacementHistory), and either way the module may have run there before it. A key's value that does not read places
// nothing on its node, and Put says why.
//
// Safe for use from a watch and from a reader at once.
type Placed struct {
mu sync.Mutex
since map[string]map[string]time.Time // node -> module -> since when it stands
heard map[string]bool // the nodes whose key was heard at all
// newForm are the nodes whose node key `<node>.node` was heard: their legacy key `placement.<node>` is no longer
// read (placement_keys.go).
newForm map[string]bool
}
// NewPlaced is a Placed that holds no placement yet.
func NewPlaced() *Placed {
return &Placed{since: map[string]map[string]time.Time{}, heard: map[string]bool{}, newForm: map[string]bool{}}
}
// Put takes one revision of a key of the state `placement` (its value, whether it is a delete, and when the bus wrote
// it). It answers the node the key is of and the modules whose standing on it changed (placed or removed), in name
// order, and an error for a key or a value that does not read (the node then places nothing).
func (p *Placed) Put(key string, value []byte, deleted bool, at time.Time) (node string, changed []string, err error) {
node, _, kind, err := ParsePlacementKey(key)
if err != nil {
return "", nil, err
}
if kind == PlacementModuleKind {
return node, nil, nil // which modules stand is the node key's to say
}
p.mu.Lock()
if kind == PlacementLegacyKind && p.newForm[node] {
p.mu.Unlock()
return node, nil, nil // the node is read from its node key once one was heard
}
if kind == PlacementNodeKind {
p.newForm[node] = true
}
p.mu.Unlock()
now := map[string]bool{}
if !deleted {
var modules []string
var derr error
var named string
if kind == PlacementNodeKind {
var n PlacementNode
n, derr = DecodePlacementNode(value)
named = n.Node
for _, r := range n.Modules {
modules = append(modules, r.Module)
}
} else {
var pl Placement
pl, derr = DecodePlacement(value)
named, modules = pl.Node, pl.Assigned()
}
switch {
case derr != nil:
err = fmt.Errorf("the placement of %s does not read, so it places nothing there: %w", node, derr)
case named != node:
err = fmt.Errorf("the placement under %s names the node %q, so it places nothing there", key, named)
default:
for _, m := range modules {
now[m] = true
}
}
}
p.mu.Lock()
defer p.mu.Unlock()
if !p.heard[node] {
p.heard[node] = true
at = time.Time{}
}
was := p.since[node]
next := map[string]time.Time{}
for m := range now {
if s, ok := was[m]; ok {
next[m] = s
} else {
next[m] = at
changed = append(changed, m)
}
}
for m := range was {
if !now[m] {
changed = append(changed, m)
}
}
if len(next) == 0 {
delete(p.since, node)
} else {
p.since[node] = next
}
slices.Sort(changed)
return node, changed, err
}
// Seeded says some node's placement is held: before the assigner's adopt writes the first placement nothing is placed,
// and a reader keeps what it did before (ADR 0549 §5).
func (p *Placed) Seeded() bool {
p.mu.Lock()
defer p.mu.Unlock()
return len(p.since) > 0
}
// Since says whether module stands on node, and since when (the zero time: since before the revisions kept).
func (p *Placed) Since(node, module string) (time.Time, bool) {
p.mu.Lock()
defer p.mu.Unlock()
s, ok := p.since[node][module]
return s, ok
}
// PlacedNodes is every node module is placed on, in name order.
func (p *Placed) PlacedNodes(module string) []string {
p.mu.Lock()
defer p.mu.Unlock()
var out []string
for node, mods := range p.since {
if _, ok := mods[module]; ok {
out = append(out, node)
}
}
slices.Sort(out)
return out
}
// ByNode is, by node, the modules placed on it in name order.
func (p *Placed) ByNode() map[string][]string {
p.mu.Lock()
defer p.mu.Unlock()
out := make(map[string][]string, len(p.since))
for node, mods := range p.since {
out[node] = slices.Sorted(maps.Keys(mods))
}
return out
}