Skip to content

Commit

Permalink
capsules: kv: Expose garbage_collection to userspace
Browse files Browse the repository at this point in the history
Expose the garbage collection functionality to userspace so that
userspace can cleanup deleted entries after it has marked them as
invalid.

Signed-off-by: Alistair Francis <alistair@alistair23.me>
  • Loading branch information
alistair23 committed Jan 2, 2025
1 parent 002e27a commit f58d250
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 3 deletions.
30 changes: 28 additions & 2 deletions capsules/extra/src/kv_driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ enum UserSpaceOp {
Delete,
Add,
Update,
GarbageCollect,
}

/// Contents of the grant for each app.
Expand Down Expand Up @@ -266,6 +267,11 @@ impl<'a, V: kv::KVPermissions<'a>> KVStoreDriver<'a, V> {
return e;
}
}
Some(UserSpaceOp::GarbageCollect) => {
self.kv.garbage_collect()?;
return Ok(());
}

_ => {}
}

Expand Down Expand Up @@ -465,6 +471,24 @@ impl<'a, V: kv::KVPermissions<'a>> kv::KVClient for KVStoreDriver<'a, V> {
self.processid.clear();
self.check_queue();
}

fn garbage_collection_complete(&self, result: Result<(), ErrorCode>) {
self.processid.map(move |id| {
self.apps.enter(id, move |app, upcalls| {
if app.op.contains(&UserSpaceOp::GarbageCollect) {
app.op.clear();
upcalls
.schedule_upcall(upcalls::VALUE, (errorcode::into_statuscode(result), 0, 0))
.ok();
}
})
});

// We have completed the operation so see if there is a queued operation
// to run next.
self.processid.clear();
self.check_queue();
}
}

impl<'a, V: kv::KVPermissions<'a>> SyscallDriver for KVStoreDriver<'a, V> {
Expand All @@ -479,8 +503,8 @@ impl<'a, V: kv::KVPermissions<'a>> SyscallDriver for KVStoreDriver<'a, V> {
// check if present
0 => CommandReturn::success(),

// get, set, delete, add, update
1 | 2 | 3 | 4 | 5 => {
// get, set, delete, add, update, garbage collect
1 | 2 | 3 | 4 | 5 | 6 => {
if self.processid.is_none() {
// Nothing is using the KV store, so we can handle this
// request.
Expand All @@ -491,6 +515,7 @@ impl<'a, V: kv::KVPermissions<'a>> SyscallDriver for KVStoreDriver<'a, V> {
3 => app.op.set(UserSpaceOp::Delete),
4 => app.op.set(UserSpaceOp::Add),
5 => app.op.set(UserSpaceOp::Update),
6 => app.op.set(UserSpaceOp::GarbageCollect),
_ => {}
});
let ret = self.run();
Expand Down Expand Up @@ -520,6 +545,7 @@ impl<'a, V: kv::KVPermissions<'a>> SyscallDriver for KVStoreDriver<'a, V> {
3 => app.op.set(UserSpaceOp::Delete),
4 => app.op.set(UserSpaceOp::Add),
5 => app.op.set(UserSpaceOp::Update),
6 => app.op.set(UserSpaceOp::GarbageCollect),
_ => {}
}
CommandReturn::success()
Expand Down
18 changes: 18 additions & 0 deletions capsules/extra/src/kv_store_permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum Operation {
Add,
Update,
Delete,
GarbageCollect,
}

/// Current version of the Tock K-V header.
Expand Down Expand Up @@ -288,6 +289,16 @@ impl<'a, K: kv::KV<'a>> kv::KVPermissions<'a> for KVStorePermissions<'a, K> {
}
}

fn garbage_collect(&self) -> Result<(), ErrorCode> {
if self.operation.is_some() {
return Err(ErrorCode::BUSY);
}

self.operation.set(Operation::GarbageCollect);

self.kv.garbage_collect()
}

fn header_size(&self) -> usize {
HEADER_LENGTH
}
Expand Down Expand Up @@ -503,4 +514,11 @@ impl<'a, K: kv::KV<'a>> kv::KVClient for KVStorePermissions<'a, K> {
cb.delete_complete(result, key);
});
}

fn garbage_collection_complete(&self, result: Result<(), ErrorCode>) {
self.operation.clear();
self.client.map(move |cb| {
cb.garbage_collection_complete(result);
});
}
}
27 changes: 26 additions & 1 deletion capsules/extra/src/tickv_kv_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum Operation {
Add,
Update,
Delete,
GarbageCollect,
}

/// `TicKVKVStore` implements the KV interface using the TicKV KVSystem
Expand Down Expand Up @@ -209,6 +210,21 @@ impl<'a, K: KVSystem<'a, K = T>, T: KeyType> kv::KV<'a> for TicKVKVStore<'a, K,
None => Err((key, ErrorCode::FAIL)),
}
}

fn garbage_collect(&self) -> Result<(), ErrorCode> {
if self.operation.is_some() {
return Err(ErrorCode::BUSY);
}

self.operation.set(Operation::GarbageCollect);

if let Err(e) = self.kv.garbage_collect() {
self.operation.clear();
Err(e)
} else {
Ok(())
}
}
}

impl<'a, K: KVSystem<'a, K = T>, T: KeyType> KVSystemClient<T> for TicKVKVStore<'a, K, T> {
Expand Down Expand Up @@ -260,6 +276,7 @@ impl<'a, K: KVSystem<'a, K = T>, T: KeyType> KVSystemClient<T> for TicKVKVStore<
cb.delete_complete(Err(ErrorCode::FAIL), unhashed_key);
});
}
Operation::GarbageCollect => {}
}
} else {
match op {
Expand Down Expand Up @@ -350,6 +367,7 @@ impl<'a, K: KVSystem<'a, K = T>, T: KeyType> KVSystemClient<T> for TicKVKVStore<
}
};
}
Operation::GarbageCollect => {}
}
}
});
Expand Down Expand Up @@ -441,6 +459,7 @@ impl<'a, K: KVSystem<'a, K = T>, T: KeyType> KVSystemClient<T> for TicKVKVStore<
});
});
}
Operation::GarbageCollect => {}
});
}

