diff options
Diffstat (limited to 'toolkit/components/kvstore')
-rw-r--r-- | toolkit/components/kvstore/kvstore.sys.mjs | 24 | ||||
-rw-r--r-- | toolkit/components/kvstore/nsIKeyValue.idl | 16 | ||||
-rw-r--r-- | toolkit/components/kvstore/src/lib.rs | 44 | ||||
-rw-r--r-- | toolkit/components/kvstore/src/task.rs | 26 | ||||
-rw-r--r-- | toolkit/components/kvstore/test/xpcshell/test_kvstore.js | 74 |
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"); |