The wave-2 facade skeleton and routing (tasks/store-construction.md):
the public surface consumers program against, with the ops methods
still to land against it in store-read-paths/gc-protocol/store-put-paths.
- src/store.rs — the public `Store` type (engine-blind, ADR-007/008/
010) over crate-internal tier enums (`KvEngine`{mem}, `LargeEngine`{
local}): the engine-representation design point decided as concrete
+ enum dispatch, generic rejected (pub(crate) engines, no dyn);
the three constructor modes as named constructors — `mem_only`,
`kv_only_mem`, `dual_tier_mem_local` (the ADR-012 §6.3
dispatch-coverage shape) — with `StoreMode` introspection; the
write-side routing seam (`tier_for`), the read-side fall-through
order (`kv()` then `large()`, ADR-012 §6.1), and the `GcHome`
GC-state field; the ADR-012 §3 fleet declaration ruled deferred to
the engine waves (nothing fleet-valid is constructible yet)
- src/dispatch.rs — the pure routing function (`route`, at-or-below
→ kv), `DEFAULT_DISPATCH_THRESHOLD` (128 KiB) exported at the
crate root, boundary-value unit tests (exactly-at-threshold,
degenerate tunings, purity)
- src/gc.rs — `GcHome`: the store-instance GC-state home the
gc-protocol task fills (in-process; fleet state stays on the
engine-state seam per ADR-010 §2/ADR-012 §4)
- tests/conformance — the wave-2 binding made as anticipated: the
factory's `Store` associated type collapsed into the concrete
`alkblobs::Store` (`open` vends it directly), recorded in
factory.rs + README; the wave-1 mock probe retired, replaced by
mem-gated binding demo factories (binding.rs) over both
constructible shapes
Verification: `cargo test` (default), `cargo test --features mem`,
`cargo test --all-features` green (41/53/53 unit + conformance
targets green in all three shapes); `cargo clippy --all-targets
-- -D warnings` green in all three feature shapes; `cargo fmt
--check` green; `cargo doc --no-deps` adds no warnings beyond the
pre-existing private-intra-doc-links class.
141 lines
4.9 KiB
Rust
141 lines
4.9 KiB
Rust
//! The wave-2 factory binding demonstrator (mem-gated — mem is the
|
|
//! skip-free reference shape): the concrete `Store` facade vended by
|
|
//! [`StoreFactory::open`] in both constructible wave-2 shapes, the
|
|
//! factory contract's isolation + idempotent-teardown requirements
|
|
//! exercised, the wave-1 mock probe retired by the binding. The real
|
|
//! columns carry these factories forward with rows (suite-column-mem,
|
|
//! suite-column-local).
|
|
|
|
#![allow(dead_code)]
|
|
|
|
use std::path::PathBuf;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::sync::Mutex;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use crate::factory::StoreFactory;
|
|
use alkblobs::{Error, Store, StoreMode, DEFAULT_DISPATCH_THRESHOLD};
|
|
|
|
/// The binding demo factory, mem-only mode: a fresh `BTreeMap`-backed
|
|
/// store per `open` (isolated by construction — the ephemeral engine
|
|
/// shares nothing across instances), teardown a no-op. Mem is the
|
|
/// skip-free reference shape, so this column never skips.
|
|
struct MemOnlyBinding;
|
|
|
|
impl StoreFactory for MemOnlyBinding {
|
|
fn name(&self) -> &'static str {
|
|
"mem-only (binding demo)"
|
|
}
|
|
|
|
async fn open(&self) -> Result<Store, Error> {
|
|
Ok(Store::mem_only())
|
|
}
|
|
|
|
async fn teardown(&self) -> Result<(), Error> {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A fresh temp root per factory `open` — the isolated backing for the
|
|
/// dual-tier binding demo. No Drop guard: teardown owns removal (the
|
|
/// factory contract's ownership; a factory dropped without teardown
|
|
/// leaks its temp roots — harmless temp debris, and teardown makes
|
|
/// sense over the abandoned instance per the contract).
|
|
fn fresh_temp_root() -> PathBuf {
|
|
static SEQ: AtomicU64 = AtomicU64::new(0);
|
|
let seq = SEQ.fetch_add(1, Ordering::Relaxed);
|
|
let nanos = SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.map(|d| d.as_nanos())
|
|
.unwrap_or(0);
|
|
let dir = std::env::temp_dir().join(format!(
|
|
"alkblobs-conformance-{}-{:x}-{seq}",
|
|
std::process::id(),
|
|
nanos
|
|
));
|
|
std::fs::create_dir_all(&dir).expect("temp root created");
|
|
dir
|
|
}
|
|
|
|
/// The binding demo factory, dual-tier mode (the ADR-012 §6.3
|
|
/// dispatch-coverage shape, `kv = mem` + `large = local` at the default
|
|
/// threshold): a fresh temp directory per `open` so two opens never
|
|
/// share state, teardown removing every root it has vended (idempotent
|
|
/// — a repeated teardown is a no-op) and safe against abandoned
|
|
/// instances (each root is private to its open, so a half-open
|
|
/// instance's root is removable the same way).
|
|
struct DualTierBinding {
|
|
roots: Mutex<Vec<PathBuf>>,
|
|
}
|
|
|
|
impl Default for DualTierBinding {
|
|
fn default() -> Self {
|
|
DualTierBinding {
|
|
roots: Mutex::new(Vec::new()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl StoreFactory for DualTierBinding {
|
|
fn name(&self) -> &'static str {
|
|
"dual-tier mem+local (binding demo)"
|
|
}
|
|
|
|
async fn open(&self) -> Result<Store, Error> {
|
|
let path = fresh_temp_root();
|
|
let store = Store::dual_tier_mem_local(path.clone(), DEFAULT_DISPATCH_THRESHOLD).await?;
|
|
self.roots
|
|
.lock()
|
|
.unwrap_or_else(|e| e.into_inner())
|
|
.push(path);
|
|
Ok(store)
|
|
}
|
|
|
|
async fn teardown(&self) -> Result<(), Error> {
|
|
for root in self
|
|
.roots
|
|
.lock()
|
|
.unwrap_or_else(|e| e.into_inner())
|
|
.drain(..)
|
|
{
|
|
match std::fs::remove_dir_all(&root) {
|
|
Ok(()) => {}
|
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
|
|
Err(e) => return Err(Error::Io(e.to_string())),
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// The binding holds: the demo factory vends the concrete facade in
|
|
/// the mem-only mode, reports its identity, and tears down idempotently.
|
|
#[tokio::test]
|
|
async fn binding_factory_vends_the_concrete_store_facade() {
|
|
let factory = MemOnlyBinding;
|
|
assert_eq!(factory.name(), "mem-only (binding demo)");
|
|
let store = factory.open().await.expect("open vends the store");
|
|
assert_eq!(store.mode(), StoreMode::MemOnly);
|
|
factory.teardown().await.expect("teardown is idempotent");
|
|
factory.teardown().await.expect("teardown repeats cleanly");
|
|
}
|
|
|
|
/// The binding holds in dual-tier mode too: every `open` is isolated
|
|
/// (fresh backing), the vend store reports the dispatch-coverage
|
|
/// shape, and teardown across open-then-teardown cycles is idempotent.
|
|
#[tokio::test]
|
|
async fn dual_tier_binding_factory_vends_isolated_stores() {
|
|
let factory = DualTierBinding::default();
|
|
assert_eq!(factory.name(), "dual-tier mem+local (binding demo)");
|
|
|
|
for _ in 0..2 {
|
|
let store = factory.open().await.expect("open vends the store");
|
|
assert_eq!(store.mode(), StoreMode::DualTier, "dispatch-coverage shape");
|
|
}
|
|
factory
|
|
.teardown()
|
|
.await
|
|
.expect("teardown removes the roots");
|
|
factory.teardown().await.expect("teardown repeats cleanly");
|
|
}
|