Taken from the join base (6ce86c96, ff6e74f9) onto the build branch. Builds: design 47 G §198, §248, §316, §255, §57 (tests 54, 57) Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
163 lines
5.2 KiB
Go
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
|
|
}
|