Expand Down Expand Up @@ -564,8 +583,14 @@ impl<'a, K: KVSystem<'a, K = T>, T: KeyType> KVSystemClient<T> for TicKVKVStore<
});
});
}
Operation::GarbageCollect => {}
});
}

fn garbage_collect_complete(&self, _result: Result<(), ErrorCode>) {}
fn garbage_collect_complete(&self, result: Result<(), ErrorCode>) {
self.operation.clear();
self.client.map(move |cb| {
cb.garbage_collection_complete(result);
});
}
}
45 changes: 45 additions & 0 deletions capsules/extra/src/virtual_kv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ enum Operation {
Delete,
Add,
Update,
GarbageCollect,
}

pub struct VirtualKVPermissions<'a, V: kv::KVPermissions<'a>> {
Expand Down Expand Up @@ -224,6 +225,16 @@ impl<'a, V: kv::KVPermissions<'a>> kv::KVPermissions<'a> for VirtualKVPermission
.map_err(|e| (self.key.take().unwrap(), e))
}

fn garbage_collect(&self) -> Result<(), ErrorCode> {
if self.operation.is_some() {
return Err(ErrorCode::BUSY);
}

self.operation.set(Operation::GarbageCollect);

self.mux_kv.do_next_op(false)
}

fn header_size(&self) -> usize {
self.mux_kv.kv.header_size()
}
Expand All @@ -250,6 +261,28 @@ impl<'a, V: kv::KVPermissions<'a>> MuxKVPermissions<'a, V> {

mnode.map_or(Ok(()), |node| {
node.operation.map_or(Ok(()), |op| {
// GarbageCollect doesn't have a key, so we check it above
// the match case below
if op == Operation::GarbageCollect {
return match self.kv.garbage_collect() {
Ok(()) => {
self.inflight.set(node);
Ok(())
}
Err(e) => {
node.operation.clear();
if async_op {
node.client.map(move |cb| {
cb.garbage_collection_complete(Err(e));
});
Ok(())
} else {
Err(e)
}
}
};
}

node.key.take().map_or(Ok(()), |key| match op {
Operation::Get => node.value.take().map_or(Ok(()), |value| {
node.valid_ids.map_or(Ok(()), |perms| {
Expand Down Expand Up @@ -364,6 +397,7 @@ impl<'a, V: kv::KVPermissions<'a>> MuxKVPermissions<'a, V> {
}
})
}
Operation::GarbageCollect => Err(ErrorCode::NOSUPPORT),
})
})
})
Expand Down Expand Up @@ -445,4 +479,15 @@ impl<'a, V: kv::KVPermissions<'a>> kv::KVClient for MuxKVPermissions<'a, V> {

let _ = self.do_next_op(true);
}

fn garbage_collection_complete(&self, result: Result<(), ErrorCode>) {
self.inflight.take().map(|node| {
node.operation.clear();
node.client.map(move |cb| {
cb.garbage_collection_complete(result);
});
});

let _ = self.do_next_op(true);
}
}
38 changes: 38 additions & 0 deletions kernel/src/hil/kv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ pub trait KVClient {
/// completed.
/// - `key`: The key buffer.
fn delete_complete(&self, result: Result<(), ErrorCode>, key: SubSliceMut<'static, u8>);

/// This callback is called when the garbage collection operation completes.
///
/// ### Return Values
///
/// - `result`: `Ok(())` on success, `Err(ErrorCode)` on error. Valid
/// `ErrorCode`s:
/// - `FAIL`: An internal error occurred and the operation cannot be
/// completed.
fn garbage_collection_complete(&self, result: Result<(), ErrorCode>);
}

/// Key-Value interface with permissions.
Expand Down Expand Up @@ -319,6 +329,20 @@ pub trait KVPermissions<'a> {
permissions: StoragePermissions,
) -> Result<(), (SubSliceMut<'static, u8>, ErrorCode)>;

/// Run garbage collection on the underlying Key/Value store.
///
/// This is generally used to reclaim keys that have been removed with
/// the `delete()` call.
///
/// ### Return
///
/// - On success returns `Ok(())`. A callback will be issued.
/// - On error, returns the buffers and:
/// - `BUSY`: An operation is already in progress.
/// - `FAIL`: An internal error occurred and the operation cannot be
/// completed.
fn garbage_collect(&self) -> Result<(), ErrorCode>;

/// Returns the length of the key-value store's header in bytes.
///
/// Room for this header must be accommodated in a `set`, `add`, or `update`
Expand Down Expand Up @@ -478,4 +502,18 @@ pub trait KV<'a> {
&self,
key: SubSliceMut<'static, u8>,
) -> Result<(), (SubSliceMut<'static, u8>, ErrorCode)>;

/// Run garbage collection on the underlying Key/Value store.
///
/// This is generally used to reclaim keys that have been removed with
/// the `delete()` call.
///
/// ### Return
///
/// - On success returns `Ok(())`. A callback will be issued.
/// - On error, returns the buffers and:
/// - `BUSY`: An operation is already in progress.
/// - `FAIL`: An internal error occurred and the operation cannot be
/// completed.
fn garbage_collect(&self) -> Result<(), ErrorCode>;
}

0 comments on commit f58d250

Please sign in to comment.