This repository was archived by the owner on Nov 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Implement storage lock for off-chain worker #6004
Copy link
Copy link
Closed
Description
@tomusdrw outlined an implementation of storage lock for off-chain worker here. Copying the content here so it is not lost.
const MAX_SPIN_ITERATIONS: usize = 1_000; // we wait up to 100 seconds
const SLEEP_TIME_MS: usize = 100;
pub struct StorageLock<'a, BlockNumber = u64> {
storage: StorageValue<'a>,
blocks: Option<BlockNumber>,
}
impl<'a> StorageLock<'a> {
pub fn new(id: &'a str) -> Self {
StorageLock {
storage: StorageValue::persistent(id),
blocks: None,
}
}
}
impl<'a, BlockNumber: Ord> StorageLock<'a, BlockNumber> {
pub fn with_block_limit(id :&'a str, blocks: BlockNumber) -> Self {
StorageLock {
storage: StorageValue::persistent(id),
blocks,
}
}
pub fn try_lock<'b>(&'b mut self) -> Result<
StorageLockGuard<'a, 'b, BlockNumber>,
()
> {
let current_block_number = frame_system::Module<T>::block_number();
let release_at = current_block_number + self.blocks.clone().unwrap_or(0.into());
let res: Result<Result<BlockNumber, BlockNumber>, ()> = self.storage
.mutate(|s: Option<Option<BlockNumber>>| {
match s {
// no lock set, we can safely acquire it
None => Ok(release_at),
// lock is set, but it's old. We can re-acquire it.
Some(Some(number)) if number < current_block_number => Ok(release_at),
// lock is present and is still active, fail.
_ => Err(()),
}
});
if let Ok(Ok(_)) = res {
Ok(StorageLockGuard {
lock: Some(self),
})
} else {
Err(())
}
}
pub fn spin_lock<'b>(&'b mut self) -> Result<
StorageLockGuard<'a, 'b, BlockNumber>,
()
> {
for i in 1..MAX_SPIN_ITERATIONS {
if let Ok(guard) = self.try_lock() {
return Ok(guard);
}
sp_io::offchain::sleep(SLEEP_TIME_MS);
}
Err(())
}
fn unlock(&mut self) {
let block: BlockNumber = 0.into();
self.storage.set(&block);
}
}
pub struct StorageLockGuard<'a, 'b, BlockNumber> {
lock: Option<&'b mut StorageLock<'a, BlockNumber>>,
}
impl<'a, 'b, BlockNumber> StorageLockGuard<'a, 'b, BlockNumber> {
pub fn forget(self) {
self.lock.take()
}
}
impl<'a, 'b, BlockNumber> Drop for StorageLockGuard<'a, 'b, BlockNumber> {
fn drop(&mut self) {
if Some(lock) = self.lock.take() {
lock.unlock();
}
}
}This more complex one allows you to set some time limit (we could do it in seconds instead of block numbers though). For how long the block is valid.
This has some useful properties, for instance:
- If your node get's killed in the middle of fetching GithubInfo, it will never run the offchain worker any more, cause the storage entry will be stuck at the locked state.
- It allows you to have a grace period, where you send a transaction and then want to prevent other offchain workers from running, hoping the transaction will get accepted in the meantime (use case for guard.forget()). We do have a logic like this in offchain phragmen and im-online already.
fyi @drahnr
Let's implement this in Substrate.
Metadata
Metadata
Assignees
Labels
No labels