Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions lib/bindings/kvbm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions lib/bindings/python/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/kv-router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dynamo-truthy = { workspace = true }
# workspace
anyhow = { workspace = true }
arc-swap = "1.9.1"
crossbeam-epoch = "0.9"
async-trait = { workspace = true }
blake3 = { workspace = true }
dashmap = { workspace = true }
Expand Down
18 changes: 8 additions & 10 deletions lib/kv-router/src/indexer/compressed_radix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,16 +241,14 @@ impl NodeState {
debug_assert!(!blocks.is_empty());

let old_len = self.edge.len();
let downgraded_workers = self
.full_edge_workers
.iter()
.copied()
.filter(|&full_worker| full_worker != worker)
.collect::<Vec<_>>();
for downgraded_worker in downgraded_workers {
self.full_edge_workers.remove(&downgraded_worker);
self.worker_cutoffs.insert(downgraded_worker, old_len);
}
let worker_cutoffs = &mut self.worker_cutoffs;
self.full_edge_workers.retain(|&full_worker| {
if full_worker == worker {
return true;
}
worker_cutoffs.insert(full_worker, old_len);
false
});
self.promote_to_full(worker);

self.edge.reserve(blocks.len());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,28 @@ shared CRTC nodes concurrently.
Node internals use separate protection for edge state and child maps:

- `NodeState` is protected by a `parking_lot::RwLock`.
- `children` publishes compact snapshots through `ArcSwap`, promoting to a
- `children` publishes compact snapshots reclaimed through epochs (`crossbeam-epoch`), promoting to a
`DashMap` when fanout exceeds four children.
- Every child `Arc` a reachable child map owns is released through the epoch:
retired compact snapshots and `Arc`s a `DashMap` unlinks in place. A dropped
node frees its own map inline, which is safe because no reader can reach a node
whose last `Arc` is gone. Dropping the whole tree also frees every map inline,
since exclusive access rules out readers. `find_matches` pins once per walk and
borrows each node under that pin instead of cloning its `Arc`, so a node it
stands on stays allocated even if a writer unlinks it meanwhile. Writers still
hold `Arc`s.
- Expired epoch garbage is not freed inside the collection that releases it,
which often runs on a `find_matches` caller. It goes to the collecting thread's
graveyard when that thread drains one: a lookup frees a small budget after it
unpins and hands the rest to a shared graveyard, and an event lane frees some
after each event and all of it when idle or on `Flush`. Garbage collected on
any other thread (rayon and moka pin the same global collector), overflow, and
garbage from exiting threads also go to the shared graveyard, which event
lanes drain. So a detached subtree never stalls one lookup, an idle lookup
thread holds no garbage, and a lookup touches the shared queue only when its
collection left more than its budget. A drain releases a retired snapshot's
reference counts only as it drops each child, so leftovers of a spent budget,
moved or not, stay counted.
- `shape_gate` and `shape_version` coordinate plans that depend on the relation
between the edge and child map.

Expand All @@ -194,9 +214,11 @@ exclusive shape gate.

`find_matches` is best-effort during concurrent shape changes. It reads node
state and child pointers without taking `shape_gate` on the hot step, so it may
observe adjacent tree shapes during a split and undercount. It must not panic,
and apart from the equal-size skip below it must not return a match past a
valid reachable prefix.
observe adjacent tree shapes during a split and undercount. Because reads hold
no reference counts, stale-leaf cleanup can also unlink an empty leaf a read is
standing on; that leaf has no workers, so the read cannot overcount there. It
must not panic, and apart from the equal-size skip below it must not return a
match past a valid reachable prefix.

### Equal-size skip

Expand Down
Loading
Loading