MCPcopy Create free account
hub / github.com/NodeDB-Lab/nodedb / assign

Method assign

nodedb/src/control/surrogate/assign.rs:99–152  ·  view source on GitHub ↗

Resolve `(collection, pk_bytes)` to a stable surrogate. - If the credential store has no catalog (in-memory test fixture), returns `Surrogate::ZERO`. Production state always wires a redb-backed `CredentialStore::open` so this branch never fires. - If a binding already exists, return it (no allocation, no flush). - Else: allocate one surrogate, persist the binding, and check the registry's flush t

(&self, collection: &str, pk_bytes: &[u8])

Source from the content-addressed store, hash-verified

97 /// on the registry write-lock so the registry hwm and the
98 /// persisted PK row cannot diverge under concurrent assigners.
99 pub fn assign(&self, collection: &str, pk_bytes: &[u8]) -> crate::Result<Surrogate> {
100 let catalog = match self.credential_store.catalog().as_ref() {
101 Some(c) => c,
102 None => return Ok(Surrogate::ZERO),
103 };
104
105 // Fast-path: existing binding. Done under a read lock — most
106 // production calls land here once the per-collection working
107 // set has been observed.
108 if let Some(s) = catalog.get_surrogate_for_pk(DatabaseId::DEFAULT, collection, pk_bytes)? {
109 return Ok(s);
110 }
111
112 // Slow path: allocate + persist + maybe flush. The write lock
113 // guards the (allocate, write-pk-row) pair so two concurrent
114 // assigners can't both observe "missing", both allocate, and
115 // both write — the second would silently overwrite the
116 // first's binding with a different surrogate.
117 let registry = self.registry.write().map_err(|_| crate::Error::Internal {
118 detail: "surrogate registry lock poisoned".into(),
119 })?;
120 // Re-check inside the lock: another assigner may have raced
121 // us between the read above and the lock acquisition.
122 if let Some(s) = catalog.get_surrogate_for_pk(DatabaseId::DEFAULT, collection, pk_bytes)? {
123 return Ok(s);
124 }
125 let surrogate = registry.alloc_one()?;
126 catalog.put_surrogate(DatabaseId::DEFAULT, collection, pk_bytes, surrogate)?;
127 // Emit a durable WAL bind before the lock releases. Order is
128 // load-bearing: a crash between catalog write and bind append
129 // is invisible (the catalog row is already on disk via redb's
130 // own WAL); a crash before the catalog write leaves nothing
131 // to recover; a crash between bind append and lock release is
132 // recovered by replaying the bind into the catalog (idempotent
133 // via the two-table overwrite).
134 self.wal_appender
135 .record_bind_to_wal(surrogate.as_u32(), collection, pk_bytes)?;
136
137 // Flush trigger: durably checkpoint the new hwm if either the
138 // ops or elapsed-time threshold has tripped. Both writes are
139 // idempotent so a crash between them on a re-run replays
140 // cleanly.
141 if registry.should_flush() {
142 let raft_shared = self.shared.get().and_then(|w| w.upgrade());
143 let combined = CombinedPersist {
144 catalog,
145 wal_appender: self.wal_appender.as_ref(),
146 raft_shared: raft_shared.as_deref(),
147 };
148 registry.flush(&combined)?;
149 }
150
151 Ok(surrogate)
152 }
153
154 /// Read-only lookup: return the surrogate previously bound to
155 /// `(collection, pk_bytes)` without ever allocating or writing.

Calls 11

get_surrogate_for_pkMethod · 0.80
put_surrogateMethod · 0.80
as_refMethod · 0.45
catalogMethod · 0.45
writeMethod · 0.45
alloc_oneMethod · 0.45
record_bind_to_walMethod · 0.45
as_u32Method · 0.45
should_flushMethod · 0.45
getMethod · 0.45
flushMethod · 0.45