Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/source.go
T
jschoubben 907e092abc
mesh/delivery merged: source.main moved to novox/mesh-delivery@b3a4b09f5b6f
mesh/check being carried to 907e092abc45 on b3a4b09f5b6f
mesh-delivery-indexer reads unread registered modules again in one background run, so a forge timeout holds no merged message or source revision past its ack wait (issue 1945, review round 1)
The take-in tool's bound now says the take-in is still running and where its answer will be.
2026-10-26 14:11:12 +01:00

94 lines
3.5 KiB
Go

package index
import (
"context"
"fmt"
"maps"
"slices"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// The gate's source revision (novox/hq ADR 0437 §5, ADR 0450 §1): source.main names every repository's merged head, and its
// revision in the bucket is the mesh's source revision. The indexer keeps the latest it has seen. A module is stamped
// with it (Module.Src) when the head the source names for its repository is the merge commit the module's fingerprint
// was computed at: from the modules bucket when the merge was heard first, and when the merge is heard after the source.
// sourceNow is the latest source revision seen.
type sourceNow struct {
rev uint64
heads map[string]string
}
// srcOf is the source revision that names this merge commit as the repository's head, or 0 when the latest source seen
// names another head or none has been seen.
func (ix *Indexer) srcOf(repository, merge string) uint64 {
ix.mu.Lock()
defer ix.mu.Unlock()
if ix.source.rev != 0 && merge != "" && ix.source.heads[repository] == merge {
return ix.source.rev
}
return 0
}
// ApplySource takes a revision of the gate's source.main in. An older revision than the latest seen is left. For each
// repository the source names, each module built from it whose fingerprint was computed at the head the source names, and
// that carries no Src yet, is written again with Src the revision (once: a later revision leaves it); a module computed at another commit is left (a merge heard later is stamped
// when it is written, applyMerged), and a repository none of the indexer's modules is built from writes nothing. The
// modules of a repository are those the modules bucket holds of it (ADR 0501 clause 6).
func (ix *Indexer) ApplySource(ctx context.Context, rev uint64, s delivery.Source) error {
ix.mu.Lock()
if rev < ix.source.rev {
ix.mu.Unlock()
return nil
}
ix.source = sourceNow{rev: rev, heads: maps.Clone(s.Heads)}
ix.mu.Unlock()
if ix.Modules == nil {
return ix.RecomputeCandidates(ctx)
}
// A registered module the forge could not read is read again at each new source revision (issue 1945), in the
// background: a forge that does not answer must not hold this revision. One taken in later is stamped when it is written
// (applyManifests reads srcOf).
ix.kickRetry(ctx)
held, err := ix.heldModules(ctx)
if err != nil {
return err
}
for _, key := range slices.Sorted(maps.Keys(held)) {
repository := ""
for r := range s.Heads {
if delivery.SameRepository(r, held[key].Repository) {
repository = r
}
}
if repository == "" {
continue
}
head := s.Heads[repository]
err = write(ctx, ix.Modules, key, func(value []byte, found bool) ([]byte, bool, error) {
if !found {
return nil, false, nil
}
cur, err := delivery.DecodeModule(value)
if err != nil {
ix.logf("modules.%s holds what is not a module, left: %v", key, err)
return nil, false, nil
}
// Stamped once, with the first revision naming the commit as the head: a later revision (a merge of another
// repository) leaves the value as it is, so no revision of the source rewrites a module that did not change.
if cur.Repository != repository || cur.Commit != head || cur.Src != 0 {
return nil, false, nil
}
cur.Src = rev
data, err := delivery.EncodeModule(cur)
return data, false, err
})
if err != nil {
return fmt.Errorf("modules.%s: %w", key, err)
}
}
// A trunk that moved makes the previews made onto the old one out of date.
return ix.RecomputeCandidates(ctx)
}