summaryrefslogtreecommitdiffstats
path: root/toolkit/components/kvstore
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/kvstore')
-rw-r--r--toolkit/components/kvstore/kvstore.sys.mjs24
-rw-r--r--toolkit/components/kvstore/nsIKeyValue.idl16
-rw-r--r--toolkit/components/kvstore/src/lib.rs44
-rw-r--r--toolkit/components/kvstore/src/task.rs26
-rw-r--r--toolkit/components/kvstore/test/xpcshell/test_kvstore.js74
5 files changed, 169 insertions, 15 deletions
diff --git a/toolkit/components/kvstore/kvstore.sys.mjs b/toolkit/components/kvstore/kvstore.sys.mjs
index 838f68a5df..9085eed530 100644
--- a/toolkit/components/kvstore/kvstore.sys.mjs
+++ b/toolkit/components/kvstore/kvstore.sys.mjs
@@ -18,7 +18,8 @@ function promisify(fn, ...args) {
* with a database's path and (optionally) its name:
*
* ```
- * ChromeUtils.import("resource://gre/modules/kvstore.jsm");
+ * let { keyValueService } =
+ * ChromeUtils.importESModule("resource://gre/modules/kvstore.sys.mjs");
* let database = await KeyValueService.getOrCreate(path, name);
* ```
*
@@ -27,11 +28,32 @@ function promisify(fn, ...args) {
*/
export class KeyValueService {
+ static RecoveryStrategy = {
+ ERROR: gKeyValueService.ERROR,
+ DISCARD: gKeyValueService.DISCARD,
+ RENAME: gKeyValueService.RENAME,
+ };
+
static async getOrCreate(dir, name) {
return new KeyValueDatabase(
await promisify(gKeyValueService.getOrCreate, dir, name)
);
}
+
+ static async getOrCreateWithOptions(
+ dir,
+ name,
+ { strategy = gKeyValueService.RENAME } = {}
+ ) {
+ return new KeyValueDatabase(
+ await promisify(
+ gKeyValueService.getOrCreateWithOptions,
+ dir,
+ name,
+ strategy
+ )
+ );
+ }
}
/**
diff --git a/toolkit/components/kvstore/nsIKeyValue.idl b/toolkit/components/kvstore/nsIKeyValue.idl
index b90d45fc5a..08cd548af2 100644
--- a/toolkit/components/kvstore/nsIKeyValue.idl
+++ b/toolkit/components/kvstore/nsIKeyValue.idl
@@ -22,7 +22,7 @@ interface nsIKeyValuePair;
* for all use cases. Extension of this API to support transactions is tracked
* by bug 1499238.
*
- * The kvstore.jsm module wraps this API in a more idiomatic, Promise-based
+ * The kvstore.sys.mjs module wraps this API in a more idiomatic, Promise-based
* JS API that supports async/await. In most cases, you're better off using
* that API from JS rather than using this one directly. Bug 1512319 tracks
* native support for Promise in Rust-implemented XPCOM methods.
@@ -33,6 +33,12 @@ interface nsIKeyValuePair;
*/
[scriptable, builtinclass, rust_sync, uuid(46c893dd-4c14-4de0-b33d-a1be18c6d062)]
interface nsIKeyValueService : nsISupports {
+ cenum RecoveryStrategy: 8 {
+ ERROR,
+ DISCARD,
+ RENAME,
+ };
+
/**
* Get a handle to an existing database or a newly-created one
* at the specified path and with the given name.
@@ -46,6 +52,12 @@ interface nsIKeyValueService : nsISupports {
in nsIKeyValueDatabaseCallback callback,
in AUTF8String path,
in AUTF8String name);
+
+ void getOrCreateWithOptions(
+ in nsIKeyValueDatabaseCallback callback,
+ in AUTF8String path,
+ in AUTF8String name,
+ [optional] in nsIKeyValueService_RecoveryStrategy recoveryStrategy);
};
/**
@@ -157,7 +169,7 @@ interface nsIKeyValuePair : nsISupports {
* an nsIKeyValuePair rather than an nsISupports, so consumers don't need
* to QI it to that interface; but this interface doesn't implement the JS
* iteration protocol (because the Rust-XPCOM bindings don't yet support it),
- * which is another reason why you should use the kvstore.jsm module from JS
+ * which is another reason why you should use the kvstore.sys.mjs module from JS
* instead of accessing this API directly.
*/
[scriptable, builtinclass, rust_sync, uuid(b9ba7116-b7ff-4717-9a28-a08e6879b199)]
diff --git a/toolkit/components/kvstore/src/lib.rs b/toolkit/components/kvstore/src/lib.rs
index 5601ecb12a..c110313ad2 100644
--- a/toolkit/components/kvstore/src/lib.rs
+++ b/toolkit/components/kvstore/src/lib.rs
@@ -29,7 +29,7 @@ use moz_task::{create_background_task_queue, DispatchOptions, TaskRunnable};
use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
use nsstring::{nsACString, nsCString};
use owned_value::{owned_to_variant, variant_to_owned};
-use rkv::backend::{SafeModeDatabase, SafeModeEnvironment};
+use rkv::backend::{RecoveryStrategy, SafeModeDatabase, SafeModeEnvironment};
use rkv::OwnedValue;
use std::{
ptr,
@@ -37,14 +37,16 @@ use std::{
vec::IntoIter,
};
use task::{
- ClearTask, DeleteTask, EnumerateTask, GetOrCreateTask, GetTask, HasTask, PutTask, WriteManyTask,
+ ClearTask, DeleteTask, EnumerateTask, GetOrCreateWithOptionsTask, GetTask, HasTask, PutTask,
+ WriteManyTask,
};
use thin_vec::ThinVec;
use xpcom::{
getter_addrefs,
interfaces::{
nsIKeyValueDatabaseCallback, nsIKeyValueEnumeratorCallback, nsIKeyValuePair,
- nsIKeyValueVariantCallback, nsIKeyValueVoidCallback, nsISerialEventTarget, nsIVariant,
+ nsIKeyValueService, nsIKeyValueVariantCallback, nsIKeyValueVoidCallback,
+ nsISerialEventTarget, nsIVariant,
},
nsIID, xpcom, xpcom_method, RefPtr,
};
@@ -109,15 +111,49 @@ impl KeyValueService {
path: &nsACString,
name: &nsACString,
) -> Result<(), nsresult> {
- let task = Box::new(GetOrCreateTask::new(
+ let task = Box::new(GetOrCreateWithOptionsTask::new(
RefPtr::new(callback),
nsCString::from(path),
nsCString::from(name),
+ RecoveryStrategy::Error,
));
TaskRunnable::new("KVService::GetOrCreate", task)?
.dispatch_background_task_with_options(DispatchOptions::default().may_block(true))
}
+
+ xpcom_method!(
+ get_or_create_with_options => GetOrCreateWithOptions(
+ callback: *const nsIKeyValueDatabaseCallback,
+ path: *const nsACString,
+ name: *const nsACString,
+ strategy: u8
+ )
+ );
+
+ fn get_or_create_with_options(
+ &self,
+ callback: &nsIKeyValueDatabaseCallback,
+ path: &nsACString,
+ name: &nsACString,
+ xpidl_strategy: u8,
+ ) -> Result<(), nsresult> {
+ let strategy = match xpidl_strategy {
+ nsIKeyValueService::ERROR => RecoveryStrategy::Error,
+ nsIKeyValueService::DISCARD => RecoveryStrategy::Discard,
+ nsIKeyValueService::RENAME => RecoveryStrategy::Rename,
+ _ => return Err(NS_ERROR_FAILURE),
+ };
+ let task = Box::new(GetOrCreateWithOptionsTask::new(
+ RefPtr::new(callback),
+ nsCString::from(path),
+ nsCString::from(name),
+ strategy,
+ ));
+
+ TaskRunnable::new("KVService::GetOrCreateWithOptions", task)?
+ .dispatch_background_task_with_options(DispatchOptions::default().may_block(true))
+ }
}
#[xpcom(implement(nsIKeyValueDatabase), atomic)]
diff --git a/toolkit/components/kvstore/src/task.rs b/toolkit/components/kvstore/src/task.rs
index 3608dc9665..2e0ba02e0b 100644
--- a/toolkit/components/kvstore/src/task.rs
+++ b/toolkit/components/kvstore/src/task.rs
@@ -10,7 +10,10 @@ use moz_task::Task;
use nserror::{nsresult, NS_ERROR_FAILURE};
use nsstring::nsCString;
use owned_value::owned_to_variant;
-use rkv::backend::{BackendInfo, SafeMode, SafeModeDatabase, SafeModeEnvironment};
+use rkv::backend::{
+ BackendEnvironmentBuilder, BackendInfo, RecoveryStrategy, SafeMode, SafeModeDatabase,
+ SafeModeEnvironment,
+};
use rkv::{OwnedValue, StoreError, StoreOptions, Value};
use std::{
path::Path,
@@ -161,23 +164,26 @@ fn passive_resize(env: &Rkv, wanted: usize) -> Result<(), StoreError> {
Ok(())
}
-pub struct GetOrCreateTask {
+pub struct GetOrCreateWithOptionsTask {
callback: AtomicCell<Option<ThreadBoundRefPtr<nsIKeyValueDatabaseCallback>>>,
path: nsCString,
name: nsCString,
+ strategy: RecoveryStrategy,
result: AtomicCell<Option<Result<RkvStoreTuple, KeyValueError>>>,
}
-impl GetOrCreateTask {
+impl GetOrCreateWithOptionsTask {
pub fn new(
callback: RefPtr<nsIKeyValueDatabaseCallback>,
path: nsCString,
name: nsCString,
- ) -> GetOrCreateTask {
- GetOrCreateTask {
+ strategy: RecoveryStrategy,
+ ) -> GetOrCreateWithOptionsTask {
+ GetOrCreateWithOptionsTask {
callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(callback))),
path,
name,
+ strategy,
result: AtomicCell::default(),
}
}
@@ -187,18 +193,24 @@ impl GetOrCreateTask {
}
}
-impl Task for GetOrCreateTask {
+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;
+ let mut builder = Rkv::environment_builder::<SafeMode>();
+ builder.set_corruption_recovery_strategy(self.strategy);
let mut manager = Manager::singleton().write()?;
// Note that path canonicalization is diabled to work around crashes on Fennec:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1531887
let path = Path::new(str::from_utf8(&self.path)?);
- let rkv = manager.get_or_create(path, Rkv::new::<SafeMode>)?;
+ let rkv = manager.get_or_create_from_builder(
+ path,
+ builder,
+ Rkv::from_builder::<SafeMode>,
+ )?;
{
let env = rkv.read()?;
let load_ratio = env.load_ratio()?.unwrap_or(0.0);
diff --git a/toolkit/components/kvstore/test/xpcshell/test_kvstore.js b/toolkit/components/kvstore/test/xpcshell/test_kvstore.js
index 363feaa43a..4c591622e1 100644
--- a/toolkit/components/kvstore/test/xpcshell/test_kvstore.js
+++ b/toolkit/components/kvstore/test/xpcshell/test_kvstore.js
@@ -12,9 +12,16 @@ function run_test() {
run_next_test();
}
-async function makeDatabaseDir(name) {
+async function makeDatabaseDir(name, { mockCorrupted = false } = {}) {
const databaseDir = PathUtils.join(PathUtils.profileDir, name);
await IOUtils.makeDirectory(databaseDir);
+ if (mockCorrupted) {
+ // Mock a corrupted db.
+ await IOUtils.write(
+ PathUtils.join(databaseDir, "data.safe.bin"),
+ new Uint8Array([0x00, 0x00, 0x00, 0x00])
+ );
+ }
return databaseDir;
}
@@ -26,6 +33,71 @@ add_task(async function getService() {
Assert.ok(gKeyValueService);
});
+add_task(async function getOrCreate_defaultRecoveryStrategyError() {
+ const databaseDir = await makeDatabaseDir("getOrCreate_Error", {
+ mockCorrupted: true,
+ });
+
+ await Assert.rejects(
+ KeyValueService.getOrCreate(databaseDir, "db"),
+ /FileInvalid/
+ );
+});
+
+add_task(async function getOrCreateWithOptions_RecoveryStrategyError() {
+ const databaseDir = await makeDatabaseDir("getOrCreateWithOptions_Error", {
+ mockCorrupted: true,
+ });
+
+ await Assert.rejects(
+ KeyValueService.getOrCreateWithOptions(databaseDir, "db", {
+ strategy: KeyValueService.RecoveryStrategy.ERROR,
+ }),
+ /FileInvalid/
+ );
+});
+
+add_task(async function getOrCreateWithOptions_RecoveryStrategyRename() {
+ const databaseDir = await makeDatabaseDir("getOrCreateWithOptions_Rename", {
+ mockCorrupted: true,
+ });
+
+ const database = await KeyValueService.getOrCreateWithOptions(
+ databaseDir,
+ "db",
+ {
+ strategy: KeyValueService.RecoveryStrategy.RENAME,
+ }
+ );
+ Assert.ok(database);
+
+ Assert.ok(
+ await IOUtils.exists(PathUtils.join(databaseDir, "data.safe.bin.corrupt")),
+ "Expect corrupt file to be found"
+ );
+});
+
+add_task(async function getOrCreateWithOptions_RecoveryStrategyDiscard() {
+ const databaseDir = await makeDatabaseDir("getOrCreateWithOptions_Discard", {
+ mockCorrupted: true,
+ });
+
+ const database = await KeyValueService.getOrCreateWithOptions(
+ databaseDir,
+ "db",
+ {
+ strategy: KeyValueService.RecoveryStrategy.DISCARD,
+ }
+ );
+ Assert.ok(database);
+
+ Assert.equal(
+ await IOUtils.exists(PathUtils.join(databaseDir, "data.safe.bin.corrupt")),
+ false,
+ "Expect corrupt file to not exist"
+ );
+});
+
add_task(async function getOrCreate() {
const databaseDir = await makeDatabaseDir("getOrCreate");
const database = await KeyValueService.getOrCreate(databaseDir, "db");