use std::collections::{BTreeMap, BTreeSet};
use std::sync::{Arc, Mutex, MutexGuard};
use crate::control::{BlobId, ControlStore};
use crate::error::{Error, Result};
pub const MAX_ATTEMPTS: usize = 8;
fn retry_backoff(attempt: usize) -> std::time::Duration {
let jitter = (crate::model::now_ms() as u64 % (attempt as u64 + 1)) as u64;
std::time::Duration::from_millis(attempt as u64 + jitter)
}
const ARITY: usize = 32;
const LEAVES: usize = ARITY * ARITY;
fn leaf_of(key: &str) -> usize {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for byte in key.as_bytes() {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
(hash % LEAVES as u64) as usize
}
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
struct StoredMap {
#[serde(default, skip_serializing_if = "Option::is_none")]
root: Option<String>,
#[serde(default)]
len: usize,
}
struct Loaded<V> {
root: Option<Arc<BTreeMap<usize, BlobId>>>,
branches: BTreeMap<usize, Arc<BTreeMap<usize, BlobId>>>,
leaves: BTreeMap<usize, Arc<BTreeMap<String, V>>>,
}
impl<V> Default for Loaded<V> {
fn default() -> Self {
Loaded {
root: None,
branches: BTreeMap::new(),
leaves: BTreeMap::new(),
}
}
}
impl<V> Clone for Loaded<V> {
fn clone(&self) -> Self {
Loaded {
root: self.root.clone(),
branches: self.branches.clone(),
leaves: self.leaves.clone(),
}
}
}
pub(crate) struct ShardedMap<V> {
stored: StoredMap,
store: Option<Arc<dyn ControlStore>>,
loaded: Mutex<Loaded<V>>,
dirty: BTreeSet<usize>,
}
impl<V> Default for ShardedMap<V> {
fn default() -> Self {
ShardedMap {
stored: StoredMap::default(),
store: None,
loaded: Mutex::new(Loaded::default()),
dirty: BTreeSet::new(),
}
}
}
impl<V> std::fmt::Debug for ShardedMap<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ShardedMap")
.field("len", &self.stored.len)
.field("root", &self.stored.root)
.finish()
}
}
impl<V> Clone for ShardedMap<V> {
fn clone(&self) -> Self {
ShardedMap {
stored: self.stored.clone(),
store: self.store.clone(),
loaded: Mutex::new(guard(&self.loaded).clone()),
dirty: self.dirty.clone(),
}
}
}
impl<V> serde::Serialize for ShardedMap<V> {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
if !self.dirty.is_empty() {
return Err(serde::ser::Error::custom(
"a sharded map was serialised with unwritten shards; Doc::flush was not called",
));
}
self.stored.serialize(serializer)
}
}
impl<'de, V> serde::Deserialize<'de> for ShardedMap<V> {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
Ok(ShardedMap {
stored: StoredMap::deserialize(deserializer)?,
..ShardedMap::default()
})
}
}
fn guard<T>(lock: &Mutex<T>) -> MutexGuard<'_, T> {
lock.lock().unwrap_or_else(|poison| poison.into_inner())
}
fn node_bytes(node: &BTreeMap<usize, BlobId>) -> Result<Vec<u8>> {
let encoded: BTreeMap<String, String> = node
.iter()
.map(|(slot, id)| (slot.to_string(), id.to_string()))
.collect();
Ok(serde_json::to_vec(&encoded)?)
}
fn parse_node(bytes: &[u8], id: &BlobId) -> Result<BTreeMap<usize, BlobId>> {
let encoded: BTreeMap<String, String> =
serde_json::from_slice(bytes).map_err(|err| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: format!("node {id} is not a trie node: {err}"),
})?;
let mut out = BTreeMap::new();
for (slot, child) in encoded {
let slot: usize = slot.parse().map_err(|_| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: format!("node {id} has a non-numeric slot {slot:?}"),
})?;
let child = BlobId::from_hex(&child).ok_or_else(|| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: format!("node {id} slot {slot} is not a blob id: {child:?}"),
})?;
out.insert(slot, child);
}
Ok(out)
}
fn read_blob<T: serde::de::DeserializeOwned>(
store: &dyn ControlStore,
id: &BlobId,
what: &str,
) -> Result<T> {
let bytes = store.get_blob(id)?.ok_or_else(|| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: format!("{what} {id} is not in the control store"),
})?;
serde_json::from_slice(&bytes).map_err(|err| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: format!("{what} {id} could not be read: {err}"),
})
}
impl<V> ShardedMap<V>
where
V: Clone + serde::Serialize + serde::de::DeserializeOwned,
{
fn store(&self) -> Result<&Arc<dyn ControlStore>> {
self.store.as_ref().ok_or_else(|| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: "a sharded map was used before it was bound to a control store".to_owned(),
})
}
pub(crate) fn bind(&mut self, store: &Arc<dyn ControlStore>) {
self.store = Some(Arc::clone(store));
}
fn root_node(
loaded: &mut Loaded<V>,
stored: &StoredMap,
store: &dyn ControlStore,
) -> Result<Arc<BTreeMap<usize, BlobId>>> {
if let Some(root) = &loaded.root {
return Ok(Arc::clone(root));
}
let node = match &stored.root {
None => BTreeMap::new(),
Some(hex) => {
let id = BlobId::from_hex(hex).ok_or_else(|| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: format!("the trie root {hex:?} is not a blob id"),
})?;
let bytes = store.get_blob(&id)?.ok_or_else(|| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: format!("the trie root {id} is not in the control store"),
})?;
parse_node(&bytes, &id)?
}
};
let node = Arc::new(node);
loaded.root = Some(Arc::clone(&node));
Ok(node)
}
fn branch_node(
loaded: &mut Loaded<V>,
stored: &StoredMap,
store: &dyn ControlStore,
branch: usize,
) -> Result<Arc<BTreeMap<usize, BlobId>>> {
if let Some(node) = loaded.branches.get(&branch) {
return Ok(Arc::clone(node));
}
let root = Self::root_node(loaded, stored, store)?;
let node = match root.get(&branch) {
None => BTreeMap::new(),
Some(id) => {
let bytes = store.get_blob(id)?.ok_or_else(|| Error::CorruptDocument {
ref_name: "<shard trie>".to_owned(),
reason: format!("trie branch {id} is not in the control store"),
})?;
parse_node(&bytes, id)?
}
};
let node = Arc::new(node);
loaded.branches.insert(branch, Arc::clone(&node));
Ok(node)
}
fn leaf_map(
loaded: &mut Loaded<V>,
stored: &StoredMap,
store: &dyn ControlStore,
slot: usize,
) -> Result<Arc<BTreeMap<String, V>>> {
if let Some(leaf) = loaded.leaves.get(&slot) {
return Ok(Arc::clone(leaf));
}
let branch = Self::branch_node(loaded, stored, store, slot / ARITY)?;
let leaf = match branch.get(&(slot % ARITY)) {
None => BTreeMap::new(),
Some(id) => read_blob(store, id, "trie leaf")?,
};
let leaf = Arc::new(leaf);
loaded.leaves.insert(slot, Arc::clone(&leaf));
Ok(leaf)
}
fn leaf(&self, slot: usize) -> Result<Arc<BTreeMap<String, V>>> {
let store = Arc::clone(self.store()?);
let mut loaded = guard(&self.loaded);
Self::leaf_map(&mut loaded, &self.stored, store.as_ref(), slot)
}
pub(crate) fn len(&self) -> usize {
self.stored.len
}
pub(crate) fn is_empty(&self) -> bool {
self.stored.len == 0
}
pub(crate) fn get(&self, key: &str) -> Result<Option<V>> {
Ok(self.leaf(leaf_of(key))?.get(key).cloned())
}
pub(crate) fn contains_key(&self, key: &str) -> Result<bool> {
Ok(self.leaf(leaf_of(key))?.contains_key(key))
}
pub(crate) fn insert(&mut self, key: String, value: V) -> Result<Option<V>> {
let slot = leaf_of(&key);
let leaf = self.leaf(slot)?;
let mut next = (*leaf).clone();
let previous = next.insert(key, value);
if previous.is_none() {
self.stored.len += 1;
}
guard(&self.loaded).leaves.insert(slot, Arc::new(next));
self.dirty.insert(slot);
Ok(previous)
}
pub(crate) fn remove(&mut self, key: &str) -> Result<Option<V>> {
let slot = leaf_of(key);
let leaf = self.leaf(slot)?;
if !leaf.contains_key(key) {
return Ok(None);
}
let mut next = (*leaf).clone();
let previous = next.remove(key);
self.stored.len -= 1;
guard(&self.loaded).leaves.insert(slot, Arc::new(next));
self.dirty.insert(slot);
Ok(previous)
}
pub(crate) fn modify(&mut self, key: &str, edit: impl FnOnce(&mut V)) -> Result<bool> {
let slot = leaf_of(key);
let leaf = self.leaf(slot)?;
if !leaf.contains_key(key) {
return Ok(false);
}
let mut next = (*leaf).clone();
if let Some(value) = next.get_mut(key) {
edit(value);
}
guard(&self.loaded).leaves.insert(slot, Arc::new(next));
self.dirty.insert(slot);
Ok(true)
}
pub(crate) fn entries(&self) -> Result<BTreeMap<String, V>> {
let mut out = BTreeMap::new();
for leaf in self.all_leaves()? {
for (key, value) in leaf.iter() {
out.insert(key.clone(), value.clone());
}
}
Ok(out)
}
fn all_leaves(&self) -> Result<Vec<Arc<BTreeMap<String, V>>>> {
let store = Arc::clone(self.store()?);
let mut loaded = guard(&self.loaded);
let root = Self::root_node(&mut loaded, &self.stored, store.as_ref())?;
let branches: Vec<usize> = root.keys().copied().collect();
let mut leaves = Vec::new();
for branch in branches {
let node = Self::branch_node(&mut loaded, &self.stored, store.as_ref(), branch)?;
for child in node.keys().copied().collect::<Vec<_>>() {
leaves.push(Self::leaf_map(
&mut loaded,
&self.stored,
store.as_ref(),
branch * ARITY + child,
)?);
}
}
Ok(leaves)
}
pub(crate) fn any(&self, mut hit: impl FnMut(&str, &V) -> bool) -> Result<bool> {
for leaf in self.all_leaves()? {
if leaf.iter().any(|(key, value)| hit(key, value)) {
return Ok(true);
}
}
Ok(false)
}
pub(crate) fn values(&self) -> Result<Vec<V>> {
Ok(self.entries()?.into_values().collect())
}
pub(crate) fn keys(&self) -> Result<Vec<String>> {
Ok(self.entries()?.into_keys().collect())
}
pub(crate) fn retain(&mut self, mut keep: impl FnMut(&str, &V) -> bool) -> Result<()> {
let doomed: Vec<String> = self
.entries()?
.into_iter()
.filter(|(key, value)| !keep(key, value))
.map(|(key, _)| key)
.collect();
for key in doomed {
self.remove(&key)?;
}
Ok(())
}
pub(crate) fn flush(&mut self) -> Result<()> {
if self.dirty.is_empty() {
return Ok(());
}
let store = Arc::clone(self.store()?);
let store = store.as_ref();
let mut loaded = guard(&self.loaded);
let mut root = (*Self::root_node(&mut loaded, &self.stored, store)?).clone();
let branches: BTreeSet<usize> = self.dirty.iter().map(|slot| slot / ARITY).collect();
for branch in branches {
let mut node = (*Self::branch_node(&mut loaded, &self.stored, store, branch)?).clone();
for slot in self
.dirty
.iter()
.copied()
.filter(|slot| slot / ARITY == branch)
{
let leaf = Self::leaf_map(&mut loaded, &self.stored, store, slot)?;
if leaf.is_empty() {
node.remove(&(slot % ARITY));
} else {
let bytes = serde_json::to_vec(&*leaf)?;
let id = store.put_blob(&bytes)?;
node.insert(slot % ARITY, id);
}
}
if node.is_empty() {
root.remove(&branch);
} else {
let id = store.put_blob(&node_bytes(&node)?)?;
root.insert(branch, id);
}
loaded.branches.insert(branch, Arc::new(node));
}
self.stored.root = if root.is_empty() {
None
} else {
let id = store.put_blob(&node_bytes(&root)?)?;
Some(id.to_string())
};
loaded.root = Some(Arc::new(root));
drop(loaded);
self.dirty.clear();
Ok(())
}
}
pub(crate) trait Doc:
serde::Serialize + serde::de::DeserializeOwned + Default + Clone + Send + Sync + 'static
{
fn bind(&mut self, store: &Arc<dyn ControlStore>);
fn flush(&mut self) -> Result<()>;
}
macro_rules! impl_doc {
($doc:ty { $($field:ident),+ $(,)? }) => {
impl crate::doc::Doc for $doc {
fn bind(&mut self, store: &std::sync::Arc<dyn crate::control::ControlStore>) {
$(self.$field.bind(store);)+
}
fn flush(&mut self) -> crate::error::Result<()> {
$(self.$field.flush()?;)+
Ok(())
}
}
};
}
pub(crate) use impl_doc;
struct Cached<T> {
version: Option<BlobId>,
doc: Arc<T>,
}
pub(crate) struct DocRef<T> {
store: Arc<dyn ControlStore>,
name: std::borrow::Cow<'static, str>,
cache: Arc<Mutex<Option<Cached<T>>>>,
writers: Arc<Mutex<()>>,
}
impl<T> std::fmt::Debug for DocRef<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DocRef").field("name", &self.name).finish()
}
}
impl<T> Clone for DocRef<T> {
fn clone(&self) -> Self {
DocRef {
store: Arc::clone(&self.store),
name: self.name.clone(),
cache: Arc::clone(&self.cache),
writers: Arc::clone(&self.writers),
}
}
}
impl<T: Doc> DocRef<T> {
pub(crate) fn new(store: Arc<dyn ControlStore>, name: &'static str) -> Result<Self> {
Self::named(store, std::borrow::Cow::Borrowed(name))
}
pub(crate) fn named(
store: Arc<dyn ControlStore>,
name: std::borrow::Cow<'static, str>,
) -> Result<Self> {
Ok(DocRef {
store,
name,
cache: Arc::new(Mutex::new(None)),
writers: Arc::new(Mutex::new(())),
})
}
pub(crate) fn ref_name(&self) -> &str {
&self.name
}
pub(crate) fn version(&self) -> Result<Option<crate::control::BlobId>> {
self.store.read_doc(&self.name)
}
pub(crate) fn history(&self) -> Result<Vec<crate::control::DocVersion>> {
self.store.history(&self.name)
}
fn empty(&self) -> T {
let mut doc = T::default();
doc.bind(&self.store);
doc
}
fn read(&self) -> Result<(Option<BlobId>, Arc<T>)> {
let version = self.store.read_doc(&self.name)?;
if let Some(cached) = guard(&self.cache).as_ref() {
if cached.version == version {
return Ok((version, Arc::clone(&cached.doc)));
}
}
let doc = match &version {
None => self.empty(),
Some(id) => {
let bytes = self.store.get_blob(id)?.ok_or_else(|| {
Error::CorruptDocument {
ref_name: self.name.to_string(),
reason: format!("points at {id}, which is not in the control store"),
}
})?;
let mut doc: T =
serde_json::from_slice(&bytes).map_err(|err| Error::CorruptDocument {
ref_name: self.name.to_string(),
reason: format!("blob {id} is not a valid document: {err}"),
})?;
doc.bind(&self.store);
doc
}
};
let doc = Arc::new(doc);
self.remember(version, Arc::clone(&doc));
Ok((version, doc))
}
fn remember(&self, version: Option<BlobId>, doc: Arc<T>) {
*guard(&self.cache) = Some(Cached { version, doc });
}
pub(crate) fn load(&self) -> Result<Arc<T>> {
Ok(self.read()?.1)
}
pub(crate) fn update<R>(&self, mut edit: impl FnMut(&mut T) -> Result<R>) -> Result<R> {
let span = tracing::debug_span!(
target: crate::observe::TARGET_STORE,
"store.doc_txn",
document = &*self.name,
);
let _entered = span.enter();
let started = std::time::Instant::now();
let _turn = self
.writers
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let queued_ms = started.elapsed().as_millis() as u64;
for attempt in 1..=MAX_ATTEMPTS {
let (previous, current) = self.read()?;
let mut doc = (*current).clone();
let out = edit(&mut doc)?;
doc.flush()?;
let bytes = serde_json::to_vec(&doc)?;
let new = self.store.put_blob(&bytes)?;
if previous == Some(new) {
tracing::debug!(
target: crate::observe::TARGET_STORE,
attempt,
elapsed_ms = started.elapsed().as_millis() as u64,
outcome = "unchanged",
"control document rewrote to the same bytes; the pointer was not moved"
);
return Ok(out);
}
if self.store.commit_doc(&self.name, previous, new)? {
self.remember(Some(new), Arc::new(doc));
tracing::debug!(
target: crate::observe::TARGET_STORE,
attempt,
queued_ms,
elapsed_ms = started.elapsed().as_millis() as u64,
bytes = bytes.len(),
outcome = "committed",
"control document committed"
);
return Ok(out);
}
if attempt >= 2 {
tracing::warn!(
target: crate::observe::TARGET_STORE,
attempt,
elapsed_ms = started.elapsed().as_millis() as u64,
"control document changed under us AGAIN; retrying"
);
} else {
tracing::debug!(
target: crate::observe::TARGET_STORE,
attempt,
"control document changed under us; retrying"
);
}
std::thread::sleep(retry_backoff(attempt + 1));
}
tracing::error!(
target: crate::observe::TARGET_STORE,
attempts = MAX_ATTEMPTS,
elapsed_ms = started.elapsed().as_millis() as u64,
"the control document lost every compare-and-swap; giving up"
);
Err(Error::Contended {
ref_name: self.name.to_string(),
attempts: MAX_ATTEMPTS,
})
}
}