An ask's asker is the last token of its accept subject; the holder checks the kind against the callers the build seat's definition lists (Puller.Callers, CallersFromDefinitions), refuses an ask of another kind answering error naming the kind and the asker and ending it failed, takes only a build and a run on main's subject, and gives back an ask the seat list does not judge yet. A second worker per queue (AskNamedWorker, filtered to accept.<queue>.*) is pulled after main's; the holder says `named worker not bound` while its membership lists none. Builds: design 47 G §543, §544, §481, A-163, A-170 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
139 lines
3.7 KiB
Go
139 lines
3.7 KiB
Go
package steprun
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
|
|
)
|
|
|
|
// FakeAsks is a test double of a seat's workers for the build modules' unit tests: Publish puts an ask on a kind's
|
|
// worker, Next hands them out in their order, and the fates the puller gives them are counted. Safe for concurrent use.
|
|
type FakeAsks struct {
|
|
mu sync.Mutex
|
|
waiting map[string][][]byte
|
|
// subjects are the subjects asks were published with, by their data.
|
|
subjects map[string]string
|
|
added chan struct{}
|
|
// Acked, Termed and Naked are the asks' ids or data by fate, InProgress how many heartbeats were given.
|
|
Acked, Termed, Naked []string
|
|
// Delayed are the asks handed back to be given again after a delay (NakAfter), and Delays that delay of each.
|
|
Delayed []string
|
|
Delays []time.Duration
|
|
InProgress int
|
|
// Delivered, when set, is the delivery count every ask handed over says (1 when 0): a test of a poisoned ask sets it.
|
|
Delivered uint64
|
|
}
|
|
|
|
// NewFakeAsks is an empty double.
|
|
func NewFakeAsks() *FakeAsks {
|
|
return &FakeAsks{waiting: map[string][][]byte{}, added: make(chan struct{}, 1)}
|
|
}
|
|
|
|
// Publish puts an encoded ask on kind's worker.
|
|
func (f *FakeAsks) Publish(kind string, data []byte) {
|
|
f.mu.Lock()
|
|
f.waiting[kind] = append(f.waiting[kind], data)
|
|
f.mu.Unlock()
|
|
select {
|
|
case f.added <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// PublishWithSubject puts an encoded ask on kind's worker as the bus would hand it over from subject (design 47 G §481).
|
|
func (f *FakeAsks) PublishWithSubject(kind string, data []byte, subject string) {
|
|
f.mu.Lock()
|
|
if f.subjects == nil {
|
|
f.subjects = map[string]string{}
|
|
}
|
|
f.subjects[string(data)] = subject
|
|
f.mu.Unlock()
|
|
f.Publish(kind, data)
|
|
}
|
|
|
|
// PublishAsk encodes a and puts it on its kind's worker.
|
|
func (f *FakeAsks) PublishAsk(a sdkstep.StepAsk) error {
|
|
raw, err := sdkstep.EncodeStepAsk(a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
f.Publish(a.Kind, raw)
|
|
return nil
|
|
}
|
|
|
|
// Waiting is how many asks wait on kind's worker.
|
|
func (f *FakeAsks) Waiting(kind string) int {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return len(f.waiting[kind])
|
|
}
|
|
|
|
// Fates is a copy of the acked, termed and naked lists.
|
|
func (f *FakeAsks) Fates() (acked, termed, naked []string) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return append([]string(nil), f.Acked...), append([]string(nil), f.Termed...), append([]string(nil), f.Naked...)
|
|
}
|
|
|
|
// Next is AskSource's.
|
|
func (f *FakeAsks) Next(ctx context.Context, kind string, wait time.Duration) (*Pulled, error) {
|
|
var deadline <-chan time.Time
|
|
if wait > 0 {
|
|
t := time.NewTimer(wait)
|
|
defer t.Stop()
|
|
deadline = t.C
|
|
}
|
|
for {
|
|
f.mu.Lock()
|
|
if q := f.waiting[kind]; len(q) > 0 {
|
|
data := q[0]
|
|
f.waiting[kind] = q[1:]
|
|
f.mu.Unlock()
|
|
return f.pulled(data), nil
|
|
}
|
|
f.mu.Unlock()
|
|
if wait <= 0 {
|
|
return nil, nil
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case <-deadline:
|
|
return nil, nil
|
|
case <-f.added:
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *FakeAsks) pulled(data []byte) *Pulled {
|
|
id := string(data)
|
|
if a, err := sdkstep.DecodeStepAsk(data); err == nil {
|
|
id = a.ID
|
|
}
|
|
fate := func(list *[]string) func() error {
|
|
return func() error {
|
|
f.mu.Lock()
|
|
*list = append(*list, id)
|
|
f.mu.Unlock()
|
|
return nil
|
|
}
|
|
}
|
|
f.mu.Lock()
|
|
subject := f.subjects[string(data)]
|
|
delivered := f.Delivered
|
|
f.mu.Unlock()
|
|
if delivered == 0 {
|
|
delivered = 1
|
|
}
|
|
nakAfter := func(delay time.Duration) error {
|
|
f.mu.Lock()
|
|
f.Delayed, f.Delays = append(f.Delayed, id), append(f.Delays, delay)
|
|
f.mu.Unlock()
|
|
return nil
|
|
}
|
|
return &Pulled{Data: data, Subject: subject, Delivered: delivered, NakAfter: nakAfter, Ack: fate(&f.Acked), Nak: fate(&f.Naked), Term: fate(&f.Termed),
|
|
InProgress: func() error { f.mu.Lock(); f.InProgress++; f.mu.Unlock(); return nil }}
|
|
}
|