80 lines
2.8 KiB
Go
80 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
)
|
|
|
|
// What the builder says it writes (novox/hq issue 1886, ADR 0522): each running instance keeps a key, named by its node,
|
|
// in its own bucket of instances (`mesh-delivery-builder_instances`, the manifest's state `instances`), saying that every
|
|
// done key it writes names where its artifacts are (ADR 0498 §1). The references come from the build modules' own pushes,
|
|
// so an instance says it only once a step of every build module it calls answered one since it started: a builder beside
|
|
// a build module from before R4e says nothing. The controller delivers from the builder's keys only while an instance
|
|
// says so; since R4g it asks no build-agent, and a module whose key is not yet done waits, named (ADR 0524 §7). The bucket is read by the controller alone, so its name and shape
|
|
// are not in mesh-sdk.
|
|
const (
|
|
instancesBucket = "mesh-delivery-builder_instances"
|
|
writesReferences = `{"v":1,"writes":["references"]}`
|
|
)
|
|
|
|
// sayStore is the bucket of instances as the builder writes it.
|
|
type sayStore interface {
|
|
Put(ctx context.Context, key string, value []byte) (uint64, error)
|
|
Delete(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt) error
|
|
}
|
|
|
|
// sayer puts this instance's say once every build module in need answered a reference, and takes it away at the stop.
|
|
type sayer struct {
|
|
ctx context.Context
|
|
store sayStore
|
|
node string
|
|
need []string
|
|
logf func(string, ...any)
|
|
|
|
mu sync.Mutex
|
|
answered map[string]bool
|
|
said bool
|
|
}
|
|
|
|
func newSayer(ctx context.Context, store sayStore, node string, need []string, logf func(string, ...any)) *sayer {
|
|
return &sayer{ctx: ctx, store: store, node: node, need: need, logf: logf, answered: map[string]bool{}}
|
|
}
|
|
|
|
// Answered is told the build module whose step answered a reference (build.Builder.Answered).
|
|
func (s *sayer) Answered(module string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.answered[module] = true
|
|
if s.said {
|
|
return
|
|
}
|
|
for _, m := range s.need {
|
|
if !s.answered[m] {
|
|
return
|
|
}
|
|
}
|
|
if _, err := s.store.Put(s.ctx, s.node, []byte(writesReferences)); err != nil {
|
|
s.logf("saying in %s that this builder writes references: %v; said again at the next reference", instancesBucket, err)
|
|
return
|
|
}
|
|
s.said = true
|
|
s.logf("every build module answered a reference: said in %s that this builder writes references", instancesBucket)
|
|
}
|
|
|
|
// unsay takes the say away, if it was made: a builder replaced by one that does not write references leaves none.
|
|
func (s *sayer) unsay(ctx context.Context) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if !s.said {
|
|
return nil
|
|
}
|
|
if err := s.store.Delete(ctx, s.node); err != nil {
|
|
return fmt.Errorf("taking away the say in %s: %w", instancesBucket, err)
|
|
}
|
|
s.said = false
|
|
return nil
|
|
}
|