Recipe 46: Shared List
What You Build
FabricVec<T> is Vec<T> semantics over leased fabric memory. A tasklet holding the memory lease can push items and later readers can observe those writes from the same fabric-backed vector. Ordering is the order of successful push calls on that vector.
Source
cookbook/recipe-46-shared-list/ in the source tree.
Core grafOS API Path
The list is a FabricVec<T> over a memory lease. The convenience
with_capacity constructor acquires the lease for you; the explicit path makes
the ownership visible:
use grafos_collections::vec::FabricVec;use grafos_std::mem::MemBuilder;
let capacity = 4usize;let stride = 64usize;let bytes = 24 + capacity as u64 * stride as u64;let lease = MemBuilder::new() .min_bytes(bytes) .lease_secs(300) .acquire()?;
let mut list = FabricVec::<String>::new(lease, stride)?;list.push(&"first".to_string())?;list.push(&"second".to_string())?;
let observed = list.iter().collect::<grafos_std::error::Result<Vec<String>>>()?;assert_eq!(observed, vec!["first".to_string(), "second".to_string()]);# Ok::<(), grafos_std::FabricError>(())The recipe helper packages the append and readback:
pub fn compute(input: &[u8]) -> Result<AppendOutput, &'static str> { let mut parsed: AppendInput = serde_json::from_slice(input).map_err(|_| "invalid_input")?; if parsed.to_append.is_empty() { return Err("empty_append"); } if parsed.to_append.len() > 1024 { return Err("append_too_large"); } parsed.current.push(parsed.to_append); let new_index = parsed.current.len() - 1; Ok(AppendOutput { list: parsed.current, new_index })}
pub fn append_fabric_vec(list: &mut FabricVec<String>, value: String) -> FabricResult<AppendOutput> { list.push(&value)?; let new_index = list.len() - 1; let list = list.iter().collect::<FabricResult<Vec<String>>>()?; Ok(AppendOutput { list, new_index })}What’s interesting
- Bounded inputs. Every distributed-collection recipe should bound input size; an unbounded append from one writer can starve memory the other writer needs.
- Index returned. The position the new entry landed at is part of the response. In production this is what
FabricVec::push()returns — a unique slot index that the lease generation makes safe to reference. - Real fabric path.
append_fabric_veccallsFabricVec::pushand then reads the observed state withFabricVec::iter.
Failure Behavior
- Invalid JSON returns
invalid_input. - Empty appends return
empty_append. - Values larger than the recipe’s 1024 byte input cap return
append_too_large. FabricVec::pushcan returnCapacityExceededif the serialized element is wider than the configured stride or if the fabric cannot grow the vector.
Run And Verify
cargo test -p cookbook-recipe-46-shared-listExpected: the tests cover appending to an empty list, appending to an existing list, rejecting an empty append, and the real FabricVec helper path.
Adapt It
Change the element type, stride, and capacity to match the data you store. Keep the returned index in the response when callers need an idempotent pointer to the item they just appended.