Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/startread.go
T
2026-11-11 11:51:07 +01:00

163 lines
5.2 KiB
Go

package index
import (
"context"
"errors"
"sync"
"time"
)
// GitWaitBound is how long the indexer waits, at its start, for the seat git to answer the read of every contracts module's
// trunk definitions before it judges from the definitions it kept (design 47, *A part that cannot read the forge keeps
// what it has, and says so*): longer than a forge's restart, short enough that a start is not held behind a forge down
// for good.
const GitWaitBound = 2 * time.Minute
// GitDefinitionsView is what the indexer says it waits for in its first views while the seat git does not answer.
const GitDefinitionsView = "trunk seat definitions"
// ViewWaiter is where the indexer says it waits for the seat git and since when, and raises view-waits past its bound
// (mesh-sdk views.Follower's WaitFor and Holds).
type ViewWaiter interface {
WaitFor(ctx context.Context, view string)
Holds(view string)
}
// DefinitionsAtStart is the indexer's read of the trunk definitions at its start through the seat git: the first read,
// a wait of at most Bound for the seat when it does not answer, the judging from the kept definitions after it, and the
// read again once the seat answers, which judges again every manifest judged from kept definitions.
type DefinitionsAtStart struct {
// Read reads every contracts module's trunk definitions (Indexer.ReadTrunkDefinitions).
Read func(ctx context.Context) (int, error)
// Judge judges every manifest again, as at the start (Indexer.JudgeAtStart).
Judge func(ctx context.Context) error
Waits ViewWaiter
Bound time.Duration
Logf func(string, ...any)
mu sync.Mutex
answered chan struct{} // closed once a read is answered
waiting bool // a read was not answered and no read since was
fromKept bool // the judging at start ran on kept definitions
}
func (d *DefinitionsAtStart) init() {
if d.answered == nil {
d.answered = make(chan struct{})
}
}
func (d *DefinitionsAtStart) logf(format string, args ...any) {
if d.Logf != nil {
d.Logf(format, args...)
}
}
// Start reads the trunk definitions once; a seat that does not answer is said as a wait (Waits.WaitFor) and read again on
// the next sign that the forge runs (Heard). A read answered with other errors (a manifest it cannot read) is answered.
func (d *DefinitionsAtStart) Start(ctx context.Context) {
d.mu.Lock()
d.init()
d.mu.Unlock()
d.read(ctx, "at start")
}
func (d *DefinitionsAtStart) read(ctx context.Context, when string) bool {
n, err := d.Read(ctx)
if errors.Is(err, errForgeRead) {
d.mu.Lock()
first := !d.waiting
d.waiting = true
d.mu.Unlock()
if first {
d.logf("the seat git did not answer the read of %d contracts modules' trunk definitions %s: %v; the indexer judges from the definitions it kept after %s, and reads them again once the seat answers", n, when, err, d.bound())
if d.Waits != nil {
d.Waits.WaitFor(ctx, GitDefinitionsView)
}
}
return false
}
if err != nil {
d.logf("reading the seat definitions of %d contracts modules' trunks %s: %v; each is read again at its next merged message or by indexer_seat_definitions_again", n, when, err)
} else {
d.logf("read the seat definitions of %d contracts modules' trunks %s", n, when)
}
d.mu.Lock()
was := d.waiting
d.waiting = false
select {
case <-d.answered:
default:
close(d.answered)
}
again := d.fromKept
d.fromKept = false
d.mu.Unlock()
if was && d.Waits != nil {
d.Waits.Holds(GitDefinitionsView)
}
if again && d.Judge != nil {
if err := d.Judge(ctx); err != nil {
d.logf("judging again the manifests judged from kept definitions: %v; judged again at the next message heard", err)
} else {
d.logf("judged again every manifest judged from kept definitions, now that the seat git answers")
}
}
return true
}
func (d *DefinitionsAtStart) bound() time.Duration {
if d.Bound > 0 {
return d.Bound
}
return GitWaitBound
}
// Ready waits, while the seat git does not answer the read at start, until a read is answered, or Bound passed (no timer: a context's bound), and says whether the
// judging that follows runs on the definitions the indexer kept; it marks it so, and the read that answers later judges
// again (Read's answer).
func (d *DefinitionsAtStart) Ready(ctx context.Context) (fromKept bool) {
d.mu.Lock()
d.init()
answered, waiting := d.answered, d.waiting
d.mu.Unlock()
if !waiting {
return false // read, or not read at all (the seat definitions' state not bound yet): nothing to wait for
}
wait, cancel := context.WithTimeout(ctx, d.bound())
defer cancel()
select {
case <-answered:
return false
case <-wait.Done():
}
d.mu.Lock()
defer d.mu.Unlock()
select {
case <-d.answered:
return false
default:
}
d.fromKept = true
d.logf("the seat git has not answered within %s: the indexer judges from the definitions it kept", d.bound())
return true
}
// Heard is a sign that the forge runs (a message the forge bridge relays): while the indexer waits for the seat git, it
// reads the trunk definitions again.
func (d *DefinitionsAtStart) Heard(ctx context.Context) {
d.mu.Lock()
waiting := d.waiting
d.mu.Unlock()
if waiting {
d.read(ctx, "once the forge was heard")
}
}
// Waiting says the indexer waits for the seat git.
func (d *DefinitionsAtStart) Waiting() bool {
d.mu.Lock()
defer d.mu.Unlock()
return d.waiting
}