B5g5-1: the indexer sends the input's chunks with buscalls.SeatChunks.Send, so its Subscribe exception goes
The acknowledgement subscription is the SDK's; the indexer's inputs.go holds none, and the test that forbids a subscription in the program no longer excepts it. Builds: design 47 G §712, §681, A-237, A-139 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
@@ -77,22 +76,3 @@ func (o *inputObjects) List() ([]stateobjects.Object, error) {
|
||||
}
|
||||
return b.List()
|
||||
}
|
||||
|
||||
// coreChunks is the core connection as a send of chunks reaches it (index.ChunkLine): a chunk is published on no stream, an
|
||||
// acknowledgement is a request the holder answers.
|
||||
type coreChunks struct{ nc *nats.Conn }
|
||||
|
||||
func (c coreChunks) Publish(subject string, data []byte) error { return c.nc.Publish(subject, data) }
|
||||
|
||||
func (c coreChunks) Hear(subject string, fn func(string)) (func(), error) {
|
||||
sub, err := c.nc.Subscribe(subject, func(m *nats.Msg) {
|
||||
fn(m.Subject)
|
||||
if m.Reply != "" {
|
||||
_ = m.Respond([]byte("{}"))
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func() { _ = sub.Unsubscribe() }, nil
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
inputsStore := awaitedStore{b: process.AwaitBucket(ctx, js, conn, index.InputsBucket, "no input of a run ask is kept, so the verb input answers none", logger.Printf)}
|
||||
inObjects := &inputObjects{js: js, module: cred.Module}
|
||||
stopInput, err := calls.AnswerSeat(meshcatalog.Seat, map[string]process.SeatHandler{
|
||||
meshcatalog.VerbInput: index.InputVerb(inputsStore, inObjects, coreChunks{conn}, buscalls.SeatAckWithin, logger.Printf)})
|
||||
meshcatalog.VerbInput: index.InputVerb(inputsStore, inObjects, conn, buscalls.SeatAckWithin, logger.Printf)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("answering the verb input: %w", err)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
||||
"git.novox.be/novox/mesh-sdk/go/stateobjects"
|
||||
@@ -42,7 +44,7 @@ func (o *objectsMem) List() ([]stateobjects.Object, error) { return nil, nil }
|
||||
// lineMem is a core bus in memory: a chunk published is kept, and its acknowledgement heard at once.
|
||||
type lineMem struct {
|
||||
mu sync.Mutex
|
||||
hear func(string)
|
||||
hear nats.MsgHandler
|
||||
chunks map[string][]byte
|
||||
}
|
||||
|
||||
@@ -55,15 +57,15 @@ func (l *lineMem) Publish(subject string, data []byte) error {
|
||||
if err := json.Unmarshal(data, &c); err != nil {
|
||||
return err
|
||||
}
|
||||
go h(buscalls.SeatChunks{Seat: meshcatalog.Seat, Verb: meshcatalog.VerbInput, Call: "0123456789abcdef"}.Ack(c.N))
|
||||
go h(&nats.Msg{Subject: buscalls.SeatChunks{Seat: meshcatalog.Seat, Verb: meshcatalog.VerbInput, Call: "0123456789abcdef"}.Ack(c.N)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lineMem) Hear(_ string, fn func(string)) (func(), error) {
|
||||
func (l *lineMem) Subscribe(_ string, fn nats.MsgHandler) (*nats.Subscription, error) {
|
||||
l.mu.Lock()
|
||||
l.hear = fn
|
||||
l.mu.Unlock()
|
||||
return func() {}, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Design 47 G §712, A-237: an input is of any size: over a key's limit it is kept as an object beside the state, and the verb
|
||||
|
||||
@@ -225,20 +225,12 @@ func InputBody(ctx context.Context, st Store, objs stateobjects.Objects, digest
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// ChunkLine is the core bus as a send of chunks reaches it: a chunk is published on its subject, kept on no stream, and each is
|
||||
// acknowledged by the caller as a request on its ack subject, which the holder answers (design 47 G §681).
|
||||
type ChunkLine interface {
|
||||
Publish(subject string, data []byte) error
|
||||
// Hear calls fn with the subject of each request made on subject (a pattern) and answers it, until stop is called.
|
||||
Hear(subject string, fn func(subject string)) (stop func(), err error)
|
||||
}
|
||||
|
||||
var callToken = regexp.MustCompile(`^[A-Za-z0-9_-]{16,64}$`)
|
||||
|
||||
// InputVerb answers the verb input (marked chunks): the digest and how many chunks carry the bytes, then sends them in chunks of at
|
||||
// most buscalls.SeatChunkBytes on the call's chunk subjects, each acknowledged before the next (A-237, A-139). The input is of any
|
||||
// size. A caller that does not acknowledge within buscalls.SeatAckWithin ends the send, said in logf.
|
||||
func InputVerb(st Store, objs stateobjects.Objects, line ChunkLine, within time.Duration, logf func(string, ...any)) func(caller string, body json.RawMessage) (any, error) {
|
||||
func InputVerb(st Store, objs stateobjects.Objects, line buscalls.ChunkLine, within time.Duration, logf func(string, ...any)) func(caller string, body json.RawMessage) (any, error) {
|
||||
return func(_ string, raw json.RawMessage) (any, error) {
|
||||
var in meshcatalog.InputInput
|
||||
if err := json.Unmarshal(raw, &in); err != nil {
|
||||
@@ -253,31 +245,10 @@ func InputVerb(st Store, objs stateobjects.Objects, line ChunkLine, within time.
|
||||
}
|
||||
send := buscalls.SeatChunks{Seat: meshcatalog.Seat, Verb: meshcatalog.VerbInput, Call: in.Call}
|
||||
total := int64((len(body) + buscalls.SeatChunkBytes - 1) / buscalls.SeatChunkBytes)
|
||||
acks := make(chan string, 1)
|
||||
// heard before the answer goes, since the caller acknowledges only after it
|
||||
stop, err := line.Hear(buscalls.SeatAcks(meshcatalog.Seat, meshcatalog.VerbInput), func(subject string) {
|
||||
select {
|
||||
case acks <- subject:
|
||||
default:
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("the acknowledgements cannot be heard, so no chunk is sent: %w", err)
|
||||
}
|
||||
// the send subscribes to the acknowledgements itself and ends when it has sent every chunk or one is not acknowledged
|
||||
go func() {
|
||||
defer stop()
|
||||
for n := int64(1); n <= total; n++ {
|
||||
from := (n - 1) * buscalls.SeatChunkBytes
|
||||
to := min(int64(len(body)), from+buscalls.SeatChunkBytes)
|
||||
data, _ := json.Marshal(buscalls.SeatChunk{N: n, Of: total, Data: body[from:to]})
|
||||
if err := line.Publish(send.Chunk(n), data); err != nil {
|
||||
logf("the send %s of the input %s ends at chunk %d of %d: %v", in.Call, in.Digest, n, total, err)
|
||||
return
|
||||
}
|
||||
if !acknowledged(acks, send.Ack(n), within) {
|
||||
logf("the send %s of the input %s ends at chunk %d of %d: no acknowledgement within %s", in.Call, in.Digest, n, total, within)
|
||||
return
|
||||
}
|
||||
if err := send.Send(line, body, within, logf); err != nil {
|
||||
logf("the send %s of the input %s: %v", in.Call, in.Digest, err)
|
||||
}
|
||||
}()
|
||||
return meshcatalog.InputAnswer{Digest: in.Digest, Chunks: total}, nil
|
||||
@@ -339,19 +310,3 @@ func InputsReadTool(st Store, objs stateobjects.Objects) stdio.Tool {
|
||||
return out, nil
|
||||
}}
|
||||
}
|
||||
|
||||
// acknowledged waits within for the acknowledgement on subject, skipping another call's.
|
||||
func acknowledged(acks <-chan string, subject string, within time.Duration) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), within)
|
||||
defer cancel()
|
||||
for {
|
||||
select {
|
||||
case got := <-acks:
|
||||
if got == subject {
|
||||
return true
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +382,7 @@ func TestServesNoRequestSubjectAndHoldsNoTicker(t *testing.T) { // 1238/AC9 (ADR
|
||||
for _, f := range forbidden {
|
||||
// The seat list reader's Bus interface names a method Subscribe; the indexer's own bus hands it its own events
|
||||
// directly and subscribes to nothing on the bus.
|
||||
// The acknowledgements of the chunks of the verb input are heard on the core connection while a send runs (design 47 G
|
||||
// §712, A-237, §681): cmd/mesh-delivery-indexer/inputs.go.
|
||||
if f == "Subscribe(" && (strings.HasSuffix(path, "seatlist.go") || strings.HasSuffix(path, "mesh-delivery-indexer/inputs.go")) {
|
||||
if f == "Subscribe(" && strings.HasSuffix(path, "seatlist.go") {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(string(src), f) {
|
||||
|
||||
Reference in New Issue
Block a user