diff --git a/modules/mesh-delivery-indexer/internal/index/accepted.go b/modules/mesh-delivery-indexer/internal/index/accepted.go index 096a921e..c15c1606 100644 --- a/modules/mesh-delivery-indexer/internal/index/accepted.go +++ b/modules/mesh-delivery-indexer/internal/index/accepted.go @@ -39,11 +39,12 @@ func acceptedOf(digest string, v delivery.ManifestVerdict, revision uint64, raw return a } -// leftOf is the empty manifest-accepted for module, which left the trunk, judged by judgeID; its event id names the module -// and the manifest it last held, since its body names no digest. -func leftOf(module, lastDigest, judgeID string) (delivery.ManifestAccepted, string) { - a := delivery.ManifestAccepted{V: delivery.Version, Module: module, Judge: judgeID, BusState: []catalogue.BusStateEntry{}} - return a, delivery.ManifestAcceptedEvent + "-left-" + module + "-" + lastDigest +// leftOf is the empty manifest-accepted for module, which left the trunk, judged by judgeID; revision is the modules +// state's revision of the value that left (0 when none is held), so its event id (delivery.ManifestAccepted.EventID, the +// module and the revision, since its body names no digest) differs for each leave. +func leftOf(module string, revision uint64, judgeID string) (delivery.ManifestAccepted, string) { + a := delivery.ManifestAccepted{V: delivery.Version, Module: module, Judge: judgeID, BusState: []catalogue.BusStateEntry{}, Revision: revision} + return a, a.EventID() } // sayAccepted says a with the event id id, once more when the first publish fails; the module's finding is written when @@ -109,7 +110,7 @@ func (ix *Indexer) sayFor(ctx context.Context, module string, allowLeft, again b case !ok && !allowLeft: return "", fmt.Errorf("the indexer holds no module %s, so it has no accepted verdict to say", module) case !ok: - a, id = leftOf(module, "", ix.judgeID()) + a, id = leftOf(module, 0, ix.judgeID()) case m.Manifest == "" || ix.Verdicts == nil || ix.Manifests == nil: return "", fmt.Errorf("%s has no accepted verdict the indexer holds", module) default: diff --git a/modules/mesh-delivery-indexer/internal/index/candidates.go b/modules/mesh-delivery-indexer/internal/index/candidates.go index 123fa52c..2ba2e085 100644 --- a/modules/mesh-delivery-indexer/internal/index/candidates.go +++ b/modules/mesh-delivery-indexer/internal/index/candidates.go @@ -322,7 +322,8 @@ func (ix *Indexer) applyOpen(ctx context.Context, f delivery.Forge) error { // write changes one key of a store by compare-and-set: compute is given the value read (found says there is one) and // answers the new value, or del to remove the key, or neither for no change. A conflict reads again, up to writeTries. -func write(ctx context.Context, st Store, key string, compute func(value []byte, found bool) (next []byte, del bool, err error)) error { +// read, when given, is set to the revision of the value compute was last given. +func write(ctx context.Context, st Store, key string, compute func(value []byte, found bool) (next []byte, del bool, err error), read ...*uint64) error { for try := 0; try < writeTries; try++ { value, revision, found, err := st.Get(ctx, key) if err != nil { @@ -332,6 +333,9 @@ func write(ctx context.Context, st Store, key string, compute func(value []byte, if err != nil { return err } + for _, r := range read { + *r = revision + } switch { case next == nil && !del: return nil diff --git a/modules/mesh-delivery-indexer/internal/index/modules.go b/modules/mesh-delivery-indexer/internal/index/modules.go index 5c9de69a..3c8307e2 100644 --- a/modules/mesh-delivery-indexer/internal/index/modules.go +++ b/modules/mesh-delivery-indexer/internal/index/modules.go @@ -236,6 +236,7 @@ func (ix *Indexer) dropRemoved(ctx context.Context, repository string, held map[ elsewhere[key] = f continue } + var left uint64 // the revision of the value deleted, which names its leave (leftOf) err := write(ctx, ix.Modules, key, func(value []byte, found bool) ([]byte, bool, error) { if !found { return nil, false, nil @@ -244,13 +245,13 @@ func (ix *Indexer) dropRemoved(ctx context.Context, repository string, held map[ return nil, false, nil } return nil, true, nil - }) + }, &left) if err != nil { return fmt.Errorf("modules.%s: %w", key, err) } ix.logf("modules.%s deleted: %s holds no module of that name at its trunk (issue 1900)", key, repository) // The bus module hears the module asks nothing now, so its entries reach orphans (design 25 ยง 12.2, DLV-I9). - a, id := leftOf(key, cur.Manifest, ix.judgeID()) + a, id := leftOf(key, left, ix.judgeID()) _ = ix.sayAccepted(ctx, a, id) } for _, key := range handed { diff --git a/modules/mesh-delivery-indexer/internal/index/remove_test.go b/modules/mesh-delivery-indexer/internal/index/remove_test.go index 1b14c189..86475653 100644 --- a/modules/mesh-delivery-indexer/internal/index/remove_test.go +++ b/modules/mesh-delivery-indexer/internal/index/remove_test.go @@ -37,7 +37,9 @@ func TestAMergeThatRemovesAModuleDeletesItsKey(t *testing.T) { // 1900/AC1 for i, a := range said { if a.Digest == "" { left = append(left, a.Module) - if a.BusState == nil || len(a.BusState) != 0 || !strings.Contains(ids[i], "-left-b") { + // Join item 3: the id is the event's own (delivery.ManifestAccepted.EventID), naming the module and the + // modules state's revision of the value that left, so two leaves of one module are two events. + if a.BusState == nil || len(a.BusState) != 0 || a.Revision == 0 || ids[i] != a.EventID() || !strings.Contains(ids[i], "-left-b-") { t.Errorf("the empty manifest-accepted = %+v, id %s", a, ids[i]) } }