Skip to content

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

  1. Bounded inputs. Every distributed-collection recipe should bound input size; an unbounded append from one writer can starve memory the other writer needs.
  2. 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.
  3. Real fabric path. append_fabric_vec calls FabricVec::push and then reads the observed state with FabricVec::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::push can return CapacityExceeded if the serialized element is wider than the configured stride or if the fabric cannot grow the vector.

Run And Verify

Terminal window
cargo test -p cookbook-recipe-46-shared-list

Expected: 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.