Implements require_transactional (#7261)

* Implements require_transactional

* support wasm

* only enable for debug build

* remove wasm support and add feature flag

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* only use check for debug_assertions

* update per review

* update docs

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* remove duplicated tests

* fix test

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Xiliang Chen
2020-10-12 20:46:15 +13:00
committed by GitHub
parent 3c0a049bee
commit 806dc9a659
5 changed files with 135 additions and 2 deletions
+34 -1
View File
@@ -267,7 +267,40 @@ macro_rules! ord_parameter_types {
}
#[doc(inline)]
pub use frame_support_procedural::{decl_storage, construct_runtime, transactional};
pub use frame_support_procedural::{
decl_storage, construct_runtime, transactional
};
/// Assert the annotated function is executed within a storage transaction.
///
/// The assertion is enabled for native execution and when `debug_assertions` are enabled.
///
/// # Example
///
/// ```
/// # use frame_support::{
/// # require_transactional, transactional, dispatch::DispatchResult
/// # };
///
/// #[require_transactional]
/// fn update_all(value: u32) -> DispatchResult {
/// // Update multiple storages.
/// // Return `Err` to indicate should revert.
/// Ok(())
/// }
///
/// #[transactional]
/// fn safe_update(value: u32) -> DispatchResult {
/// // This is safe
/// update_all(value)
/// }
///
/// fn unsafe_update(value: u32) -> DispatchResult {
/// // this may panic if unsafe_update is not called within a storage transaction
/// update_all(value)
/// }
/// ```
pub use frame_support_procedural::require_transactional;
/// Return Err of the expression: `return Err($expression);`.
///
@@ -30,6 +30,57 @@ pub mod child;
pub mod generator;
pub mod migration;
#[cfg(all(feature = "std", any(test, debug_assertions)))]
mod debug_helper {
use std::cell::RefCell;
thread_local! {
static TRANSACTION_LEVEL: RefCell<u32> = RefCell::new(0);
}
pub fn require_transaction() {
let level = TRANSACTION_LEVEL.with(|v| *v.borrow());
if level == 0 {
panic!("Require transaction not called within with_transaction");
}
}
pub struct TransactionLevelGuard;
impl Drop for TransactionLevelGuard {
fn drop(&mut self) {
TRANSACTION_LEVEL.with(|v| *v.borrow_mut() -= 1);
}
}
/// Increments the transaction level.
///
/// Returns a guard that when dropped decrements the transaction level automatically.
pub fn inc_transaction_level() -> TransactionLevelGuard {
TRANSACTION_LEVEL.with(|v| {
let mut val = v.borrow_mut();
*val += 1;
if *val > 10 {
crate::debug::warn!(
"Detected with_transaction with nest level {}. Nested usage of with_transaction is not recommended.",
*val
);
}
});
TransactionLevelGuard
}
}
/// Assert this method is called within a storage transaction.
/// This will **panic** if is not called within a storage transaction.
///
/// This assertion is enabled for native execution and when `debug_assertions` are enabled.
pub fn require_transaction() {
#[cfg(all(feature = "std", any(test, debug_assertions)))]
debug_helper::require_transaction();
}
/// Execute the supplied function in a new storage transaction.
///
/// All changes to storage performed by the supplied function are discarded if the returned
@@ -43,6 +94,10 @@ pub fn with_transaction<R>(f: impl FnOnce() -> TransactionOutcome<R>) -> R {
use TransactionOutcome::*;
start_transaction();
#[cfg(all(feature = "std", any(test, debug_assertions)))]
let _guard = debug_helper::inc_transaction_level();
match f() {
Commit(res) => { commit_transaction(); res },
Rollback(res) => { rollback_transaction(); res },
@@ -732,4 +787,27 @@ mod test {
assert_eq!(Digest::decode(&mut &value[..]).unwrap(), expected);
});
}
#[test]
#[should_panic(expected = "Require transaction not called within with_transaction")]
fn require_transaction_should_panic() {
TestExternalities::default().execute_with(|| {
require_transaction();
});
}
#[test]
fn require_transaction_should_not_panic_in_with_transaction() {
TestExternalities::default().execute_with(|| {
with_transaction(|| {
require_transaction();
TransactionOutcome::Commit(())
});
with_transaction(|| {
require_transaction();
TransactionOutcome::Rollback(())
});
});
}
}