H9 test: entry-again takes a shadow's not-found entry in

Builds: design 47 G §698; lead answer A-319

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
This commit is contained in:
2026-11-12 23:13:57 +01:00
parent 860ccd7e30
commit 321a0744d8
+28
View File
@@ -5,8 +5,10 @@ import (
"encoding/json"
"errors"
"fmt"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
@@ -128,6 +130,32 @@ func composeFromEntries(t *testing.T) {
if !nf || nfOther {
t.Errorf("entry-not-found told %v for the ghost and %v for the entry held", nf, nfOther)
}
// An entry asked again by hand is asked again in the shadow's cache too (lead answer A-319): the shadow holds the ghost as not
// found, the half then holds it, and the node's own composer asked again takes it in on the shadow as well.
var late atomic.Bool
shadowEnv := b.env()
shadowEnv.Entry = EntryCall(func(ctx context.Context, seat, verb string, args any) (json.RawMessage, error) {
if in := args.(meshcatalog.EntryInput); in.Fingerprint == ghost && late.Load() {
return json.Marshal(meshcatalog.EntryAnswer{Entry: meshcatalog.CatalogEntry{Module: "ghost", Fp: ghost}})
}
return ask(ctx, seat, verb, args)
})
shadow := NewShadow(shadowEnv, nil)
shadow.c.entries.Need(ctx, ghost)
for deadline := time.Now().Add(10 * time.Second); shadow.c.entries.Rows() == nil || shadow.c.entries.Rows()[0].State != meshcatalogclient.EntryNotFound; {
if time.Now().After(deadline) {
t.Fatal("the shadow did not hold the ghost as not found")
}
runtime.Gosched()
}
late.Store(true)
c.AlsoEntryAgain = shadow.EntryAgain
if err := c.EntryAgain(ctx, ghost); err != nil {
t.Fatal(err)
}
if _, state := shadow.c.entries.Need(ctx, ghost); state != meshcatalogclient.EntryAnswered {
t.Errorf("the shadow holds the ghost's entry as %s after entry-again, want answered", state)
}
// Each entry and want it composes from is kept in the composition state before the composition that uses them.
state.mu.Lock()
_, hasEntry := state.kv[entryKey(fp)]