/* 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/. */
externcrate xpcom;
use crossbeam_utils::atomic::AtomicCell; use moz_task::Task; use nserror::{nsresult, NS_ERROR_FAILURE}; use nsstring::{nsCString, nsString}; use rkv::backend::{
BackendEnvironmentBuilder, BackendInfo, RecoveryStrategy, SafeMode, SafeModeDatabase,
SafeModeEnvironment,
}; use rkv::{OwnedValue, StoreError, StoreOptions, Value}; use std::{
str,
sync::{Arc, RwLock},
}; use storage_variant::VariantType; use xpcom::{
interfaces::{
nsIKeyValueDatabaseCallback, nsIKeyValueEnumeratorCallback, nsIKeyValueVariantCallback,
nsIKeyValueVoidCallback, nsIVariant,
},
RefPtr, ThreadBoundRefPtr,
};
type Manager = rkv::Manager<SafeModeEnvironment>; type Rkv = rkv::Rkv<SafeModeEnvironment>; type SingleStore = rkv::SingleStore<SafeModeDatabase>;
/// A macro to generate a done() implementation for a Task. /// Takes one argument that specifies the type of the Task's callback function: /// value: a callback function that takes a value /// void: the callback function doesn't take a value /// /// The "value" variant calls self.convert() to convert a successful result /// into the value to pass to the callback function. So if you generate done() /// for a callback that takes a value, ensure you also implement convert()!
macro_rules! task_done {
(value) => { fn done(&self) -> Result<(), nsresult> { // If TaskRunnable calls Task.done() to return a result on the // main thread before TaskRunnable returns on the database thread, // then the Task will get dropped on the database thread. // // But the callback is an nsXPCWrappedJS that isn't safe to release // on the database thread. So we move it out of the Task here to ensure // it gets released on the main thread. let threadbound = self.callback.swap(None).ok_or(NS_ERROR_FAILURE)?; let callback = threadbound.get_ref().ok_or(NS_ERROR_FAILURE)?;
(void) => { fn done(&self) -> Result<(), nsresult> { // If TaskRunnable calls Task.done() to return a result on the // main thread before TaskRunnable returns on the database thread, // then the Task will get dropped on the database thread. // // But the callback is an nsXPCWrappedJS that isn't safe to release // on the database thread. So we move it out of the Task here to ensure // it gets released on the main thread. let threadbound = self.callback.swap(None).ok_or(NS_ERROR_FAILURE)?; let callback = threadbound.get_ref().ok_or(NS_ERROR_FAILURE)?;
/// A tuple comprising an Arc<RwLock<Rkv>> and a SingleStore, which is /// the result of GetOrCreateTask. We declare this type because otherwise /// Clippy complains "error: very complex type used. Consider factoring /// parts into `type` definitions" (i.e. clippy::type-complexity) when we /// declare the type of `GetOrCreateTask::result`. type RkvStoreTuple = (Arc<RwLock<Rkv>>, SingleStore);
// The threshold for active resizing. const RESIZE_RATIO: f32 = 0.85;
/// The threshold (50 MB) to switch the resizing policy from the double size to /// the constant increment for active resizing. const INCREMENTAL_RESIZE_THRESHOLD: usize = 52_428_800;
/// The RKV disk page size and mask. const PAGE_SIZE: usize = 4096; const PAGE_SIZE_MASK: usize = 0b_1111_1111_1111;
/// Round the non-zero size to the multiple of page size greater or equal. /// /// It does not handle the special cases such as size zero and overflow, /// because even if that happens (extremely unlikely though), RKV will /// ignore the new size if it's smaller than the current size. /// /// E.g: /// [ 1 - 4096] -> 4096, /// [4097 - 8192] -> 8192, /// [8193 - 12286] -> 12286, fn round_to_pagesize(size: usize) -> usize { if size & PAGE_SIZE_MASK == 0 {
size
} else {
(size & !PAGE_SIZE_MASK) + PAGE_SIZE
}
}
/// Kvstore employes two auto resizing strategies: active and passive resizing. /// They work together to liberate consumers from having to guess the "proper" /// size of the store upfront. See more detail about this in Bug 1543861. /// /// Active resizing that is performed at the store startup. /// /// It either increases the size in double, or by a constant size if its size /// reaches INCREMENTAL_RESIZE_THRESHOLD. /// /// Note that on Linux / MAC OSX, the increased size would only take effect if /// there is a write transaction committed afterwards. fn active_resize(env: &Rkv) -> Result<(), StoreError> { let info = env.info()?; let current_size = info.map_size();
let size = if current_size < INCREMENTAL_RESIZE_THRESHOLD {
current_size << 1
} else {
current_size + INCREMENTAL_RESIZE_STEP
};
env.set_map_size(size)?;
Ok(())
}
/// Passive resizing that is performed when the MAP_FULL error occurs. It /// increases the store with a `wanted` size. /// /// Note that the `wanted` size must be rounded to a multiple of page size /// by using `round_to_pagesize`. fn passive_resize(env: &Rkv, wanted: usize) -> Result<(), StoreError> { let info = env.info()?; let current_size = info.map_size();
env.set_map_size(current_size + wanted)?;
Ok(())
}
impl Task for GetOrCreateWithOptionsTask { fn run(&self) { // We do the work within a closure that returns a Result so we can // use the ? operator to simplify the implementation. self.result
.store(Some(|| -> Result<RkvStoreTuple, KeyValueError> { let store; letmut builder = Rkv::environment_builder::<SafeMode>();
builder.set_corruption_recovery_strategy(self.strategy); letmut manager = Manager::singleton().write()?; let path = WidePathBuf::new(&self.path).canonicalize()?; let rkv = manager.get_or_create_from_builder(
path.as_path(),
builder,
Rkv::from_builder::<SafeMode>,
)?;
{ let env = rkv.read()?; let load_ratio = env.load_ratio()?.unwrap_or(0.0); if load_ratio > RESIZE_RATIO {
active_resize(&env)?;
}
store = env.open_single(str::from_utf8(&self.name)?, StoreOptions::create())?;
}
Ok((rkv, store))
}()));
}
impl Task for PutTask { fn run(&self) { // We do the work within a closure that returns a Result so we can // use the ? operator to simplify the implementation. self.result.store(Some(|| -> Result<(), KeyValueError> { let env = self.rkv.read()?; let key = str::from_utf8(&self.key)?; let v = Value::from(&self.value); letmut resized = false;
// Use a loop here in case we want to retry from a recoverable // error such as `StoreError::MapFull`. loop { letmut writer = env.write()?;
// Only handle the first MapFull error via passive resizing. // Propogate the subsequent MapFull error.
Err(StoreError::MapFull) if !resized => { // abort the failed transaction for resizing.
writer.abort();
// calculate the size of pairs and resize the store accordingly. let pair_size =
key.len() + v.serialized_size().map_err(StoreError::from)? as usize; let wanted = round_to_pagesize(pair_size);
passive_resize(&env, wanted)?;
resized = true; continue;
}
fn calc_pair_size(&self) -> Result<usize, StoreError> { letmut total = 0;
for (key, value) inself.pairs.iter() { iflet Some(val) = value {
total += key.len();
total += Value::from(val)
.serialized_size()
.map_err(StoreError::from)? as usize;
}
}
Ok(total)
}
}
impl Task for WriteManyTask { fn run(&self) { // We do the work within a closure that returns a Result so we can // use the ? operator to simplify the implementation. self.result.store(Some(|| -> Result<(), KeyValueError> { let env = self.rkv.read()?; letmut resized = false;
// Use a loop here in case we want to retry from a recoverable // error such as `StoreError::MapFull`. 'outer: loop { letmut writer = env.write()?;
for (key, value) inself.pairs.iter() { let key = str::from_utf8(key)?; match value { // To put.
Some(val) => { matchself.store.put(&mut writer, key, &Value::from(val)) {
Ok(_) => (),
// Only handle the first MapFull error via passive resizing. // Propogate the subsequent MapFull error.
Err(StoreError::MapFull) if !resized => { // Abort the failed transaction for resizing.
writer.abort();
// Calculate the size of pairs and resize accordingly. let pair_size = self.calc_pair_size()?; let wanted = round_to_pagesize(pair_size);
passive_resize(&env, wanted)?;
resized = true; continue'outer;
}
// RKV fails with an error if the key to delete wasn't found, // and Rkv returns that error, but we ignore it, as we expect most // of our consumers to want this behavior.
Err(StoreError::KeyValuePairNotFound) => (),
impl Task for GetTask { fn run(&self) { // We do the work within a closure that returns a Result so we can // use the ? operator to simplify the implementation. self.result
.store(Some(|| -> Result<Option<OwnedValue>, KeyValueError> { let key = str::from_utf8(&self.key)?; let env = self.rkv.read()?; let reader = env.read()?; let value = self.store.get(&reader, key)?;
impl Task for HasTask { fn run(&self) { // We do the work within a closure that returns a Result so we can // use the ? operator to simplify the implementation. self.result.store(Some(|| -> Result<bool, KeyValueError> { let key = str::from_utf8(&self.key)?; let env = self.rkv.read()?; let reader = env.read()?; let value = self.store.get(&reader, key)?;
Ok(value.is_some())
}()));
}
impl Task for DeleteTask { fn run(&self) { // We do the work within a closure that returns a Result so we can // use the ? operator to simplify the implementation. self.result.store(Some(|| -> Result<(), KeyValueError> { let key = str::from_utf8(&self.key)?; let env = self.rkv.read()?; letmut writer = env.write()?;
// RKV fails with an error if the key to delete wasn't found, // and Rkv returns that error, but we ignore it, as we expect most // of our consumers to want this behavior.
Err(StoreError::KeyValuePairNotFound) => (),
impl Task for ClearTask { fn run(&self) { // We do the work within a closure that returns a Result so we can // use the ? operator to simplify the implementation. self.result.store(Some(|| -> Result<(), KeyValueError> { let env = self.rkv.read()?; letmut writer = env.write()?; self.store.clear(&mut writer)?; // Ignore errors caused by simultaneous access. // We intend to investigate/revert this in bug 1810212. match writer.commit() {
Err(StoreError::IoError(e)) if e.kind() == std::io::ErrorKind::NotFound => { // Explicitly ignore errors from simultaneous access.
}
Err(e) => return Err(From::from(e)),
_ => (),
};
impl Task for EnumerateTask { fn run(&self) { // We do the work within a closure that returns a Result so we can // use the ? operator to simplify the implementation. self.result.store(Some(
|| -> Result<Vec<KeyValuePairResult>, KeyValueError> { let env = self.rkv.read()?; let reader = env.read()?; let from_key = str::from_utf8(&self.from_key)?; let to_key = str::from_utf8(&self.to_key)?;
let iterator = if from_key.is_empty() { self.store.iter_start(&reader)?
} else { self.store.iter_from(&reader, &from_key)?
};
// Ideally, we'd enumerate pairs lazily, as the consumer calls // nsIKeyValueEnumerator.getNext(), which calls our // KeyValueEnumerator.get_next() implementation. But KeyValueEnumerator // can't reference the Iter because Rust "cannot #[derive(xpcom)] // on a generic type," and the Iter requires a lifetime parameter, // which would make KeyValueEnumerator generic. // // Our fallback approach is to eagerly collect the iterator // into a collection that KeyValueEnumerator owns. Fixing this so we // enumerate pairs lazily is bug 1499252. let pairs: Vec<KeyValuePairResult> = iterator // Convert the key to a string so we can compare it to the "to" key. // For forward compatibility, we don't fail here if we can't convert // a key to UTF-8. Instead, we store the Err in the collection // and fail lazily in KeyValueEnumerator.get_next().
.map(|result| match result {
Ok((key, val)) => Ok((str::from_utf8(&key), val)),
Err(err) => Err(err),
}) // Stop iterating once we reach the to_key, if any.
.take_while(|result| match result {
Ok((key, _val)) => { if to_key.is_empty() { true
} else { match *key {
Ok(key) => key < to_key,
Err(_err) => true,
}
}
}
Err(_) => true,
}) // Convert the key/value pair to owned.
.map(|result| match result {
Ok((key, val)) => match (key, val) {
(Ok(key), val) => Ok((key.to_owned(), OwnedValue::from(&val))),
(Err(err), _) => Err(err.into()),
},
Err(err) => Err(KeyValueError::StoreError(err)),
})
.collect();
Ok(pairs)
}(),
));
}
task_done!(value);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.2 Sekunden
(vorverarbeitet am 2026-06-18)
¤
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.