Artifact 2bb3b3982cbb6cc915d5717082e171e02a5079d19c79c4499c5311edb043f0c3:
- File
internal/sync/sparse_walk.go
— part of check-in
[dc33bca3a6]
at
2026-10-22 16:12:54
on branch feat/horizon-cones
— feat(sync): serve and take the structural horizon
The origin now answers pragma horizon-structural (draft-fossil-horizon-00 §6.1, §6.2): it keeps only the manifests and nodes the held set reaches, labels every boundary hash at the head of each reply (§6.2.2), mints the seed for C and sends it, pragma ahead of its file card, when the client offers another name (§6.2.3), and advertises the structural level only then; a request it cannot place or seed is served as content. It refuses a seed offered to it and withholds a gimme'd manifest below the horizon.
A structural store's requests carry horizon-structural and the seed it holds. It routes the announced card to seed.Hold after checking its name and shape, never storing or announcing it, records the labels, and applies the seed after each reply and after the closing link. A first reply that does not serve structural with a seed, or a seed failing provenance, makes it a content-horizon store for good, reported in the result, and the manifests below the horizon are fetched (§5.1, §5.2.4, §6.3). (user: [email protected], size: 8941) [annotate] [blame] [check-ins using] [more...]
package sync import ( "context" "database/sql" "errors" "fmt" "sort" "github.com/danmestas/go-libfossil/internal/cone" "github.com/danmestas/go-libfossil/internal/deck" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/horizon" ) // The §8 bounds of single-name lookup, charged across one request. const ( coneWalkDepth = 16 coneWalkVisits = 1 << 20 ) type sparseKind uint8 const ( sparseContent sparseKind = iota // bytes no artifact parses from sparseNode // sub-manifest node: sent by a walk, or at its row under a horizon sparseStructural // held densely (§5.1) sparseCheckin sparseCluster // held densely, but never sent under a horizon (horizon §4.5) ) // coneWalk is §6.2 keep-set discovery for one clone round. A row is read // only when the round reaches it, and a check-in's tree is walked only when // the round emits that check-in, so the work tracks the round's batch. Each // check-in sends what its primary parent's tree does not already hold; the // parent is a row of the same clone, so by induction every in-cone node and // blob of every check-in is sent once its own check-in is. // // Under a content horizon (draft-fossil-horizon-00 §6.2) only a check-in in // the held set walks its tree, and its parent counts as sent only when it is // in the held set too: the blobs sent are those above the horizon. Every // node and every other structural artifact but a cluster goes out at its // row, as §4.1 holds them at every age. type coneWalk struct { ctx context.Context h *handler cone *cone.Cone horizon bool held map[int]bool // the held set; nil when there is no horizon or the store cannot place C reach map[string]bool // a structural request's kept manifests and nodes (§5.1); nil otherwise kinds map[int]sparseKind decks map[int]*deck.Deck reached map[int]bool path map[string]bool visits int classified int } func newConeWalk(ctx context.Context, h *handler) *coneWalk { if ctx == nil || h == nil || h.cone == nil { panic("sync.newConeWalk: nil context, handler or cone") } _, horizon := h.cone.Horizon() w := &coneWalk{ctx: ctx, h: h, cone: h.cone, horizon: horizon, kinds: map[int]sparseKind{}, decks: map[int]*deck.Deck{}, reached: map[int]bool{}, path: map[string]bool{}} if h.structural != nil { w.reach = h.structural.reach } return w } // placeHorizon computes the held set over the store, bounded as every // server-side graph walk is (§8). A C the store cannot place proves no blob // below the horizon, so every check-in then walks its tree, as a request // without one would. func (w *coneWalk) placeHorizon() error { h, ok := w.cone.Horizon() if !ok { return nil } k, placed, err := horizon.Place(w.ctx, w.h.repo.DB(), h) if err != nil || !placed { return err } w.held = make(map[int]bool, len(k)) for _, rid := range k { w.held[int(rid)] = true } return nil } // inHeld reports a check-in whose tree the request keeps. func (w *coneWalk) inHeld(rid int) bool { return w.held == nil || w.held[rid] } // atRow reports a kind sent at its own row rather than by a walk. func (w *coneWalk) atRow(kind sparseKind) bool { switch kind { case sparseStructural, sparseCheckin: return true case sparseNode: return w.horizon case sparseCluster: return !w.horizon } return false } // classify parses rid once per request. Unreadable bytes stay structural: // they go out at their row and fail there exactly as a dense clone would. func (w *coneWalk) classify(rid int) (sparseKind, *deck.Deck) { if kind, ok := w.kinds[rid]; ok { return kind, w.decks[rid] } w.classified++ kind, d := sparseStructural, (*deck.Deck)(nil) data, err := w.h.cache.Expand(w.h.repo.DB(), libfossil.FslID(rid)) if err == nil { kind = sparseContent if deck.HasValidZ(data) { if parsed, perr := deck.Parse(data); perr == nil { kind, d = deckKind(parsed), parsed } } } w.kinds[rid], w.decks[rid] = kind, d return kind, d } func deckKind(d *deck.Deck) sparseKind { switch d.Type { case deck.Submanifest: return sparseNode case deck.Checkin: return sparseCheckin case deck.Cluster: return sparseCluster } return sparseStructural } // unreached reports a manifest or node a structural request does not keep: // one no tree of the held set reaches (horizon §5.1). func (w *coneWalk) unreached(kind sparseKind, uuid string) bool { return w.reach != nil && (kind == sparseCheckin || kind == sparseNode) && !w.reach[uuid] } // sourceKept is the §6.2 keep-set test for a served delta's base. func (w *coneWalk) sourceKept(rid int) bool { if w.reached[rid] { return true } kind, _ := w.classify(rid) if !w.atRow(kind) { return false } if w.reach == nil || kind != sparseCheckin && kind != sparseNode { return true } info, ok, err := w.h.loadCloneBlob(rid) return err == nil && ok && !w.unreached(kind, info.uuid) } // servable resolves uuid to a row this clone session sends: held, inside // the snapshot bound, not shunned, and public unless the clone is private. func (w *coneWalk) servable(uuid string) (int, bool, error) { var rid int err := w.h.repo.DB().QueryRow("SELECT rid FROM blob WHERE uuid=? AND size>=0", uuid).Scan(&rid) if errors.Is(err, sql.ErrNoRows) { return 0, false, nil } if err != nil { return 0, false, fmt.Errorf("sparse walk: resolve %s: %w", uuid, err) } if rid > w.h.cloneSnapMax { return 0, false, nil } info, ok, err := w.h.loadCloneBlob(rid) if err != nil || !ok { return 0, false, err } return rid, !info.priv || w.h.syncPrivate, nil } // items returns, ascending, the rows the artifact d at structural row rid // brings with it: an attachment's source, or a check-in's new in-cone tree. func (w *coneWalk) items(rid int, d *deck.Deck) ([]int, error) { out := map[int]bool{} var err error switch { case d.A != nil && d.A.Source != "": err = w.add(d.A.Source, out) case d.Type == deck.Checkin && !w.inHeld(rid): case d.Type == deck.Checkin && len(d.S) > 0: prior := coneTreeLevel{} if parent, perr := w.parent(d); perr != nil { return nil, perr } else if parent != nil { prior.cards = parent.S } err = w.tree(coneTreeLevel{cards: d.S}, prior, nil, 1, out) case d.Type == deck.Checkin: err = w.flat(d, out) } if err != nil { return nil, err } rids := make([]int, 0, len(out)) for item := range out { w.reached[item] = true rids = append(rids, item) } sort.Ints(rids) return rids, nil } func (w *coneWalk) add(uuid string, out map[int]bool) error { rid, ok, err := w.servable(uuid) if ok { out[rid] = true } return err } // parent is the primary parent check-in when this clone sends its tree, // else nil: then nothing is known to be sent already and the whole cone // goes out. func (w *coneWalk) parent(d *deck.Deck) (*deck.Deck, error) { if len(d.P) == 0 { return nil, nil } p, rid, err := w.checkin(d.P[0]) if err != nil || p == nil || !w.inHeld(rid) { return nil, err } return p, nil } func (w *coneWalk) checkin(uuid string) (*deck.Deck, int, error) { rid, ok, err := w.servable(uuid) if err != nil || !ok { return nil, 0, err } if kind, d := w.classify(rid); kind == sparseCheckin { return d, rid, nil } return nil, 0, nil } // flat sends the in-cone files of d's full file list that its parent's full // list does not name identically. func (w *coneWalk) flat(d *deck.Deck, out map[int]bool) error { files, err := w.fileList(d) if err != nil { return err } parent, err := w.parent(d) if err != nil { return err } prior := map[deck.FileCard]bool{} if parent != nil { pf, err := w.fileList(parent) if err != nil { return err } for _, f := range pf { prior[deck.FileCard{Name: f.Name, UUID: f.UUID}] = true } } return w.files(files, prior, out) } // fileList applies a delta manifest's F cards to its baseline's. An unheld // baseline leaves the delta's own cards, the most this store can prove. func (w *coneWalk) fileList(d *deck.Deck) ([]deck.FileCard, error) { if d.B == "" { return d.F, nil } base, _, err := w.checkin(d.B) if err != nil || base == nil { return d.F, err } byName := make(map[string]deck.FileCard, len(base.F)+len(d.F)) for _, f := range base.F { byName[f.Name] = f } for _, f := range d.F { byName[f.Name] = f } merged := make([]deck.FileCard, 0, len(byName)) for _, f := range byName { merged = append(merged, f) } return merged, nil } func (w *coneWalk) files(files []deck.FileCard, prior map[deck.FileCard]bool, out map[int]bool) error { for _, f := range files { if err := w.ctx.Err(); err != nil { return err } key := deck.FileCard{Name: f.Name, UUID: f.UUID} if f.UUID == "" || prior[key] || !w.cone.Covers(f.Name) { continue } if err := w.add(f.UUID, out); err != nil { return err } } return nil }