Phase 3a: Lamport-stamped refs with CRDT-merge on PutRef
Build with clawstor cache / Cargo build (clawstor-cached) (pull_request) Successful in 16s

Concurrent PutRef safety via (clock, node) total order. New wire
methods PutRefVersioned (0x15) + GetRefVersioned (0x16). Existing
PutRef/GetRef unchanged for backward compat. Data in refs-v2/
namespace so the two coexist during cutover.

+8 tests, 272 total (baseline +8).
This commit is contained in:
Omar Sobh
2026-07-13 07:34:28 -07:00
parent 58c5bc341b
commit af5350ac17
4 changed files with 477 additions and 1 deletions
+56
View File
@@ -180,6 +180,7 @@ pub fn decode_error(b: u8) -> Option<ErrorCode> {
0xf3 => Some(ErrorCode::NotFound),
0xf4 => Some(ErrorCode::InvalidRequest),
0xf5 => Some(ErrorCode::NotConfigured),
0xf6 => Some(ErrorCode::AlreadyExists),
_ => None,
}
}
@@ -472,6 +473,61 @@ pub async fn call_put_ref(
}
}
// ── Phase 3: stamped-ref client helpers ──────────────────────────────
/// Phase 3 (2026-07-13): submit a stamped (CRDT-merge) PutRef.
///
/// Returns `Ok(true)` when the peer merged the write (Merged),
/// `Ok(false)` when the peer rejected it because an equal or
/// higher `(clock, node)` already exists (AlreadyExists). Any
/// other reply is an error.
pub async fn call_put_ref_versioned(
conn: &Connection,
key: &crate::cluster::refs::RefKey,
incoming: &crate::cluster::refs::StampedRef,
) -> Result<bool> {
let mut payload = Vec::with_capacity(32 + crate::cluster::refs::StampedRef::ENCODED_LEN);
payload.extend_from_slice(key);
payload.extend_from_slice(&incoming.to_bytes());
let reply = rpc_call(conn, Method::PutRefVersioned, &payload).await?;
if reply.len() != 1 {
bail!(
"expected single-byte PutRefVersioned reply, got {} bytes",
reply.len()
);
}
match reply[0] {
STREAM_STATUS_OK => Ok(true),
code => match decode_error(code) {
Some(ErrorCode::AlreadyExists) => Ok(false),
Some(err) => bail!("peer rejected PutRefVersioned: {}", err.describe()),
None => bail!(
"peer replied with unknown byte 0x{:02x} for PutRefVersioned",
code
),
},
}
}
/// Phase 3: fetch a stamped (CRDT-merge) ref.
pub async fn call_get_ref_versioned(
conn: &Connection,
key: &crate::cluster::refs::RefKey,
) -> Result<Option<crate::cluster::refs::StampedRef>> {
let reply = rpc_call(conn, Method::GetRefVersioned, key).await?;
if reply.len() == 1 {
match decode_error(reply[0]) {
Some(ErrorCode::NotFound) => return Ok(None),
Some(err) => bail!("peer replied with error: {}", err.describe()),
None => {}
}
}
Ok(Some(
crate::cluster::refs::StampedRef::from_bytes(&reply)
.context("decoding stamped ref reply")?,
))
}
// ── Phase 5g: cache metrics client helper ────────────────────────────
/// Fetch the peer's current cache-metrics snapshot.