A merged message is kept in the indexer's own state pending and the merges there are computed together on overlays of the indexer's states: taken in whole when the indexer's fingerprint is the running build, otherwise only the indexer's own entry, graph key and verdict are written and the rest waits for the named indexer. A group's members are gathered until its end. Waits are said on the stream take-in, read by indexer_pending and steered by indexer_take_in_pending and indexer_drop_pending. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
111 lines
3.0 KiB
Go
111 lines
3.0 KiB
Go
package index
|
|
|
|
import (
|
|
"context"
|
|
"maps"
|
|
"slices"
|
|
"sync"
|
|
)
|
|
|
|
// overlayStore is a store whose writes stay in memory over the store it reads through (novox/hq ADR 0633 §1): a merge
|
|
// applied to it is computed whole, its every fingerprint and verdict, and nothing reaches the bus. A key it wrote reads
|
|
// as written, a key it deleted as absent, every other as the store under it holds it. Its compare-and-set holds against
|
|
// what it reads: a key it wrote has a revision of its own, counted above every revision under it. A nil store under
|
|
// it holds nothing.
|
|
type overlayStore struct {
|
|
under Store
|
|
mu sync.Mutex
|
|
vals map[string]overlaid
|
|
next uint64
|
|
}
|
|
|
|
type overlaid struct {
|
|
value []byte
|
|
revision uint64
|
|
deleted bool
|
|
}
|
|
|
|
func newOverlay(under Store) *overlayStore {
|
|
return &overlayStore{under: under, vals: map[string]overlaid{}, next: 1 << 62}
|
|
}
|
|
|
|
func (o *overlayStore) Get(ctx context.Context, key string) ([]byte, uint64, bool, error) {
|
|
o.mu.Lock()
|
|
v, ok := o.vals[key]
|
|
o.mu.Unlock()
|
|
if ok {
|
|
if v.deleted {
|
|
return nil, v.revision, false, nil
|
|
}
|
|
return slices.Clone(v.value), v.revision, true, nil
|
|
}
|
|
if o.under == nil {
|
|
return nil, 0, false, nil
|
|
}
|
|
return o.under.Get(ctx, key)
|
|
}
|
|
|
|
// put checks the revision read against what the key holds now and writes it.
|
|
func (o *overlayStore) put(ctx context.Context, key string, value []byte, revision uint64, create, del bool) error {
|
|
_, rev, found, err := o.Get(ctx, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
switch {
|
|
case create && found, !create && (!found || rev != revision):
|
|
return ErrConflict
|
|
}
|
|
o.next++
|
|
o.vals[key] = overlaid{value: slices.Clone(value), revision: o.next, deleted: del}
|
|
return nil
|
|
}
|
|
|
|
func (o *overlayStore) Create(ctx context.Context, key string, value []byte) error {
|
|
return o.put(ctx, key, value, 0, true, false)
|
|
}
|
|
|
|
func (o *overlayStore) Update(ctx context.Context, key string, value []byte, revision uint64) error {
|
|
return o.put(ctx, key, value, revision, false, false)
|
|
}
|
|
|
|
func (o *overlayStore) Delete(ctx context.Context, key string, revision uint64) error {
|
|
return o.put(ctx, key, nil, revision, false, true)
|
|
}
|
|
|
|
func (o *overlayStore) Keys(ctx context.Context) ([]string, error) {
|
|
var keys []string
|
|
if o.under != nil {
|
|
var err error
|
|
if keys, err = o.under.Keys(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
keys = slices.DeleteFunc(keys, func(k string) bool { _, ok := o.vals[k]; return ok })
|
|
for k, v := range o.vals {
|
|
if !v.deleted {
|
|
keys = append(keys, k)
|
|
}
|
|
}
|
|
slices.Sort(keys)
|
|
return keys, nil
|
|
}
|
|
|
|
// written is what the overlay holds of key: its value, and whether it wrote or deleted it at all.
|
|
func (o *overlayStore) written(key string) (value []byte, deleted, ok bool) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
v, ok := o.vals[key]
|
|
return slices.Clone(v.value), v.deleted, ok
|
|
}
|
|
|
|
// changed is every key the overlay wrote or deleted, in order.
|
|
func (o *overlayStore) changed() []string {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
return slices.Sorted(maps.Keys(o.vals))
|
|
}
|