Keep every composition the engine applied on the machine, so a module whose composition the bus no longer keeps runs as applied and is never composed as nothing (issue 774)
On novox on 2026-10-29 at 07:54 UTC the kept declaration was retired and the node's own composition was in no bucket.
The composer's first pass reads what runs before the agent reads any have (the agent waits for the composer's caught,
sent after that pass), so the node's own was composed with its folded modules running nothing, and the bus server and
four held modules were removed.
- Quick fix: read the node's own have in loadHad too. It still waits for the composer's first pass, and still finds
nothing when the bucket no longer keeps the composition the have names.
- Thorough fix, chosen: the engine keeps each composition it applied (the node's own too) in `applied/` beside its
state, 0600. The agent reads it at Run's start, before any watch, as what runs ahead of the bucket and the kept
declaration. A have naming another running composition wins. An apply keeps; a removal forgets.
Test: a case in TestEverythingIsWhatTheAgentRunsAndWhatIsKeptForTheRest (no new test function). It fails on L3's
engine 4f9227a (0 compositions at the first own composition) and passes here.
This commit is contained in:
@@ -156,6 +156,22 @@ type Env struct {
|
||||
// Data of its own. A composition says Data, so without it a data module from the kept declaration is judged to move
|
||||
// its data against its first composition after a restart (mongodb and mssql on novox, issue 924). Nil reads none.
|
||||
HoldsData func(ctx context.Context, module string) (data, known bool)
|
||||
// Applied keeps every composition the agent applied, by module (the node's own under NodeModule), beside the engine's
|
||||
// state on the machine: read at the start before anything is watched, so what runs is known before the node composes
|
||||
// its own for the first time, whatever the bucket and the kept declaration still hold (issue 774: on 2026-10-29 the
|
||||
// bus node, its kept declaration retired and its own composition in no bucket, composed its own as if its folded
|
||||
// modules ran nothing and removed the bus server and four held modules). Nil keeps none.
|
||||
Applied AppliedStore
|
||||
}
|
||||
|
||||
// AppliedStore is where the engine keeps the compositions it applied (Env.Applied): each module's last, as its bytes.
|
||||
type AppliedStore interface {
|
||||
// Load is every composition kept, by module; a store that cannot be read says so and loads none.
|
||||
Load() (map[string][]byte, error)
|
||||
// Keep keeps the composition the module was applied from, in place of the one before.
|
||||
Keep(module string, raw []byte) error
|
||||
// Drop forgets the module's, once it is removed.
|
||||
Drop(module string) error
|
||||
}
|
||||
|
||||
// transientFor is how many transient retries say the phase applying before the have says failed: past them the bus has
|
||||
@@ -248,6 +264,9 @@ func New(env Env) *Agent {
|
||||
// before any module's, then each change as it comes.
|
||||
func (a *Agent) Run(ctx context.Context) error {
|
||||
defer close(a.stopped)
|
||||
// What this engine last applied, before any composition is watched: the node composing its own reads what runs
|
||||
// (Everything) from its first pass on, and that pass comes before the haves are read below (issue 774).
|
||||
a.loadApplied(ctx)
|
||||
wants, err := a.env.Wants.Watch(ctx, "want."+a.env.Node+".>")
|
||||
if err != nil {
|
||||
return fmt.Errorf("the agent cannot watch its wants: %w", err)
|
||||
@@ -861,6 +880,10 @@ func (a *Agent) loadHad(ctx context.Context) {
|
||||
continue
|
||||
}
|
||||
a.mu.Lock()
|
||||
if c, kept := a.had[m]; kept && c.Dig == h.Composed {
|
||||
a.mu.Unlock()
|
||||
continue // what this engine applied and kept itself (loadApplied)
|
||||
}
|
||||
c, found := a.compositionFor(m, h.FP)
|
||||
a.mu.Unlock()
|
||||
if !found || c.Err != nil || c.Dig != h.Composed {
|
||||
@@ -869,6 +892,8 @@ func (a *Agent) loadHad(ctx context.Context) {
|
||||
a.mu.Lock()
|
||||
if found {
|
||||
a.had[m] = c
|
||||
} else if _, applied := a.had[m]; applied {
|
||||
// Kept as this engine applied it (loadApplied), never composed as nothing.
|
||||
} else if _, kept := a.keptHas(m); kept {
|
||||
a.sayOnce("had:"+m, fmt.Sprintf("%s's have names the composition %s, which the bucket no longer keeps: it runs "+
|
||||
"from the kept declaration until it is applied or taken", m, short(h.Composed)))
|
||||
@@ -877,6 +902,76 @@ func (a *Agent) loadHad(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// loadApplied takes, once at the start, what this engine last applied of each module as what runs of it, ahead of the
|
||||
// bucket and the kept declaration (Env.Applied, issue 774): it is what the machine holds, whatever the bus still keeps.
|
||||
// One whose have names another composition that runs (applied since by another engine) is left to the have; one whose
|
||||
// have cannot be read, or that has none, is kept as applied, so a module is never composed as running nothing.
|
||||
func (a *Agent) loadApplied(ctx context.Context) {
|
||||
if a.env.Applied == nil {
|
||||
return
|
||||
}
|
||||
kept, err := a.env.Applied.Load()
|
||||
if err != nil {
|
||||
a.env.Say(fmt.Sprintf("the compositions this engine applied cannot be read, so what runs is read from the haves alone: %v", err))
|
||||
}
|
||||
names := make([]string, 0, len(kept))
|
||||
for m := range kept {
|
||||
names = append(names, m)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, m := range names {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
c, err := delivery.DecodeComposed(kept[m])
|
||||
var dig string
|
||||
if err == nil {
|
||||
dig, err = delivery.ComposedDigestOf(c)
|
||||
}
|
||||
if err != nil {
|
||||
a.env.Say(fmt.Sprintf("the composition this engine kept of %s cannot be read, so what runs of it is read from its have: %v", m, err))
|
||||
continue
|
||||
}
|
||||
h, found, unread := a.readHave(ctx, m)
|
||||
if found && h.Composed != "" && h.Composed != dig && haveRuns(h) {
|
||||
a.env.Say(fmt.Sprintf("%s's have names the composition %s, not %s this engine applied last: what runs of it is read from its have",
|
||||
m, short(h.Composed), short(dig)))
|
||||
continue
|
||||
}
|
||||
if m == NodeModule {
|
||||
c.Module, c.FP = "", ""
|
||||
}
|
||||
a.mu.Lock()
|
||||
a.had[m] = compNow{Raw: kept[m], C: c, Dig: dig}
|
||||
a.mu.Unlock()
|
||||
switch {
|
||||
case unread:
|
||||
a.env.Say(fmt.Sprintf("%s's have cannot be read: it runs as this engine applied it, %s", m, short(dig)))
|
||||
case !found:
|
||||
a.env.Say(fmt.Sprintf("%s has no have: it runs as this engine applied it, %s", m, short(dig)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// keepApplied keeps the composition a module was applied from (Env.Applied); one that cannot be kept is said, and the
|
||||
// apply stands.
|
||||
func (a *Agent) keepApplied(module string, comp compNow) {
|
||||
if a.env.Applied == nil {
|
||||
return
|
||||
}
|
||||
raw := comp.Raw
|
||||
if len(raw) == 0 {
|
||||
var err error
|
||||
if raw, err = delivery.EncodeComposed(comp.C); err != nil {
|
||||
a.env.Say(fmt.Sprintf("the composition %s was applied from cannot be kept: %v", module, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := a.env.Applied.Keep(module, raw); err != nil {
|
||||
a.env.Say(fmt.Sprintf("the composition %s was applied from cannot be kept: %v", module, err))
|
||||
}
|
||||
}
|
||||
|
||||
// haveRuns says a have names what runs, in the phases baseline takes as running: running (or empty), held for a failed
|
||||
// backup, or a composer's mark over what still runs. A have backing up, applying, checking, failed or waiting names a
|
||||
// composition not applied, or not yet (issue 924, review of 031e106).
|
||||
@@ -1327,6 +1422,7 @@ func (a *Agent) apply(ctx context.Context, module string, w wantNow, comp compNo
|
||||
a.running[module] = delivery.Have{FP: fp, Composed: dig, Phase: delivery.PhaseRunning}
|
||||
a.applied[module] = comp
|
||||
a.mu.Unlock()
|
||||
a.keepApplied(module, comp)
|
||||
if a.env.Settled != nil {
|
||||
a.env.Settled(module)
|
||||
}
|
||||
@@ -1356,8 +1452,14 @@ func (a *Agent) remove(ctx context.Context, module string, w wantNow) {
|
||||
return
|
||||
}
|
||||
delete(a.attempt, module)
|
||||
delete(a.had, module)
|
||||
a.running[module] = delivery.Have{Phase: delivery.PhaseRemoved}
|
||||
a.mu.Unlock()
|
||||
if a.env.Applied != nil {
|
||||
if err := a.env.Applied.Drop(module); err != nil {
|
||||
a.env.Say(fmt.Sprintf("the composition %s was applied from cannot be forgotten now it is removed: %v", module, err))
|
||||
}
|
||||
}
|
||||
if a.env.Settled != nil {
|
||||
a.env.Settled(module)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user