/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// `std::collections::HashMap::extract_if` is unstable [1], // so we use Hashbrown. // // [1]: https://github.com/rust-lang/rust/issues/59618 use hashbrown::hash_map::{Entry, HashMap};
/// Manages access to all persistent stores. /// /// The coordinator is a singleton that lives for the lifetime of /// the process. It has two main responsibilities: /// /// 1. Decoupling the lifecycle of the stores from the /// lifecycle of the [`crate::skv::interface`] objects. /// 2. Keeping track of which objects are accessing which stores, and /// closing stores once they're no longer needed. /// /// ## Why have a coordinator? /// /// Interface objects are a little cumbersome to work with in pure Rust: /// they don't have predictable lifetimes; they can only be held by an /// [`xpcom::RefPtr`]; and they can unintentionally retain the resources /// they're managing beyond their intended use. /// /// The coordinator introduces a layer of indirection to help /// with these challenges. /// /// ## Using the coordinator /// /// When an interface object is instantiated, it either requests a new client /// from the coordinator, or creates a child of an existing client. The object /// holds on to the client, and uses it to access any stores it needs /// during its lifecycle. /// /// When the object is done accessing its stores, it invalidates its client. /// The coordinator detaches the invalidated client, and its children, /// from any open stores. This prevents the object from accessing any stores /// again, even if the [`xpcom::RefPtr`] holding the object isn't released /// right away. /// /// ## Object hierarchies /// /// Clients can have multiple children, grandchildren, great-grandchildren, /// and so on; recursively. This is useful for managing hierarchies of /// interface objects, where the child's lifecycle is the same or shorter /// than its parent's. /// /// Check out [`crate::skv::interface::KeyValueService`] and /// [`crate::skv::interface::KeyValueDatabase`] for an example of /// hierarchies in action. #[derive(Debug)] pubstruct Coordinator {
state: Mutex<CoordinatorState>,
}
/// Creates a new coordinator client. pubfn client_with_name(&self, name: &'static str) -> CoordinatorClient<'_> { letmut state = self.state.lock().unwrap();
// Make this client the new last sibling by incrementing // the first bud of the current last sibling's key. let last_sibling_key = state.clients.last_key_value().map(|(key, _)| key); let key = ClientKey::new(
last_sibling_key
.map(|key| key.first().increment())
.unwrap_or(ClientKeyBud::MIN),
); let controller = AbortController::new(); let signal = controller.signal();
state
.clients
.insert(key.clone(), ActiveClient { controller });
/// A client owned by an interface object. #[derive(Clone, Debug)] pubstruct CoordinatorClient<'a> {
coordinator: &'a Coordinator,
name: &'static str,
key: ClientKey,
signal: AbortSignal,
}
impl<'a> CoordinatorClient<'a> { /// Gets or opens a store for this client. /// /// Stores connect to the underlying physical database lazily, /// so this does _not_ block on I/O. pubfn store_for_path(&self, path: StorePath) -> Result<Arc<Store>, CoordinatorError> { letmut state = self.coordinator.state.lock().unwrap(); if !state.clients.contains_key(&self.key) { return Err(CoordinatorError::Invalidated(self.name));
} match state.stores.entry(path) {
Entry::Occupied(mut entry) => Ok(entry.get_mut().attach(self.key.clone())),
Entry::Vacant(entry) => { let store = InUseStore::new(Store::new(entry.key().clone()));
Ok(entry.insert(store).attach(self.key.clone()))
}
}
}
/// Creates a child of this client. pubfn child_with_name(
&self,
name: &'static str,
) -> Result<CoordinatorClient<'a>, CoordinatorError> { letmut state = self.coordinator.state.lock().unwrap(); if !state.clients.contains_key(&self.key) { return Err(CoordinatorError::Invalidated(self.name));
}
let child_key = { // Make this child the new last child by incrementing // the last bud of the current last child's key. let last_child_key = { let max_child_key = self.key.clone().appending(ClientKeyBud::MAX); let children = state
.clients
.range(( // Include all our descendants, but not ourselves.
Bound::Excluded(&self.key),
Bound::Included(&max_child_key),
))
.map(|(key, _)| key)
.filter(|key| { // Narrow down our descendants to immediate children.
key.len() <= max_child_key.len()
});
children.last()
}; self.key.clone().appending(
last_child_key
.map(|key| key.last().increment())
.unwrap_or(ClientKeyBud::MIN),
)
}; let controller = AbortController::new(); let signal = controller.signal();
state
.clients
.insert(child_key.clone(), ActiveClient { controller });
/// Invalidates this client. /// /// Invalidation is recursive: if the client has descendants /// (children, grandchildren, etc.), they'll be invalidated, too. /// /// If the client or any of its descendants are the last clients /// of a store, invalidating the client will also close those stores. /// **This can block on disk I/O**, so clients should not be /// invalidated on the main thread. pubfn invalidate(&self) { let (abortable_clients, closeable_stores) = { letmut state = self.coordinator.state.lock().unwrap(); let keys = { let max_child_key = self.key.clone().appending(ClientKeyBud::MAX);
state
.clients
.range( // Include ourselves and all our descendants.
&self.key..=&max_child_key,
)
.map(|(key, _)| key)
.cloned()
.collect::<Vec<_>>()
}; if keys.is_empty() { // Already invalidated; no need to do anything. return;
} let abortable_clients = keys
.iter()
.map(|key| { // Invariant: `keys` only contains keys that exist // in `clients`.
state.clients.remove(key).expect("invariant violation")
})
.collect::<Vec<_>>(); let closeable_stores = state
.stores
.extract_if(|_, store| { // Detach ourselves from every store. match store.detach(&keys) {
DetachResult::StillInUse => false, // If we detached all clients from this store, also // remove the store from the map, so that we can // close it.
DetachResult::Closeable => true,
}
})
.map(|(_, store)| store)
.collect::<Vec<_>>();
(abortable_clients, closeable_stores)
};
for client in abortable_clients {
client.controller.abort();
}
for store in closeable_stores { // Invariant: `into_inner` always succeeds for closeable stores. let store = store.into_inner().expect("invariant violation");
store.close();
}
}
/// Returns a shared reference to this client's cancellation signal. /// /// The signal will fire when the client, or any of its ancestors, /// are invalidated. pubfn signal(&self) -> AbortSignal { self.signal.clone()
}
}
/// An incrementable and totally ordered scalar value. /// /// These are called "buds" because they can form leaves and branches. #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] struct ClientKeyBud(NonZeroUsize);
/// A hierarchical key uniquely identifying a coordinator client. /// /// A key comprises one or more "buds". If clients M and N are siblings, /// then their keys only differ in the last bud. If N is a child of M, /// then N's key has one more bud than M's key. #[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)] struct ClientKey(ClientKeyBud, Box<[ClientKeyBud]>);
/// Returns the first bud of the key. fn first(&self) -> ClientKeyBud { self.0
}
/// Returns the last bud of the key. fn last(&self) -> ClientKeyBud { self.1.last().copied().unwrap_or(self.0)
}
/// Returns the number of buds in the key. fn len(&self) -> usize { 1usize.checked_add(self.1.len()).unwrap()
}
/// Consumes this key, and returns a new key with /// the given bud added to the end. fn appending(self, bud: ClientKeyBud) -> Self { letmut buds = self.1.into_vec();
buds.push(bud);
ClientKey(self.0, buds.into_boxed_slice())
}
}
/// Notes that a client with the given key /// is now using this store. /// /// If the client is already using this store, /// attaching it again is a no-op. fn attach(&mutself, key: ClientKey) -> Arc<Store> { self.attached_client_keys.insert(key);
Arc::clone(&self.store)
}
/// Notes that all the clients with the given keys /// are no longer using this store. fn detach(&mutself, keys: &[ClientKey]) -> DetachResult { for key in keys { self.attached_client_keys.remove(key);
} ifself.attached_client_keys.is_empty() {
DetachResult::Closeable
} else {
DetachResult::StillInUse
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.