indexer: the seed's steps fetch carries the entry's packages; the time a seat is unanswered is kept in the own-build state
Builds: design 47 G §704, A-66 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -87,7 +87,7 @@ func (ix *Indexer) SeedModulesFrom(ctx context.Context, defs map[string]seatdef.
|
||||
name := artifactName(a.Ref)
|
||||
sa := SeedArtifact{Name: name, Seat: seat}
|
||||
in := nodebuildgo.StepsInput{Module: e.Module, Artifact: nodebuildgo.DetectedStepsByModuleArtifactKey(e.Module, name), Fingerprint: e.Fp,
|
||||
Fetch: &nodebuildgo.Fetch{Repository: e.Repository, Commit: e.Commit, Dir: e.Path, Paths: namedPaths(e, name)}}
|
||||
Fetch: &nodebuildgo.Fetch{Repository: e.Repository, Commit: e.Commit, Dir: e.Path, Paths: namedPaths(e, name), Packages: maps.Clone(e.Packages)}}
|
||||
ans, err := steps(ctx, seat, in)
|
||||
switch {
|
||||
case err != nil:
|
||||
@@ -124,27 +124,20 @@ func (ix *Indexer) stepsFindings(ctx context.Context, answered map[string]bool,
|
||||
if ix.Findings == nil {
|
||||
return
|
||||
}
|
||||
ix.mu.Lock()
|
||||
if ix.stepsSince == nil {
|
||||
ix.stepsSince = map[string]time.Time{}
|
||||
}
|
||||
now := ix.now()
|
||||
var raise []string
|
||||
since := map[string]time.Time{}
|
||||
for seat := range unanswered {
|
||||
if _, ok := ix.stepsSince[seat]; !ok {
|
||||
ix.stepsSince[seat] = now
|
||||
}
|
||||
if now.Sub(ix.stepsSince[seat]) >= mcc.CallUnansweredAfter {
|
||||
since[seat] = ix.stepsUnansweredSince(ctx, seat, now)
|
||||
if now.Sub(since[seat]) >= mcc.CallUnansweredAfter {
|
||||
raise = append(raise, seat)
|
||||
}
|
||||
}
|
||||
for seat := range answered {
|
||||
if _, bad := unanswered[seat]; !bad {
|
||||
delete(ix.stepsSince, seat)
|
||||
ix.stepsAnswered(ctx, seat)
|
||||
}
|
||||
}
|
||||
since := maps.Clone(ix.stepsSince)
|
||||
ix.mu.Unlock()
|
||||
for _, seat := range slices.Sorted(slices.Values(raise)) {
|
||||
id := seat + "." + StepsVerb
|
||||
f := ix.observed(mcc.ScopeCall, id, mcc.KindCallUnanswered,
|
||||
@@ -176,3 +169,50 @@ func namedPaths(e meshcatalog.CatalogEntry, name string) []string {
|
||||
}
|
||||
return e.Inputs.Paths[ArtifactRefOf(e.Module, name)]
|
||||
}
|
||||
|
||||
// StepsSincePrefix opens the keys of the own-build state (OwnBuildState) that keep, by build seat, since when its steps
|
||||
// has not answered (design 47 G A-66): a restart keeps the time, and the finding is written at the next seed attempt past
|
||||
// five minutes.
|
||||
const StepsSincePrefix = "steps-since."
|
||||
|
||||
// stepsUnansweredSince is the time the seat first failed to answer, kept in the indexer's own state; the first failure
|
||||
// writes now. Where no state is kept it is held in memory.
|
||||
func (ix *Indexer) stepsUnansweredSince(ctx context.Context, seat string, now time.Time) time.Time {
|
||||
if ix.OwnBuildState == nil {
|
||||
ix.mu.Lock()
|
||||
defer ix.mu.Unlock()
|
||||
if ix.stepsSince == nil {
|
||||
ix.stepsSince = map[string]time.Time{}
|
||||
}
|
||||
if _, ok := ix.stepsSince[seat]; !ok {
|
||||
ix.stepsSince[seat] = now
|
||||
}
|
||||
return ix.stepsSince[seat]
|
||||
}
|
||||
key := StepsSincePrefix + seat
|
||||
if v, _, found, err := ix.OwnBuildState.Get(ctx, key); err == nil && found {
|
||||
if t, err := time.Parse(time.RFC3339Nano, string(v)); err == nil {
|
||||
return t
|
||||
}
|
||||
}
|
||||
if err := ix.OwnBuildState.Create(ctx, key, []byte(now.UTC().Format(time.RFC3339Nano))); err != nil {
|
||||
ix.logf("seed: the time since %s.%s is unanswered is not kept: %v", seat, StepsVerb, err)
|
||||
}
|
||||
return now
|
||||
}
|
||||
|
||||
// stepsAnswered forgets the time a seat that answers was unanswered since.
|
||||
func (ix *Indexer) stepsAnswered(ctx context.Context, seat string) {
|
||||
if ix.OwnBuildState == nil {
|
||||
ix.mu.Lock()
|
||||
delete(ix.stepsSince, seat)
|
||||
ix.mu.Unlock()
|
||||
return
|
||||
}
|
||||
key := StepsSincePrefix + seat
|
||||
if _, rev, found, err := ix.OwnBuildState.Get(ctx, key); err == nil && found {
|
||||
if err := ix.OwnBuildState.Delete(ctx, key, rev); err != nil {
|
||||
ix.logf("seed: the time since %s.%s is unanswered is not forgotten: %v", seat, StepsVerb, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,11 @@ func TestTheSeedAsksStepsOfTheBuildSeatsOfTheTrunkEntries(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
now := time.Date(2026, 11, 1, 3, 0, 0, 0, time.UTC)
|
||||
w := newFindings()
|
||||
ix := &Indexer{Findings: w, Revisions: newMemObjects(1 << 20), Now: func() time.Time { return now }}
|
||||
ix := &Indexer{Findings: w, OwnBuildState: newMem(), Revisions: newMemObjects(1 << 20), Now: func() time.Time { return now }}
|
||||
fp := "src2:" + strings.Repeat("a", 64)
|
||||
entries := map[string]meshcatalog.CatalogEntry{"avahi": {Module: "avahi", Repository: "novox/r", Commit: "c1", Path: "mods/avahi", Fp: fp, Manifest: map[string]any{},
|
||||
Artifacts: []meshcatalog.Artifact{{Ref: "avahi:program", Language: "go"}, {Ref: "avahi:image", Language: "go", Kind: "image"}},
|
||||
Packages: map[string]string{"git.novox.be/novox/mesh-sdk": "c9"},
|
||||
Inputs: &meshcatalog.EntryInputs{Paths: map[string][]string{"program": {"cmd"}}}}}
|
||||
rev, objects, err := CatalogRevisionOf(meshcatalog.CatalogRevision{Revision: 3, Kind: "trunk"}, entries)
|
||||
if err != nil {
|
||||
@@ -70,13 +71,14 @@ func TestTheSeedAsksStepsOfTheBuildSeatsOfTheTrunkEntries(t *testing.T) {
|
||||
t.Errorf("the image artifact is %+v", a)
|
||||
}
|
||||
in := got[0]
|
||||
if in.Module != "avahi" || in.Artifact != "avahi.program" || in.Fingerprint != fp || in.Fetch == nil || in.Fetch.Repository != "novox/r" || in.Fetch.Commit != "c1" || in.Fetch.Dir != "mods/avahi" || len(in.Fetch.Paths) != 1 {
|
||||
if in.Module != "avahi" || in.Artifact != "avahi.program" || in.Fingerprint != fp || in.Fetch == nil || in.Fetch.Repository != "novox/r" || in.Fetch.Commit != "c1" || in.Fetch.Dir != "mods/avahi" || len(in.Fetch.Paths) != 1 || in.Fetch.Packages["git.novox.be/novox/mesh-sdk"] != "c9" {
|
||||
t.Errorf("the steps input is %+v", in)
|
||||
}
|
||||
if len(w.Held()) != 0 {
|
||||
t.Fatal("a call unanswered for no time wrote its finding")
|
||||
}
|
||||
now = now.Add(5 * time.Minute)
|
||||
ix = &Indexer{Findings: w, OwnBuildState: ix.OwnBuildState, Revisions: ix.Revisions, Now: func() time.Time { return now }} // a restart keeps the time
|
||||
if _, err := ix.SeedModulesFrom(ctx, defs, call); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user