diff --git a/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches.go b/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches.go index bfe073b1..573f4d81 100644 --- a/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches.go +++ b/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches.go @@ -12,6 +12,7 @@ import ( seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/sourcebranches" "git.novox.be/novox/mesh-sdk/go/buscalls" + "git.novox.be/novox/mesh-sdk/go/busretry" "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/views" ) @@ -392,7 +393,8 @@ func (e *Entries) Rows() []EntryRow { } // ask reads fingerprint's entry once. A call that failed to reach the holder leaves the entry asked, with its reason; -// the holder's own answer that it holds none is `not-found`. +// the holder's own answer that it holds none is `not-found`, whether the call gives it in the reply or, as a process's +// call does, as its error (busretry.Answered). func (e *Entries) ask(_ context.Context, fingerprint string) { raw, err := e.call(EntryVerb, seat.EntryInput{Fingerprint: fingerprint}) var row EntryRow @@ -402,7 +404,11 @@ func (e *Entries) ask(_ context.Context, fingerprint string) { e.mu.Unlock() return } + var holderSaid *busretry.Answered switch { + case errors.As(err, &holderSaid): + // A process's call gives the holder's own answer as its error: it holds none. + cur.State, cur.Since, cur.Detail = EntryNotFound, e.now(), holderSaid.Words case err != nil: cur.Detail = err.Error() e.mu.Unlock() diff --git a/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches_test.go b/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches_test.go index 6e0f733e..2dfade9d 100644 --- a/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches_test.go +++ b/modules/mesh-delivery-contracts/go/clients/meshcatalogclient/branches_test.go @@ -12,7 +12,9 @@ import ( seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/sourcebranches" + "git.novox.be/novox/mesh-sdk/go/busretry" "git.novox.be/novox/mesh-sdk/go/views" + "github.com/nats-io/nats.go" ) // fakeWiring is a part's reader: it keeps the views adopted, by `.`, and says each drop. @@ -239,4 +241,30 @@ func TestEntriesAreAskedOnceAnsweredOrNotFound(t *testing.T) { if err := e.Again(ctx, "src2:a"); err != nil || seats.entryAsk != 3 { t.Fatalf("again: %v, asks %d", err, seats.entryAsk) } + // A process's call gives the holder's answer that it holds none as its error (busretry.Answered): not-found too, and + // the part is told, so a pass waiting on it runs (design 47 G §614, §298); a call that reached no holder stays asked. + viaConn := NewEntries(func(string, any) (json.RawMessage, error) { + return nil, &busretry.Answered{Words: "no catalog entry has the fingerprint src2:old"} + }) + toldConn := make(chan EntryRow, 2) + viaConn.Changed = func(r EntryRow) { toldConn <- r } + viaConn.Need(ctx, "src2:old") + if err := viaConn.Again(ctx, "src2:old"); err != nil { + t.Fatal(err) + } + if rows := viaConn.Rows(); len(rows) != 1 || rows[0].State != EntryNotFound || rows[0].Detail == "" || len(toldConn) == 0 { + t.Fatalf("the holder's answer through a process's call: rows %+v, told %d", rows, len(toldConn)) + } + unreached := NewEntries(func(string, any) (json.RawMessage, error) { return nil, nats.ErrNoResponders }) + unreached.Changed = func(r EntryRow) { t.Errorf("a call that reached no holder told %+v", r) } + if err := unreached.Again(ctx, "src2:x"); err == nil { + t.Fatal("again of an entry never needed answers no error") + } + unreached.Need(ctx, "src2:x") + if err := unreached.Again(ctx, "src2:x"); err != nil { + t.Fatal(err) + } + if rows := unreached.Rows(); len(rows) != 1 || rows[0].State != EntryAsked || rows[0].Detail == "" { + t.Fatalf("a call that reached no holder: rows %+v", rows) + } }