From 37c0a257bc6d6ed99b34a441c7d70b1c232f2c45 Mon Sep 17 00:00:00 2001 From: Arseniy Lyashenko Date: Sat, 11 Nov 2023 01:42:08 +0300 Subject: [PATCH 01/37] Initial commit --- Cargo.lock | 11 ++ Cargo.toml | 2 + examples/async-critical/Cargo.toml | 21 +++ examples/async-critical/build.rs | 21 +++ examples/async-critical/src/lib.rs | 40 ++++++ examples/async-critical/src/wasm.rs | 87 ++++++++++++ gstd/src/async_runtime/critical.rs | 113 +++++++++++++++ gstd/src/async_runtime/mod.rs | 3 + gstd/src/lib.rs | 2 +- pallets/gear/Cargo.toml | 1 + pallets/gear/src/tests.rs | 208 ++++++++++++++++++++++++++++ 11 files changed, 508 insertions(+), 1 deletion(-) create mode 100644 examples/async-critical/Cargo.toml create mode 100644 examples/async-critical/build.rs create mode 100644 examples/async-critical/src/lib.rs create mode 100644 examples/async-critical/src/wasm.rs create mode 100644 gstd/src/async_runtime/critical.rs diff --git a/Cargo.lock b/Cargo.lock index 06d86e1eefb..ec57ad6006b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2012,6 +2012,16 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "demo-async-critical" +version = "0.1.0" +dependencies = [ + "futures", + "gear-wasm-builder", + "gstd", + "parity-scale-codec", +] + [[package]] name = "demo-async-custom-entry" version = "0.1.0" @@ -7561,6 +7571,7 @@ version = "1.0.2" dependencies = [ "blake2-rfc", "demo-async", + "demo-async-critical", "demo-async-custom-entry", "demo-async-init", "demo-async-recursion", diff --git a/Cargo.toml b/Cargo.toml index 7ef743886fd..d43f328659d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "core-processor", "core-errors", "examples/async", + "examples/async-critical", "examples/async-custom-entry", "examples/async-init", "examples/async-signal-entry", @@ -375,6 +376,7 @@ try-runtime-cli = { version = "0.10.0-dev", git = "https://github.com/gear-tech/ # Examples test-syscalls = { path = "examples/sys-calls", default-features = false } demo-async = { path = "examples/async" } +demo-async-critical = { path = "examples/async-critical" } demo-async-custom-entry = { path = "examples/async-custom-entry" } demo-async-init = { path = "examples/async-init" } demo-async-recursion = { path = "examples/async-recursion" } diff --git a/examples/async-critical/Cargo.toml b/examples/async-critical/Cargo.toml new file mode 100644 index 00000000000..83b971f56aa --- /dev/null +++ b/examples/async-critical/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "demo-async-critical" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +gstd.workspace = true +parity-scale-codec.workspace = true +futures.workspace = true + +[build-dependencies] +gear-wasm-builder.workspace = true + +[features] +debug = ["gstd/debug"] +default = ["std"] +std = [] diff --git a/examples/async-critical/build.rs b/examples/async-critical/build.rs new file mode 100644 index 00000000000..4c502a3ddee --- /dev/null +++ b/examples/async-critical/build.rs @@ -0,0 +1,21 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +fn main() { + gear_wasm_builder::build(); +} diff --git a/examples/async-critical/src/lib.rs b/examples/async-critical/src/lib.rs new file mode 100644 index 00000000000..83853233e90 --- /dev/null +++ b/examples/async-critical/src/lib.rs @@ -0,0 +1,40 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![no_std] + +#[cfg(feature = "std")] +mod code { + include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +} + +#[cfg(feature = "std")] +pub use code::WASM_BINARY_OPT as WASM_BINARY; + +use gstd::{Decode, Encode}; + +#[derive(Debug, Encode, Decode)] +pub enum HandleAction { + Normal, + Panic, + Wait, + WaitAndPanic, +} + +#[cfg(target_arch = "wasm32")] +mod wasm; diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs new file mode 100644 index 00000000000..a5d403e7758 --- /dev/null +++ b/examples/async-critical/src/wasm.rs @@ -0,0 +1,87 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! The program demonstrates asynchronous execution and +//! how to use macros `gstd::async_init`/`gstd::async_main`. +//! +//! `Init` method gets three addresses, sends "PING" messages +//! to them and waits for at least two replies with any payload ("approvals"). +//! +//! `Handle` processes only "PING" messages. When `handle` gets such message +//! it sends empty requests to the three addresses and waits for just one approval. +//! If an approval is obtained the method replies with "PONG". + +use crate::HandleAction; +use gstd::{critical::Section, exec, msg, prelude::*}; + +#[gstd::async_init] +async fn init() {} + +#[gstd::async_main] +async fn main() { + let action: HandleAction = msg::load().expect("Failed to read handle action"); + + match action { + HandleAction::Normal => { + let normal0 = Section::new(|| { + msg::send_bytes(msg::source(), b"normal0", 0).unwrap(); + }); + + let normal1 = Section::new(|| { + msg::send_bytes(msg::source(), b"normal1", 0).unwrap(); + }); + + normal0.execute(); + normal1.execute(); + } + HandleAction::Panic => { + // would not be executed + let _before_panic = Section::new(|| { + msg::send_bytes(msg::source(), b"before_panic", 0).unwrap(); + }); + + panic!(); + } + HandleAction::Wait => { + let section = Section::new(|| { + msg::send_bytes(msg::source(), b"before_wait", 0).unwrap(); + }); + + gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) + .expect("Failed to send message") + .await + .expect("Received error reply"); + + section.execute(); + } + HandleAction::WaitAndPanic => { + // call `gr_source` outside because it is forbidden in `handle_signal` + let source = msg::source(); + let section = Section::new(move || { + msg::send_bytes(source, b"before_wait", 0).unwrap(); + }); + + gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) + .expect("Failed to send message") + .await + .expect("Received error reply"); + + panic!(); + } + } +} diff --git a/gstd/src/async_runtime/critical.rs b/gstd/src/async_runtime/critical.rs new file mode 100644 index 00000000000..51f26c57f45 --- /dev/null +++ b/gstd/src/async_runtime/critical.rs @@ -0,0 +1,113 @@ +// This file is part of Gear. + +// Copyright (C) 2023 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Critical section that guarantees code section execution +//! +//! Code is executed in case of failure in `handle_signal` entry point +//! only across [`wait`](crate::exec::wait) or `.await` calls. +//! +//! ```rust,ignore +//! use gstd::msg; +//! use gstd::critical; +//! +//! // get source outside of critical section +//! // because `gr_source` sys-call is forbidden inside `handle_signal` entry point +//! let source = msg::source(); +//! let section = critical::Section::new(move || { +//! msg::send(source, "example", 0).expect("Failed to send message"); +//! }); +//! +//! msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) +//! .expect("Failed to send message") +//! .await +//! .expect("Received error reply"); +//! +//! section.execute(); +//! +//! ``` + +use alloc::{boxed::Box, collections::BTreeMap}; +use core::{any::TypeId, mem}; + +static mut SECTIONS: Sections = Sections::new(); + +pub(crate) struct Sections { + fns: BTreeMap>, +} + +impl Sections { + const fn new() -> Self { + Self { + fns: BTreeMap::new(), + } + } + + pub(crate) fn get() -> &'static mut Self { + unsafe { &mut SECTIONS } + } + + fn register(&mut self, f: F) -> Section + where + F: FnMut() + Clone + 'static, + { + self.fns.insert(TypeId::of::(), Box::new(f.clone())); + Section(f) + } + + fn unregister(&mut self) + where + F: FnMut() + Clone + 'static, + { + self.fns.remove(&TypeId::of::()); + } + + pub(crate) fn execute_all(&mut self) { + for (_, mut f) in mem::take(&mut self.fns) { + (f)(); + } + } +} + +/// Critical section. +pub struct Section(F) +where + F: FnMut() + Clone + 'static; + +impl Section +where + F: FnMut() + Clone + 'static, +{ + /// Creates a new critical section. + pub fn new(f: F) -> Self { + Sections::get().register(f) + } + + /// Executes critical section. + pub fn execute(mut self) { + (self.0)() + } +} + +impl Drop for Section +where + F: FnMut() + Clone + 'static, +{ + fn drop(&mut self) { + Sections::get().unregister::(); + } +} diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index a5f8d10a6ea..e3c9649217f 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +pub mod critical; mod futures; mod locks; mod signals; @@ -55,6 +56,8 @@ pub fn record_reply() { /// Default signal handler. pub fn handle_signal() { + critical::Sections::get().execute_all(); + let msg_id = crate::msg::signal_from().expect( "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); diff --git a/gstd/src/lib.rs b/gstd/src/lib.rs index 2ed7a6809b5..34670a61e8c 100644 --- a/gstd/src/lib.rs +++ b/gstd/src/lib.rs @@ -150,7 +150,7 @@ mod reservations; pub mod sync; pub mod util; -pub use async_runtime::{handle_signal, message_loop, record_reply}; +pub use async_runtime::{critical, handle_signal, message_loop, record_reply}; pub use common::{errors, primitives::*}; pub use config::Config; pub use gcore::{ext, BlockCount, BlockNumber, Gas, GasMultiplier, Percent, Value}; diff --git a/pallets/gear/Cargo.toml b/pallets/gear/Cargo.toml index b0e76956394..74c052249ee 100644 --- a/pallets/gear/Cargo.toml +++ b/pallets/gear/Cargo.toml @@ -113,6 +113,7 @@ demo-out-of-memory.workspace = true demo-ping.workspace = true demo-sync-duplicate.workspace = true demo-custom.workspace = true +demo-async-critical = { workspace = true, features = ["debug"] } test-syscalls.workspace = true page_size.workspace = true frame-support-test = { workspace = true, features = ["std"] } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 1c539cd4c38..0e64a584491 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -14770,6 +14770,214 @@ fn test_gas_info_of_terminated_program() { }) } +#[test] +fn critical_section_works() { + use demo_async_critical::{HandleAction, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + vec![], + 10_000_000_000, + 0, + false, + )); + let pid = get_last_program_id(); + + run_to_block(2, None); + + assert!(Gear::is_initialized(pid)); + assert!(Gear::is_active(pid)); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + HandleAction::Normal.encode(), + 10_000_000_000, + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_block(3, None); + + assert_succeed(mid); + + MailboxOf::::iter_key(USER_1) + .find(|(msg, _)| msg.payload_bytes() == b"normal0") + .unwrap(); + MailboxOf::::iter_key(USER_1) + .find(|(msg, _)| msg.payload_bytes() == b"normal1") + .unwrap(); + }); +} + +#[test] +fn critical_section_with_panic() { + use demo_async_critical::{HandleAction, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + vec![], + 10_000_000_000, + 0, + false, + )); + let pid = get_last_program_id(); + + run_to_block(2, None); + + assert!(Gear::is_initialized(pid)); + assert!(Gear::is_active(pid)); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + HandleAction::Panic.encode(), + 10_000_000_000, + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_block(3, None); + + assert_failed( + mid, + ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic), + ); + + assert_eq!(MailboxOf::::iter_key(USER_1).last(), None); + }); +} + +#[test] +fn critical_section_with_wait() { + use demo_async_critical::{HandleAction, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + vec![], + 10_000_000_000, + 0, + false, + )); + let pid = get_last_program_id(); + + run_to_block(2, None); + + assert!(Gear::is_initialized(pid)); + assert!(Gear::is_active(pid)); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + HandleAction::Wait.encode(), + 10_000_000_000, + 0, + false, + )); + + run_to_block(3, None); + + let msg = get_last_mail(USER_1); + assert_eq!(msg.payload_bytes(), b"for_reply"); + + assert_ok!(Gear::send_reply( + RuntimeOrigin::signed(USER_1), + msg.id(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000, + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_block(4, None); + + assert_succeed(mid); + + let msg = get_last_mail(USER_1); + assert_eq!(msg.payload_bytes(), b"before_wait"); + }); +} + +#[test] +fn critical_section_with_wait_and_panic() { + use demo_async_critical::{HandleAction, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + vec![], + 10_000_000_000, + 0, + false, + )); + let pid = get_last_program_id(); + + run_to_block(2, None); + + assert!(Gear::is_initialized(pid)); + assert!(Gear::is_active(pid)); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + HandleAction::WaitAndPanic.encode(), + 10_000_000_000, + 0, + false, + )); + + let wait_mid = get_last_message_id(); + + run_to_block(3, None); + + let msg = get_last_mail(USER_1); + assert_eq!(msg.payload_bytes(), b"for_reply"); + + assert_ok!(Gear::send_reply( + RuntimeOrigin::signed(USER_1), + msg.id(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000, + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_block(4, None); + + assert_succeed(mid); + assert_failed( + wait_mid, + ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic), + ); + + let msg = get_last_mail(USER_1); + assert_eq!(msg.payload_bytes(), b"before_wait"); + }); +} + mod utils { #![allow(unused)] From e6ed3afaac662cbf01779a0ab181cb2e2df05925 Mon Sep 17 00:00:00 2001 From: Arseniy Lyashenko Date: Fri, 17 Nov 2023 20:15:00 +0300 Subject: [PATCH 02/37] Move `critical.rs` to root of `gstd` --- gstd/src/async_runtime/mod.rs | 2 +- gstd/src/{async_runtime => }/critical.rs | 5 ++++- gstd/src/lib.rs | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) rename gstd/src/{async_runtime => }/critical.rs (94%) diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index e3c9649217f..0c9035a1e48 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -pub mod critical; mod futures; mod locks; mod signals; @@ -25,6 +24,7 @@ mod waker; pub use self::futures::message_loop; use self::futures::FuturesMap; +use crate::critical; use hashbrown::HashMap; pub(crate) use locks::Lock; use locks::LocksMap; diff --git a/gstd/src/async_runtime/critical.rs b/gstd/src/critical.rs similarity index 94% rename from gstd/src/async_runtime/critical.rs rename to gstd/src/critical.rs index 51f26c57f45..09dbb98aecf 100644 --- a/gstd/src/async_runtime/critical.rs +++ b/gstd/src/critical.rs @@ -76,7 +76,10 @@ impl Sections { self.fns.remove(&TypeId::of::()); } - pub(crate) fn execute_all(&mut self) { + /// Executes every saved critical section once. + /// + /// Must be called in `handle_signal` entry point if you don't use async runtime. + pub fn execute_all(&mut self) { for (_, mut f) in mem::take(&mut self.fns) { (f)(); } diff --git a/gstd/src/lib.rs b/gstd/src/lib.rs index 34670a61e8c..225f4feaf10 100644 --- a/gstd/src/lib.rs +++ b/gstd/src/lib.rs @@ -141,6 +141,7 @@ extern crate galloc; mod async_runtime; mod common; mod config; +pub mod critical; pub mod exec; mod macros; pub mod msg; @@ -150,7 +151,7 @@ mod reservations; pub mod sync; pub mod util; -pub use async_runtime::{critical, handle_signal, message_loop, record_reply}; +pub use async_runtime::{handle_signal, message_loop, record_reply}; pub use common::{errors, primitives::*}; pub use config::Config; pub use gcore::{ext, BlockCount, BlockNumber, Gas, GasMultiplier, Percent, Value}; From af4f5b151c692d58b6b71c59a20ea30dfec9120b Mon Sep 17 00:00:00 2001 From: Arseniy Lyashenko Date: Fri, 17 Nov 2023 20:17:38 +0300 Subject: [PATCH 03/37] Update doc --- gstd/src/critical.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 09dbb98aecf..61a891bffe6 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -18,27 +18,41 @@ //! Critical section that guarantees code section execution //! -//! Code is executed in case of failure in `handle_signal` entry point -//! only across [`wait`](crate::exec::wait) or `.await` calls. +//! Code is executed in `handle_signal` entry point in case of failure +//! only across [`wait`](crate::exec::wait) or `.await` calls +//! because sections have to be saved. //! -//! ```rust,ignore +//! ```rust,no_run //! use gstd::msg; //! use gstd::critical; //! +//! # async fn main() { +//! //! // get source outside of critical section //! // because `gr_source` sys-call is forbidden inside `handle_signal` entry point //! let source = msg::source(); +//! // register section //! let section = critical::Section::new(move || { //! msg::send(source, "example", 0).expect("Failed to send message"); //! }); //! -//! msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) +//! // section is now saved +//! msg::send_for_reply(msg::source(), "for_reply", 0, 0) //! .expect("Failed to send message") //! .await //! .expect("Received error reply"); //! +//! // if some code fails (panic, out of gas, etc) after `wait` (`send_for_reply` in our case) +//! // then saved sections will be executed in `handle_signal` +//! +//! // your code +//! // ... +//! +//! // execute section //! section.execute(); //! +//! # } +//! //! ``` use alloc::{boxed::Box, collections::BTreeMap}; From 106a7225d8f91c996640d6b5696ed2b6b80f8cdd Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Sat, 18 Nov 2023 22:22:10 +0300 Subject: [PATCH 04/37] Replace BTreeMap with HashMap --- gstd/src/critical.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 61a891bffe6..9813269e6ae 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -55,24 +55,25 @@ //! //! ``` -use alloc::{boxed::Box, collections::BTreeMap}; +use alloc::boxed::Box; use core::{any::TypeId, mem}; +use hashbrown::HashMap; -static mut SECTIONS: Sections = Sections::new(); +static mut SECTIONS: Option = None; pub(crate) struct Sections { - fns: BTreeMap>, + fns: HashMap>, } impl Sections { - const fn new() -> Self { + fn new() -> Self { Self { - fns: BTreeMap::new(), + fns: HashMap::new(), } } pub(crate) fn get() -> &'static mut Self { - unsafe { &mut SECTIONS } + unsafe { SECTIONS.get_or_insert_with(Self::new) } } fn register(&mut self, f: F) -> Section From 6d44bb1f2f85f0596d2f1a321172067bb358d04f Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Sat, 18 Nov 2023 22:22:33 +0300 Subject: [PATCH 05/37] Fix `execute_all` doc --- gstd/src/critical.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 9813269e6ae..ac8d17134a1 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -93,7 +93,8 @@ impl Sections { /// Executes every saved critical section once. /// - /// Must be called in `handle_signal` entry point if you don't use async runtime. + /// Must be called in `handle_signal` entry point + /// if you don't use async runtime. pub fn execute_all(&mut self) { for (_, mut f) in mem::take(&mut self.fns) { (f)(); From 55f3869432ffacc53a00142a752fcb1ccd19c3f3 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Sun, 19 Nov 2023 00:55:20 +0300 Subject: [PATCH 06/37] Fix module doc --- gstd/src/critical.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index ac8d17134a1..ece609671c0 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -23,8 +23,7 @@ //! because sections have to be saved. //! //! ```rust,no_run -//! use gstd::msg; -//! use gstd::critical; +//! use gstd::{critical, msg}; //! //! # async fn main() { //! @@ -52,7 +51,6 @@ //! section.execute(); //! //! # } -//! //! ``` use alloc::boxed::Box; From fdd387cae6ede8ac84be32e0ba59febeb2570e58 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Sun, 19 Nov 2023 20:46:03 +0300 Subject: [PATCH 07/37] Rename function in module docs --- gstd/src/critical.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index ece609671c0..2d0fc76e914 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -25,7 +25,7 @@ //! ```rust,no_run //! use gstd::{critical, msg}; //! -//! # async fn main() { +//! # async fn _dummy() { //! //! // get source outside of critical section //! // because `gr_source` sys-call is forbidden inside `handle_signal` entry point From 7af6af037033b5d6ca6b1c9d4ebea475df14be9f Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Tue, 28 Nov 2023 21:10:22 +0100 Subject: [PATCH 08/37] Redesign --- Cargo.lock | 1 + examples/async-critical/src/lib.rs | 5 +- examples/async-critical/src/wasm.rs | 63 +++++++------- gstd/Cargo.toml | 1 + gstd/src/async_runtime/mod.rs | 4 +- gstd/src/critical.rs | 122 ++++++++++++---------------- pallets/gear/src/tests.rs | 99 ++++++++-------------- 7 files changed, 125 insertions(+), 170 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec57ad6006b..66e3feaac44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4863,6 +4863,7 @@ dependencies = [ "hashbrown 0.14.2", "hex", "parity-scale-codec", + "pin-project", "primitive-types", "scale-info", "static_assertions", diff --git a/examples/async-critical/src/lib.rs b/examples/async-critical/src/lib.rs index 83853233e90..6e8e4acdad3 100644 --- a/examples/async-critical/src/lib.rs +++ b/examples/async-critical/src/lib.rs @@ -30,10 +30,9 @@ use gstd::{Decode, Encode}; #[derive(Debug, Encode, Decode)] pub enum HandleAction { - Normal, + Simple, Panic, - Wait, - WaitAndPanic, + DropWorks, } #[cfg(target_arch = "wasm32")] diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index a5d403e7758..18d9bfc0b0e 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -27,7 +27,7 @@ //! If an approval is obtained the method replies with "PONG". use crate::HandleAction; -use gstd::{critical::Section, exec, msg, prelude::*}; +use gstd::{critical::SectionFutureExt, exec, msg, prelude::*}; #[gstd::async_init] async fn init() {} @@ -37,51 +37,56 @@ async fn main() { let action: HandleAction = msg::load().expect("Failed to read handle action"); match action { - HandleAction::Normal => { - let normal0 = Section::new(|| { - msg::send_bytes(msg::source(), b"normal0", 0).unwrap(); - }); - - let normal1 = Section::new(|| { - msg::send_bytes(msg::source(), b"normal1", 0).unwrap(); - }); + HandleAction::Simple => { + // call `gr_source` outside because it is forbidden in `handle_signal` + let source = msg::source(); - normal0.execute(); - normal1.execute(); + gstd::msg::send_bytes_for_reply(source, b"for_reply", 0, 0) + .expect("Failed to send message") + // should not send anything as execution will be completed + .critical(move || { + msg::send_bytes(msg::source(), b"critical", 0).unwrap(); + }) + .await + .expect("Received error reply"); } HandleAction::Panic => { - // would not be executed - let _before_panic = Section::new(|| { - msg::send_bytes(msg::source(), b"before_panic", 0).unwrap(); - }); - - panic!(); - } - HandleAction::Wait => { - let section = Section::new(|| { - msg::send_bytes(msg::source(), b"before_wait", 0).unwrap(); - }); + // call `gr_source` outside because it is forbidden in `handle_signal` + let source = msg::source(); gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) .expect("Failed to send message") + // should send message because panic occurs below + .critical(move || { + msg::send_bytes(source, b"critical", 0).unwrap(); + }) .await .expect("Received error reply"); - section.execute(); + panic!(); } - HandleAction::WaitAndPanic => { + HandleAction::DropWorks => { // call `gr_source` outside because it is forbidden in `handle_signal` let source = msg::source(); - let section = Section::new(move || { - msg::send_bytes(source, b"before_wait", 0).unwrap(); - }); - gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) + gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply0", 0, 0) .expect("Failed to send message") + // set section + .critical(move || { + msg::send_bytes(source, b"critical", 0).unwrap(); + }) .await + // after wait section function should be removed .expect("Received error reply"); - panic!(); + gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply1", 0, 0) + .expect("Failed to send message") + // check if inside assertion panics + .critical(move || { + msg::send_bytes(source, b"critical", 0).unwrap(); + }) + .await + .expect("Received error reply"); } } } diff --git a/gstd/Cargo.toml b/gstd/Cargo.toml index 70c6033af9c..4ead09361d2 100644 --- a/gstd/Cargo.toml +++ b/gstd/Cargo.toml @@ -17,6 +17,7 @@ parity-scale-codec = { workspace = true, features = [ "derive" ] } primitive-types = { workspace = true, features = ["scale-info"] } scale-info = { workspace = true, features = ["derive"] } futures = { workspace = true, features = ["alloc"] } +pin-project = "1.1.3" static_assertions.workspace = true diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index 0c9035a1e48..7e143831633 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -56,7 +56,9 @@ pub fn record_reply() { /// Default signal handler. pub fn handle_signal() { - critical::Sections::get().execute_all(); + if let Some(mut f) = critical::section().take() { + f(); + } let msg_id = crate::msg::signal_from().expect( "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 2d0fc76e914..8cb50d6a492 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -19,112 +19,94 @@ //! Critical section that guarantees code section execution //! //! Code is executed in `handle_signal` entry point in case of failure -//! only across [`wait`](crate::exec::wait) or `.await` calls -//! because sections have to be saved. +//! only across `.await` calls because section has to be saved. //! //! ```rust,no_run -//! use gstd::{critical, msg}; +//! use gstd::{critical::{self, SectionFutureExt}, msg}; //! //! # async fn _dummy() { //! //! // get source outside of critical section //! // because `gr_source` sys-call is forbidden inside `handle_signal` entry point //! let source = msg::source(); -//! // register section -//! let section = critical::Section::new(move || { -//! msg::send(source, "example", 0).expect("Failed to send message"); -//! }); //! -//! // section is now saved //! msg::send_for_reply(msg::source(), "for_reply", 0, 0) //! .expect("Failed to send message") +//! // register section +//! .critical(|| { +//! msg::send(source, "example", 0).expect("Failed to send message"); +//! }) +//! // section will be saved now during `.await` //! .await //! .expect("Received error reply"); //! -//! // if some code fails (panic, out of gas, etc) after `wait` (`send_for_reply` in our case) -//! // then saved sections will be executed in `handle_signal` +//! // if some code fails (panic, out of gas, etc) after `.await` +//! // then saved section will be executed in `handle_signal` //! //! // your code //! // ... //! -//! // execute section -//! section.execute(); -//! //! # } //! ``` use alloc::boxed::Box; -use core::{any::TypeId, mem}; -use hashbrown::HashMap; +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; +use pin_project::{pin_project, pinned_drop}; -static mut SECTIONS: Option = None; +static mut SECTION: Option> = None; -pub(crate) struct Sections { - fns: HashMap>, +pub(crate) fn section() -> &'static mut Option> { + unsafe { &mut SECTION } } -impl Sections { - fn new() -> Self { - Self { - fns: HashMap::new(), - } - } - - pub(crate) fn get() -> &'static mut Self { - unsafe { SECTIONS.get_or_insert_with(Self::new) } - } - - fn register(&mut self, f: F) -> Section - where - F: FnMut() + Clone + 'static, - { - self.fns.insert(TypeId::of::(), Box::new(f.clone())); - Section(f) - } - - fn unregister(&mut self) - where - F: FnMut() + Clone + 'static, - { - self.fns.remove(&TypeId::of::()); - } - - /// Executes every saved critical section once. - /// - /// Must be called in `handle_signal` entry point - /// if you don't use async runtime. - pub fn execute_all(&mut self) { - for (_, mut f) in mem::take(&mut self.fns) { - (f)(); - } - } +/// Critical section future. +#[pin_project(PinnedDrop)] +#[must_use = "Future must be polled"] +pub struct SectionFuture { + #[pin] + fut: Fut, } -/// Critical section. -pub struct Section(F) -where - F: FnMut() + Clone + 'static; - -impl Section +impl Future for SectionFuture where - F: FnMut() + Clone + 'static, + Fut: Future, { - /// Creates a new critical section. - pub fn new(f: F) -> Self { - Sections::get().register(f) + type Output = Fut::Output; + + fn poll(self: Pin<&mut SectionFuture>, cx: &mut Context<'_>) -> Poll { + self.project().fut.poll(cx) } +} - /// Executes critical section. - pub fn execute(mut self) { - (self.0)() +#[pinned_drop] +impl PinnedDrop for SectionFuture { + fn drop(self: Pin<&mut Self>) { + let _ = section().take(); } } -impl Drop for Section +/// Extension for [`Future`](Future). +pub trait SectionFutureExt: Future + Sized { + /// Register critical section. + fn critical(self, f: Func) -> SectionFuture + where + Func: FnMut() + 'static; +} + +impl SectionFutureExt for F where - F: FnMut() + Clone + 'static, + F: Future, { - fn drop(&mut self) { - Sections::get().unregister::(); + fn critical(self, func: Func) -> SectionFuture + where + Func: FnMut() + 'static, + { + let prev = section().replace(Box::new(func)); + assert!(prev.is_none()); + SectionFuture { fut: self } } } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 0e64a584491..da51b6e6c4f 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -14795,73 +14795,34 @@ fn critical_section_works() { assert_ok!(Gear::send_message( RuntimeOrigin::signed(USER_1), pid, - HandleAction::Normal.encode(), + HandleAction::Simple.encode(), 10_000_000_000, 0, false, )); - let mid = get_last_message_id(); - run_to_block(3, None); - assert_succeed(mid); - - MailboxOf::::iter_key(USER_1) - .find(|(msg, _)| msg.payload_bytes() == b"normal0") - .unwrap(); - MailboxOf::::iter_key(USER_1) - .find(|(msg, _)| msg.payload_bytes() == b"normal1") - .unwrap(); - }); -} - -#[test] -fn critical_section_with_panic() { - use demo_async_critical::{HandleAction, WASM_BINARY}; - - init_logger(); - new_test_ext().execute_with(|| { - assert_ok!(Gear::upload_program( - RuntimeOrigin::signed(USER_1), - WASM_BINARY.to_vec(), - DEFAULT_SALT.to_vec(), - vec![], - 10_000_000_000, - 0, - false, - )); - let pid = get_last_program_id(); - - run_to_block(2, None); - - assert!(Gear::is_initialized(pid)); - assert!(Gear::is_active(pid)); + let msg = get_last_mail(USER_1); + assert_eq!(msg.payload_bytes(), b"for_reply"); - assert_ok!(Gear::send_message( + assert_ok!(Gear::send_reply( RuntimeOrigin::signed(USER_1), - pid, - HandleAction::Panic.encode(), + msg.id(), + EMPTY_PAYLOAD.to_vec(), 10_000_000_000, 0, false, )); - let mid = get_last_message_id(); - - run_to_block(3, None); - - assert_failed( - mid, - ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic), - ); + run_to_block(4, None); - assert_eq!(MailboxOf::::iter_key(USER_1).last(), None); + assert_eq!(MailboxOf::::iter_key(USER_1).count(), 0); }); } #[test] -fn critical_section_with_wait() { +fn critical_section_with_panic() { use demo_async_critical::{HandleAction, WASM_BINARY}; init_logger(); @@ -14885,12 +14846,14 @@ fn critical_section_with_wait() { assert_ok!(Gear::send_message( RuntimeOrigin::signed(USER_1), pid, - HandleAction::Wait.encode(), + HandleAction::Panic.encode(), 10_000_000_000, 0, false, )); + let mid = get_last_message_id(); + run_to_block(3, None); let msg = get_last_mail(USER_1); @@ -14905,19 +14868,20 @@ fn critical_section_with_wait() { false, )); - let mid = get_last_message_id(); - run_to_block(4, None); - assert_succeed(mid); + assert_failed( + mid, + ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic), + ); let msg = get_last_mail(USER_1); - assert_eq!(msg.payload_bytes(), b"before_wait"); + assert_eq!(msg.payload_bytes(), b"critical"); }); } #[test] -fn critical_section_with_wait_and_panic() { +fn critical_section_drop_works() { use demo_async_critical::{HandleAction, WASM_BINARY}; init_logger(); @@ -14941,18 +14905,16 @@ fn critical_section_with_wait_and_panic() { assert_ok!(Gear::send_message( RuntimeOrigin::signed(USER_1), pid, - HandleAction::WaitAndPanic.encode(), + HandleAction::DropWorks.encode(), 10_000_000_000, 0, false, )); - let wait_mid = get_last_message_id(); - run_to_block(3, None); let msg = get_last_mail(USER_1); - assert_eq!(msg.payload_bytes(), b"for_reply"); + assert_eq!(msg.payload_bytes(), b"for_reply0"); assert_ok!(Gear::send_reply( RuntimeOrigin::signed(USER_1), @@ -14963,18 +14925,21 @@ fn critical_section_with_wait_and_panic() { false, )); - let mid = get_last_message_id(); - run_to_block(4, None); - assert_succeed(mid); - assert_failed( - wait_mid, - ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic), - ); - let msg = get_last_mail(USER_1); - assert_eq!(msg.payload_bytes(), b"before_wait"); + assert_eq!(msg.payload_bytes(), b"for_reply1"); + + assert_ok!(Gear::send_reply( + RuntimeOrigin::signed(USER_1), + msg.id(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000, + 0, + false, + )); + + run_to_block(5, None); }); } From 5e35803f5a90805514de409985785c1e5db0837e Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Tue, 28 Nov 2023 21:11:42 +0100 Subject: [PATCH 09/37] Move `pin-project` dep to workspace deps --- Cargo.toml | 1 + gstd/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d43f328659d..8b9bb91430d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -454,6 +454,7 @@ toml = "0.7.8" # util tracing = "0.1.40" # utils/node-loder tracing-appender = "0.2" # utils/node-loder tracing-subscriber = "0.3.16" # utils/node-loder +pin-project = "1.1.3" # gstd trybuild = "1" # gstd/codegen wasm-opt = "0.111.0" # utils/wasm-builder wasmprinter = "0.2" # utils/wasm-gen diff --git a/gstd/Cargo.toml b/gstd/Cargo.toml index 4ead09361d2..9378eae442e 100644 --- a/gstd/Cargo.toml +++ b/gstd/Cargo.toml @@ -17,7 +17,7 @@ parity-scale-codec = { workspace = true, features = [ "derive" ] } primitive-types = { workspace = true, features = ["scale-info"] } scale-info = { workspace = true, features = ["derive"] } futures = { workspace = true, features = ["alloc"] } -pin-project = "1.1.3" +pin-project.workspace = true static_assertions.workspace = true From 41eb5f7816dc4ded6fa2e0f3ad21962b29ccce8b Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Tue, 28 Nov 2023 21:30:25 +0100 Subject: [PATCH 10/37] Fix docs --- gstd/src/critical.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 8cb50d6a492..ec4a25ab2ec 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -89,7 +89,7 @@ impl PinnedDrop for SectionFuture { } } -/// Extension for [`Future`](Future). +/// Extension for [`Future`]. pub trait SectionFutureExt: Future + Sized { /// Register critical section. fn critical(self, f: Func) -> SectionFuture From d9088f2b44d07bdf33e041b6716f6b641d556fcc Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Tue, 28 Nov 2023 21:33:50 +0100 Subject: [PATCH 11/37] Format docs --- gstd/src/critical.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index ec4a25ab2ec..4c604a9310f 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -22,7 +22,10 @@ //! only across `.await` calls because section has to be saved. //! //! ```rust,no_run -//! use gstd::{critical::{self, SectionFutureExt}, msg}; +//! use gstd::{ +//! critical::{self, SectionFutureExt}, +//! msg, +//! }; //! //! # async fn _dummy() { //! From 8a4a44d5b53b5f1f5989453fef106f4794072523 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Tue, 28 Nov 2023 21:45:15 +0100 Subject: [PATCH 12/37] Per-call sections --- gstd/src/async_runtime/mod.rs | 9 +++++---- gstd/src/critical.rs | 13 ++++++++----- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index 7e143831633..877ac4387b6 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -56,13 +56,14 @@ pub fn record_reply() { /// Default signal handler. pub fn handle_signal() { - if let Some(mut f) = critical::section().take() { - f(); - } - let msg_id = crate::msg::signal_from().expect( "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); + + if let Some(mut f) = critical::sections().remove(&msg_id) { + f(); + } + futures().remove(&msg_id); locks().remove_message_entry(msg_id); } diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 4c604a9310f..8493eeed1b0 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -52,18 +52,20 @@ //! # } //! ``` +use crate::{msg, MessageId}; use alloc::boxed::Box; use core::{ future::Future, pin::Pin, task::{Context, Poll}, }; +use hashbrown::HashMap; use pin_project::{pin_project, pinned_drop}; -static mut SECTION: Option> = None; +static mut SECTION: Option>> = None; -pub(crate) fn section() -> &'static mut Option> { - unsafe { &mut SECTION } +pub(crate) fn sections() -> &'static mut HashMap> { + unsafe { SECTION.get_or_insert_with(HashMap::new) } } /// Critical section future. @@ -88,7 +90,8 @@ where #[pinned_drop] impl PinnedDrop for SectionFuture { fn drop(self: Pin<&mut Self>) { - let _ = section().take(); + let func = sections().remove(&msg::id()); + assert!(func.is_some()); } } @@ -108,7 +111,7 @@ where where Func: FnMut() + 'static, { - let prev = section().replace(Box::new(func)); + let prev = sections().insert(msg::id(), Box::new(func)); assert!(prev.is_none()); SectionFuture { fut: self } } From 70256d46723a94da0e0ea9e9a4d7e12945b803dc Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Tue, 28 Nov 2023 21:48:36 +0100 Subject: [PATCH 13/37] Use `async-runtime` code style --- gstd/src/async_runtime/mod.rs | 15 +++++++++++---- gstd/src/critical.rs | 8 ++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index 877ac4387b6..a410dc080b4 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -22,13 +22,14 @@ mod signals; mod waker; pub use self::futures::message_loop; +pub(crate) use locks::Lock; +pub(crate) use signals::ReplyPoll; use self::futures::FuturesMap; -use crate::critical; +use crate::{critical::SectionsMap, MessageId}; +use alloc::boxed::Box; use hashbrown::HashMap; -pub(crate) use locks::Lock; use locks::LocksMap; -pub(crate) use signals::ReplyPoll; use signals::WakeSignals; static mut FUTURES: Option = None; @@ -49,6 +50,12 @@ pub(crate) fn locks() -> &'static mut LocksMap { unsafe { LOCKS.get_or_insert_with(LocksMap::default) } } +static mut SECTIONS: Option>> = None; + +pub(crate) fn sections() -> &'static mut SectionsMap { + unsafe { SECTIONS.get_or_insert_with(HashMap::new) } +} + /// Default reply handler. pub fn record_reply() { signals().record_reply(); @@ -60,7 +67,7 @@ pub fn handle_signal() { "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); - if let Some(mut f) = critical::sections().remove(&msg_id) { + if let Some(mut f) = sections().remove(&msg_id) { f(); } diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 8493eeed1b0..648a635fb27 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -52,7 +52,7 @@ //! # } //! ``` -use crate::{msg, MessageId}; +use crate::{async_runtime::sections, msg, MessageId}; use alloc::boxed::Box; use core::{ future::Future, @@ -62,11 +62,7 @@ use core::{ use hashbrown::HashMap; use pin_project::{pin_project, pinned_drop}; -static mut SECTION: Option>> = None; - -pub(crate) fn sections() -> &'static mut HashMap> { - unsafe { SECTION.get_or_insert_with(HashMap::new) } -} +pub(crate) type SectionsMap = HashMap>; /// Critical section future. #[pin_project(PinnedDrop)] From 97c28be9f06a1e1052e611807007bf898998fcb9 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Tue, 28 Nov 2023 22:23:19 +0100 Subject: [PATCH 14/37] Fix test --- gstd/src/critical.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 648a635fb27..3549197ad35 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -86,8 +86,13 @@ where #[pinned_drop] impl PinnedDrop for SectionFuture { fn drop(self: Pin<&mut Self>) { - let func = sections().remove(&msg::id()); - assert!(func.is_some()); + let _func = sections().remove(&msg::id()); + // future drops after `.await` is complete + // so `_func == Some(_)` + // + // and also drops in `handle_signal` during futures cleanup + // so `_func == None` + // because failing message ID must be obtained via `msg::signal_from()` } } From 57d07471c0c3b3e04b4c2864fd04dbdac9058933 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Wed, 29 Nov 2023 11:30:07 +0100 Subject: [PATCH 15/37] Fix module doc --- gstd/src/critical.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 3549197ad35..820f0beff3b 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -22,10 +22,7 @@ //! only across `.await` calls because section has to be saved. //! //! ```rust,no_run -//! use gstd::{ -//! critical::{self, SectionFutureExt}, -//! msg, -//! }; +//! use gstd::{critical::SectionFutureExt, msg}; //! //! # async fn _dummy() { //! @@ -36,7 +33,7 @@ //! msg::send_for_reply(msg::source(), "for_reply", 0, 0) //! .expect("Failed to send message") //! // register section -//! .critical(|| { +//! .critical(move || { //! msg::send(source, "example", 0).expect("Failed to send message"); //! }) //! // section will be saved now during `.await` From 4f168b2096e0451867283d06ecc905ba8107db68 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Wed, 29 Nov 2023 16:09:15 +0100 Subject: [PATCH 16/37] Register section during `.await` --- gstd/src/critical.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 820f0beff3b..767927ce702 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -64,24 +64,32 @@ pub(crate) type SectionsMap = HashMap>; /// Critical section future. #[pin_project(PinnedDrop)] #[must_use = "Future must be polled"] -pub struct SectionFuture { +pub struct SectionFuture { #[pin] fut: Fut, + func: Option, } -impl Future for SectionFuture +impl Future for SectionFuture where Fut: Future, + Func: FnMut() + 'static, { type Output = Fut::Output; - fn poll(self: Pin<&mut SectionFuture>, cx: &mut Context<'_>) -> Poll { - self.project().fut.poll(cx) + fn poll(self: Pin<&mut SectionFuture>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + if let Some(func) = this.func.take() { + let prev = sections().insert(msg::id(), Box::new(func)); + assert!(prev.is_none()); + } + + this.fut.poll(cx) } } #[pinned_drop] -impl PinnedDrop for SectionFuture { +impl PinnedDrop for SectionFuture { fn drop(self: Pin<&mut Self>) { let _func = sections().remove(&msg::id()); // future drops after `.await` is complete @@ -96,7 +104,7 @@ impl PinnedDrop for SectionFuture { /// Extension for [`Future`]. pub trait SectionFutureExt: Future + Sized { /// Register critical section. - fn critical(self, f: Func) -> SectionFuture + fn critical(self, f: Func) -> SectionFuture where Func: FnMut() + 'static; } @@ -105,12 +113,13 @@ impl SectionFutureExt for F where F: Future, { - fn critical(self, func: Func) -> SectionFuture + fn critical(self, func: Func) -> SectionFuture where Func: FnMut() + 'static, { - let prev = sections().insert(msg::id(), Box::new(func)); - assert!(prev.is_none()); - SectionFuture { fut: self } + SectionFuture { + fut: self, + func: Some(func), + } } } From 97e7843738367a1b3a41a51d9f4a2b37fa79a274 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Wed, 29 Nov 2023 16:54:18 +0100 Subject: [PATCH 17/37] Update docs --- gstd/src/critical.rs | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 767927ce702..d992513949a 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -25,20 +25,29 @@ //! use gstd::{critical::SectionFutureExt, msg}; //! //! # async fn _dummy() { -//! //! // get source outside of critical section //! // because `gr_source` sys-call is forbidden inside `handle_signal` entry point //! let source = msg::source(); //! -//! msg::send_for_reply(msg::source(), "for_reply", 0, 0) -//! .expect("Failed to send message") -//! // register section -//! .critical(move || { -//! msg::send(source, "example", 0).expect("Failed to send message"); -//! }) -//! // section will be saved now during `.await` -//! .await -//! .expect("Received error reply"); +//! let (msg0, msg1) = async { +//! let msg0 = msg::send_for_reply(source, "send_for_reply", 0, 0) +//! .expect("Failed to send message") +//! .await +//! .expect("Received error reply"); +//! +//! let msg1 = msg::send_with_gas_for_reply(source, "send_with_gas_for_reply", 100_000, 0, 0) +//! .expect("Failed to send message") +//! .await +//! .expect("Received error reply"); +//! +//! (msg0, msg1) +//! } +//! // can be used on any future +//! .critical(move || { +//! msg::send(source, "sends failed", 0).expect("Failed to send emergency message"); +//! }) +//! // critical section will be saved now during `.await` +//! .await; //! //! // if some code fails (panic, out of gas, etc) after `.await` //! // then saved section will be executed in `handle_signal` @@ -103,7 +112,8 @@ impl PinnedDrop for SectionFuture { /// Extension for [`Future`]. pub trait SectionFutureExt: Future + Sized { - /// Register critical section. + /// Creates future that registers critical section during polling + /// (e.g. `.await`). fn critical(self, f: Func) -> SectionFuture where Func: FnMut() + 'static; From 865867d4917b70153f4cd090f1a1addf28add5d6 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Thu, 30 Nov 2023 16:35:59 +0100 Subject: [PATCH 18/37] Redesign --- examples/async-critical/src/lib.rs | 2 +- examples/async-critical/src/wasm.rs | 42 +++++----- gstd/src/async_runtime/mod.rs | 12 ++- gstd/src/critical.rs | 122 ++++++++-------------------- pallets/gear/src/tests.rs | 4 +- 5 files changed, 64 insertions(+), 118 deletions(-) diff --git a/examples/async-critical/src/lib.rs b/examples/async-critical/src/lib.rs index 6e8e4acdad3..dba761c6065 100644 --- a/examples/async-critical/src/lib.rs +++ b/examples/async-critical/src/lib.rs @@ -32,7 +32,7 @@ use gstd::{Decode, Encode}; pub enum HandleAction { Simple, Panic, - DropWorks, + HookReset, } #[cfg(target_arch = "wasm32")] diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index 18d9bfc0b0e..97d23faaf09 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -27,7 +27,7 @@ //! If an approval is obtained the method replies with "PONG". use crate::HandleAction; -use gstd::{critical::SectionFutureExt, exec, msg, prelude::*}; +use gstd::{critical, exec, msg, prelude::*}; #[gstd::async_init] async fn init() {} @@ -41,12 +41,14 @@ async fn main() { // call `gr_source` outside because it is forbidden in `handle_signal` let source = msg::source(); + // should not send anything because execution will be completed + critical::set_hook(move || { + msg::send_bytes(msg::source(), b"critical", 0).unwrap(); + }); + + // wait occurs inside so hook is saved gstd::msg::send_bytes_for_reply(source, b"for_reply", 0, 0) .expect("Failed to send message") - // should not send anything as execution will be completed - .critical(move || { - msg::send_bytes(msg::source(), b"critical", 0).unwrap(); - }) .await .expect("Received error reply"); } @@ -54,37 +56,39 @@ async fn main() { // call `gr_source` outside because it is forbidden in `handle_signal` let source = msg::source(); + // should send message because panic occurs below + critical::set_hook(move || { + msg::send_bytes(source, b"critical", 0).unwrap(); + }); + + // wait occurs inside so hook is saved gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) .expect("Failed to send message") - // should send message because panic occurs below - .critical(move || { - msg::send_bytes(source, b"critical", 0).unwrap(); - }) .await .expect("Received error reply"); + // panic occurs so `handle_signal` will execute hook panic!(); } - HandleAction::DropWorks => { + HandleAction::HookReset => { // call `gr_source` outside because it is forbidden in `handle_signal` let source = msg::source(); + critical::set_hook(move || { + msg::send_bytes(source, b"critical0", 0).unwrap(); + }); + gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply0", 0, 0) .expect("Failed to send message") - // set section - .critical(move || { - msg::send_bytes(source, b"critical", 0).unwrap(); - }) .await - // after wait section function should be removed .expect("Received error reply"); + critical::set_hook(move || { + msg::send_bytes(source, b"critical1", 0).unwrap(); + }); + gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply1", 0, 0) .expect("Failed to send message") - // check if inside assertion panics - .critical(move || { - msg::send_bytes(source, b"critical", 0).unwrap(); - }) .await .expect("Received error reply"); } diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index a410dc080b4..b36ff8057e7 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -26,7 +26,7 @@ pub(crate) use locks::Lock; pub(crate) use signals::ReplyPoll; use self::futures::FuturesMap; -use crate::{critical::SectionsMap, MessageId}; +use crate::{critical, critical::HooksMap, MessageId}; use alloc::boxed::Box; use hashbrown::HashMap; use locks::LocksMap; @@ -50,10 +50,10 @@ pub(crate) fn locks() -> &'static mut LocksMap { unsafe { LOCKS.get_or_insert_with(LocksMap::default) } } -static mut SECTIONS: Option>> = None; +static mut HOOKS: Option>> = None; -pub(crate) fn sections() -> &'static mut SectionsMap { - unsafe { SECTIONS.get_or_insert_with(HashMap::new) } +pub(crate) fn hooks() -> &'static mut HooksMap { + unsafe { HOOKS.get_or_insert_with(HashMap::new) } } /// Default reply handler. @@ -67,9 +67,7 @@ pub fn handle_signal() { "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); - if let Some(mut f) = sections().remove(&msg_id) { - f(); - } + critical::execute_hook_once(); futures().remove(&msg_id); locks().remove_message_entry(msg_id); diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index d992513949a..72edeb2eb5d 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -16,120 +16,64 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Critical section that guarantees code section execution +//! Critical hook that guarantees code section execution //! //! Code is executed in `handle_signal` entry point in case of failure //! only across `.await` calls because section has to be saved. //! //! ```rust,no_run -//! use gstd::{critical::SectionFutureExt, msg}; +//! use gstd::{critical, msg}; //! //! # async fn _dummy() { -//! // get source outside of critical section +//! // get source outside of critical hook //! // because `gr_source` sys-call is forbidden inside `handle_signal` entry point //! let source = msg::source(); //! -//! let (msg0, msg1) = async { -//! let msg0 = msg::send_for_reply(source, "send_for_reply", 0, 0) -//! .expect("Failed to send message") -//! .await -//! .expect("Received error reply"); -//! -//! let msg1 = msg::send_with_gas_for_reply(source, "send_with_gas_for_reply", 100_000, 0, 0) -//! .expect("Failed to send message") -//! .await -//! .expect("Received error reply"); -//! -//! (msg0, msg1) -//! } -//! // can be used on any future -//! .critical(move || { +//! critical::set_hook(|| { //! msg::send(source, "sends failed", 0).expect("Failed to send emergency message"); -//! }) -//! // critical section will be saved now during `.await` -//! .await; +//! }); //! -//! // if some code fails (panic, out of gas, etc) after `.await` -//! // then saved section will be executed in `handle_signal` +//! let msg0 = msg::send_for_reply(source, "send_for_reply", 0, 0) +//! .expect("Failed to send message") +//! .await +//! .expect("Received error reply"); +//! +//! // if some code fails (panic, out of gas, etc) after `.await` or [`exec::wait()`] and friends +//! // then saved hook will be executed in `handle_signal` //! //! // your code //! // ... //! //! # } //! ``` +//! +//! [`exec::wait()`]: crate::exec::wait -use crate::{async_runtime::sections, msg, MessageId}; +use crate::{async_runtime::hooks, msg, MessageId}; use alloc::boxed::Box; -use core::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; use hashbrown::HashMap; -use pin_project::{pin_project, pinned_drop}; -pub(crate) type SectionsMap = HashMap>; +pub(crate) type HooksMap = HashMap>; -/// Critical section future. -#[pin_project(PinnedDrop)] -#[must_use = "Future must be polled"] -pub struct SectionFuture { - #[pin] - fut: Fut, - func: Option, +/// Sets critical hook. +pub fn set_hook(f: F) { + hooks().insert(msg::id(), Box::new(f)); } -impl Future for SectionFuture -where - Fut: Future, - Func: FnMut() + 'static, -{ - type Output = Fut::Output; - - fn poll(self: Pin<&mut SectionFuture>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - if let Some(func) = this.func.take() { - let prev = sections().insert(msg::id(), Box::new(func)); - assert!(prev.is_none()); - } - - this.fut.poll(cx) - } -} - -#[pinned_drop] -impl PinnedDrop for SectionFuture { - fn drop(self: Pin<&mut Self>) { - let _func = sections().remove(&msg::id()); - // future drops after `.await` is complete - // so `_func == Some(_)` - // - // and also drops in `handle_signal` during futures cleanup - // so `_func == None` - // because failing message ID must be obtained via `msg::signal_from()` - } -} - -/// Extension for [`Future`]. -pub trait SectionFutureExt: Future + Sized { - /// Creates future that registers critical section during polling - /// (e.g. `.await`). - fn critical(self, f: Func) -> SectionFuture - where - Func: FnMut() + 'static; -} +/// Executes critical hook and removes it. +/// +/// Must be called inside `handle_signal` or +/// don't be used at all if you use +/// [`#[gstd::async_init]`] or [`#[gstd::async_main]`]. +/// +/// [`#[gstd::async_init]`]: crate::async_init +/// [`#[gstd::async_main]`]: crate::async_main +pub fn execute_hook_once() { + let msg_id = msg::signal_from().expect( + "`gstd::critical::execute_hook_once()` must be called only in `handle_signal` entrypoint", + ); -impl SectionFutureExt for F -where - F: Future, -{ - fn critical(self, func: Func) -> SectionFuture - where - Func: FnMut() + 'static, - { - SectionFuture { - fut: self, - func: Some(func), - } + if let Some(mut f) = hooks().remove(&msg_id) { + f(); } } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index a613ce2a36d..5e414fb344f 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -15124,7 +15124,7 @@ fn critical_section_with_panic() { } #[test] -fn critical_section_drop_works() { +fn critical_section_hook_reset() { use demo_async_critical::{HandleAction, WASM_BINARY}; init_logger(); @@ -15148,7 +15148,7 @@ fn critical_section_drop_works() { assert_ok!(Gear::send_message( RuntimeOrigin::signed(USER_1), pid, - HandleAction::DropWorks.encode(), + HandleAction::HookReset.encode(), 10_000_000_000, 0, false, From 3bc7a2b7406dbd5df2fd2449de09b0dd8c4990fa Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Thu, 30 Nov 2023 16:42:16 +0100 Subject: [PATCH 19/37] Update doc --- gstd/src/critical.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 72edeb2eb5d..6e6de66d0f2 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -19,7 +19,7 @@ //! Critical hook that guarantees code section execution //! //! Code is executed in `handle_signal` entry point in case of failure -//! only across `.await` calls because section has to be saved. +//! only across [`exec::wait()`] calls because hook has to be saved. //! //! ```rust,no_run //! use gstd::{critical, msg}; @@ -35,10 +35,11 @@ //! //! let msg0 = msg::send_for_reply(source, "send_for_reply", 0, 0) //! .expect("Failed to send message") +//! // await on `MessageFuture` which calls `exec::wait()` inside //! .await //! .expect("Received error reply"); //! -//! // if some code fails (panic, out of gas, etc) after `.await` or [`exec::wait()`] and friends +//! // if some code fails (panic, out of gas, etc) after `exec::wait()` and friends //! // then saved hook will be executed in `handle_signal` //! //! // your code From e05ccd81af06218395cbfb52be8617b48e354d04 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Thu, 30 Nov 2023 16:43:50 +0100 Subject: [PATCH 20/37] Remove `pin-project` dep --- Cargo.lock | 1 - Cargo.toml | 1 - gstd/Cargo.toml | 1 - 3 files changed, 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c98362edca..f9d82557fec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4888,7 +4888,6 @@ dependencies = [ "hashbrown 0.14.2", "hex", "parity-scale-codec", - "pin-project", "primitive-types", "scale-info", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index 1de557d91ae..b5283d12daf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -458,7 +458,6 @@ toml = "0.7.8" # util tracing = "0.1.40" # utils/node-loder tracing-appender = "0.2" # utils/node-loder tracing-subscriber = "0.3.18" # utils/node-loder -pin-project = "1.1.3" # gstd trybuild = "1" # gstd/codegen wasm-opt = "0.111.0" # utils/wasm-builder wasmprinter = "0.2" # utils/wasm-gen diff --git a/gstd/Cargo.toml b/gstd/Cargo.toml index 9c0aaa7d23b..ab8ddff724f 100644 --- a/gstd/Cargo.toml +++ b/gstd/Cargo.toml @@ -18,7 +18,6 @@ parity-scale-codec = { workspace = true, features = ["derive"] } primitive-types = { workspace = true, features = ["scale-info"] } scale-info = { workspace = true, features = ["derive"] } futures = { workspace = true, features = ["alloc"] } -pin-project.workspace = true static_assertions.workspace = true From eca363f4eae73e4f7d5153a47ec9c6abc62d3171 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Thu, 30 Nov 2023 17:11:27 +0100 Subject: [PATCH 21/37] Fix module doc --- gstd/src/critical.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 6e6de66d0f2..cee0dc70f59 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -29,7 +29,7 @@ //! // because `gr_source` sys-call is forbidden inside `handle_signal` entry point //! let source = msg::source(); //! -//! critical::set_hook(|| { +//! critical::set_hook(move || { //! msg::send(source, "sends failed", 0).expect("Failed to send emergency message"); //! }); //! From 15787135da176c0e0e520ad8b7486c6878560ab8 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Thu, 30 Nov 2023 17:35:47 +0100 Subject: [PATCH 22/37] Docs and move static to critical.rs --- gstd/src/async_runtime/mod.rs | 9 +-------- gstd/src/critical.rs | 28 +++++++++++++++++++++------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index b36ff8057e7..02dcd8703d0 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -26,8 +26,7 @@ pub(crate) use locks::Lock; pub(crate) use signals::ReplyPoll; use self::futures::FuturesMap; -use crate::{critical, critical::HooksMap, MessageId}; -use alloc::boxed::Box; +use crate::critical; use hashbrown::HashMap; use locks::LocksMap; use signals::WakeSignals; @@ -50,12 +49,6 @@ pub(crate) fn locks() -> &'static mut LocksMap { unsafe { LOCKS.get_or_insert_with(LocksMap::default) } } -static mut HOOKS: Option>> = None; - -pub(crate) fn hooks() -> &'static mut HooksMap { - unsafe { HOOKS.get_or_insert_with(HashMap::new) } -} - /// Default reply handler. pub fn record_reply() { signals().record_reply(); diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index cee0dc70f59..dee5424c6ad 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -50,11 +50,17 @@ //! //! [`exec::wait()`]: crate::exec::wait -use crate::{async_runtime::hooks, msg, MessageId}; +use crate::{msg, MessageId}; use alloc::boxed::Box; use hashbrown::HashMap; -pub(crate) type HooksMap = HashMap>; +type HooksMap = HashMap>; + +static mut HOOKS: Option = None; + +fn hooks() -> &'static mut HooksMap { + unsafe { HOOKS.get_or_insert_with(HashMap::new) } +} /// Sets critical hook. pub fn set_hook(f: F) { @@ -63,12 +69,20 @@ pub fn set_hook(f: F) { /// Executes critical hook and removes it. /// -/// Must be called inside `handle_signal` or -/// don't be used at all if you use -/// [`#[gstd::async_init]`] or [`#[gstd::async_main]`]. +/// Must be called inside `handle_signal`: +/// +/// ```rust,no_run +/// use gstd::critical; +/// +/// #[no_mangle] +/// extern "C" fn handle_signal() { +/// critical::execute_hook_once(); +/// } +/// ``` /// -/// [`#[gstd::async_init]`]: crate::async_init -/// [`#[gstd::async_main]`]: crate::async_main +/// or __don't__ be used at all if you use +/// [`#[gstd::async_init]`](crate::async_init) or +/// [`#[gstd::async_main]`](crate::async_main). pub fn execute_hook_once() { let msg_id = msg::signal_from().expect( "`gstd::critical::execute_hook_once()` must be called only in `handle_signal` entrypoint", From 331d1672fc4f0b029f9237e1d6c1562e14c527db Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Thu, 30 Nov 2023 17:36:30 +0100 Subject: [PATCH 23/37] Remove obsolete doc --- examples/async-critical/src/wasm.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index 97d23faaf09..6a23b03b175 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -16,16 +16,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! The program demonstrates asynchronous execution and -//! how to use macros `gstd::async_init`/`gstd::async_main`. -//! -//! `Init` method gets three addresses, sends "PING" messages -//! to them and waits for at least two replies with any payload ("approvals"). -//! -//! `Handle` processes only "PING" messages. When `handle` gets such message -//! it sends empty requests to the three addresses and waits for just one approval. -//! If an approval is obtained the method replies with "PONG". - use crate::HandleAction; use gstd::{critical, exec, msg, prelude::*}; From 079de67c11da955fc86b8ed7a10301d236d98260 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Thu, 30 Nov 2023 18:27:24 +0100 Subject: [PATCH 24/37] Update module doc --- gstd/src/critical.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index dee5424c6ad..e9e0eba0877 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Critical hook that guarantees code section execution +//! Critical hook that guarantees code section execution. //! //! Code is executed in `handle_signal` entry point in case of failure //! only across [`exec::wait()`] calls because hook has to be saved. @@ -33,9 +33,10 @@ //! msg::send(source, "sends failed", 0).expect("Failed to send emergency message"); //! }); //! -//! let msg0 = msg::send_for_reply(source, "send_for_reply", 0, 0) +//! let msg = msg::send_for_reply(source, "send_for_reply", 0, 0) //! .expect("Failed to send message") //! // await on `MessageFuture` which calls `exec::wait()` inside +//! // so program state will be saved and thus hook will too //! .await //! .expect("Received error reply"); //! From f4d22a143610a27df7c2785bff7dc22ecf8d3c82 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 4 Dec 2023 17:15:46 +0100 Subject: [PATCH 25/37] Remove async init --- examples/async-critical/src/wasm.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index 6a23b03b175..8cdf4452281 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -19,9 +19,6 @@ use crate::HandleAction; use gstd::{critical, exec, msg, prelude::*}; -#[gstd::async_init] -async fn init() {} - #[gstd::async_main] async fn main() { let action: HandleAction = msg::load().expect("Failed to read handle action"); From b35aa9e253de4c90132209af4608420f4c40fb62 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 4 Dec 2023 17:32:32 +0100 Subject: [PATCH 26/37] Check dispatch status of message in test --- pallets/gear/src/tests.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 78a331b9d43..e4c5ec554b3 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -15007,8 +15007,14 @@ fn critical_section_works() { false, )); + let mid = get_last_message_id(); + run_to_block(3, None); + let (waited, _) = get_last_message_waited(); + assert_eq!(mid, waited); + assert_eq!(dispatch_status(mid), None); + let msg = get_last_mail(USER_1); assert_eq!(msg.payload_bytes(), b"for_reply"); @@ -15023,6 +15029,7 @@ fn critical_section_works() { run_to_block(4, None); + assert_succeed(mid); assert_eq!(MailboxOf::::iter_key(USER_1).count(), 0); }); } From 5bced2b503c3d45dc92bd2128d5d095498a708bc Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 18:20:01 +0300 Subject: [PATCH 27/37] Remove hook reset test --- examples/async-critical/src/lib.rs | 1 - examples/async-critical/src/wasm.rs | 24 +---------- pallets/gear/src/tests.rs | 63 ----------------------------- 3 files changed, 1 insertion(+), 87 deletions(-) diff --git a/examples/async-critical/src/lib.rs b/examples/async-critical/src/lib.rs index dba761c6065..44c7e9a3c4c 100644 --- a/examples/async-critical/src/lib.rs +++ b/examples/async-critical/src/lib.rs @@ -32,7 +32,6 @@ use gstd::{Decode, Encode}; pub enum HandleAction { Simple, Panic, - HookReset, } #[cfg(target_arch = "wasm32")] diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index 8cdf4452281..9bd1bb86e74 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -30,7 +30,7 @@ async fn main() { // should not send anything because execution will be completed critical::set_hook(move || { - msg::send_bytes(msg::source(), b"critical", 0).unwrap(); + msg::send_bytes(source, b"critical", 0).unwrap(); }); // wait occurs inside so hook is saved @@ -57,27 +57,5 @@ async fn main() { // panic occurs so `handle_signal` will execute hook panic!(); } - HandleAction::HookReset => { - // call `gr_source` outside because it is forbidden in `handle_signal` - let source = msg::source(); - - critical::set_hook(move || { - msg::send_bytes(source, b"critical0", 0).unwrap(); - }); - - gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply0", 0, 0) - .expect("Failed to send message") - .await - .expect("Received error reply"); - - critical::set_hook(move || { - msg::send_bytes(source, b"critical1", 0).unwrap(); - }); - - gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply1", 0, 0) - .expect("Failed to send message") - .await - .expect("Received error reply"); - } } } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index e4c5ec554b3..c84dd1b4eb6 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -15093,69 +15093,6 @@ fn critical_section_with_panic() { }); } -#[test] -fn critical_section_hook_reset() { - use demo_async_critical::{HandleAction, WASM_BINARY}; - - init_logger(); - new_test_ext().execute_with(|| { - assert_ok!(Gear::upload_program( - RuntimeOrigin::signed(USER_1), - WASM_BINARY.to_vec(), - DEFAULT_SALT.to_vec(), - vec![], - 10_000_000_000, - 0, - false, - )); - let pid = get_last_program_id(); - - run_to_block(2, None); - - assert!(Gear::is_initialized(pid)); - assert!(Gear::is_active(pid)); - - assert_ok!(Gear::send_message( - RuntimeOrigin::signed(USER_1), - pid, - HandleAction::HookReset.encode(), - 10_000_000_000, - 0, - false, - )); - - run_to_block(3, None); - - let msg = get_last_mail(USER_1); - assert_eq!(msg.payload_bytes(), b"for_reply0"); - - assert_ok!(Gear::send_reply( - RuntimeOrigin::signed(USER_1), - msg.id(), - EMPTY_PAYLOAD.to_vec(), - 10_000_000_000, - 0, - false, - )); - - run_to_block(4, None); - - let msg = get_last_mail(USER_1); - assert_eq!(msg.payload_bytes(), b"for_reply1"); - - assert_ok!(Gear::send_reply( - RuntimeOrigin::signed(USER_1), - msg.id(), - EMPTY_PAYLOAD.to_vec(), - 10_000_000_000, - 0, - false, - )); - - run_to_block(5, None); - }); -} - mod utils { #![allow(unused)] From 0cb1459110ce4a687614c59861c9184148ab69da Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 18:27:07 +0300 Subject: [PATCH 28/37] Add TODO --- gstd/src/critical.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index e9e0eba0877..43d5e301624 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +// TODO: describe denied sys-calls in entrypoint (#3580) //! Critical hook that guarantees code section execution. //! //! Code is executed in `handle_signal` entry point in case of failure From cb96641c1e509571b4d5fcfdb0750f74e03d3cdc Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 19:17:08 +0300 Subject: [PATCH 29/37] Forbid `set_hook` in `handle_signal` and `handle_reply` --- examples/async-critical/src/lib.rs | 2 + examples/async-critical/src/wasm.rs | 45 ++++++++++- gstd/src/critical.rs | 8 ++ pallets/gear/src/tests.rs | 114 ++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+), 1 deletion(-) diff --git a/examples/async-critical/src/lib.rs b/examples/async-critical/src/lib.rs index 44c7e9a3c4c..7ae8e99ce47 100644 --- a/examples/async-critical/src/lib.rs +++ b/examples/async-critical/src/lib.rs @@ -32,6 +32,8 @@ use gstd::{Decode, Encode}; pub enum HandleAction { Simple, Panic, + InHandleReply, + InHandleSignal, } #[cfg(target_arch = "wasm32")] diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index 9bd1bb86e74..8ea5ba35842 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -19,7 +19,10 @@ use crate::HandleAction; use gstd::{critical, exec, msg, prelude::*}; -#[gstd::async_main] +static mut REPLY_SET_HOOK: bool = false; +static mut SIGNAL_SET_HOOK: bool = false; + +#[gstd::async_main(handle_reply = my_handle_reply, handle_signal = my_handle_signal)] async fn main() { let action: HandleAction = msg::load().expect("Failed to read handle action"); @@ -57,5 +60,45 @@ async fn main() { // panic occurs so `handle_signal` will execute hook panic!(); } + HandleAction::InHandleReply => { + unsafe { + REPLY_SET_HOOK = true; + } + + gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) + .expect("Failed to send message") + .await + .expect("Received error reply"); + } + HandleAction::InHandleSignal => { + unsafe { + SIGNAL_SET_HOOK = true; + } + + gstd::msg::send_bytes_for_reply(msg::source(), b"for_reply", 0, 0) + .expect("Failed to send message") + .await + .expect("Received error reply"); + + panic!() + } + } +} + +fn my_handle_reply() { + unsafe { + if REPLY_SET_HOOK { + // should panic in this entrypoint + critical::set_hook(|| {}); + } + } +} + +fn my_handle_signal() { + unsafe { + if SIGNAL_SET_HOOK { + // should panic in this entrypoint + critical::set_hook(|| {}); + } } } diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 43d5e301624..8b991542a9e 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -66,6 +66,14 @@ fn hooks() -> &'static mut HooksMap { /// Sets critical hook. pub fn set_hook(f: F) { + if msg::reply_code().is_ok() { + panic!("`gstd::critical::set_hook()` must not be called in `handle_reply` entrypoint") + } + + if msg::signal_code().is_ok() { + panic!("`gstd::critical::set_hook()` must not be called in `handle_signal` entrypoint") + } + hooks().insert(msg::id(), Box::new(f)); } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index ac077e613d5..c5acfa6fa06 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -15313,6 +15313,118 @@ fn critical_section_with_panic() { }); } +#[test] +fn critical_hook_in_handle_reply() { + use demo_async_critical::{HandleAction, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + vec![], + 10_000_000_000, + 0, + false, + )); + let pid = get_last_program_id(); + + run_to_block(2, None); + + assert!(Gear::is_initialized(pid)); + assert!(Gear::is_active(pid)); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + HandleAction::InHandleReply.encode(), + 10_000_000_000, + 0, + false, + )); + + run_to_block(3, None); + + let msg = get_last_mail(USER_1); + assert_eq!(msg.payload_bytes(), b"for_reply"); + + assert_ok!(Gear::send_reply( + RuntimeOrigin::signed(USER_1), + msg.id(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000, + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_block(4, None); + + assert_failed( + mid, + ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic), + ); + }); +} + +#[test] +fn critical_hook_in_handle_signal() { + use demo_async_critical::{HandleAction, WASM_BINARY}; + + init_logger(); + new_test_ext().execute_with(|| { + assert_ok!(Gear::upload_program( + RuntimeOrigin::signed(USER_1), + WASM_BINARY.to_vec(), + DEFAULT_SALT.to_vec(), + vec![], + 10_000_000_000, + 0, + false, + )); + let pid = get_last_program_id(); + + run_to_block(2, None); + + assert!(Gear::is_initialized(pid)); + assert!(Gear::is_active(pid)); + + assert_ok!(Gear::send_message( + RuntimeOrigin::signed(USER_1), + pid, + HandleAction::InHandleSignal.encode(), + 10_000_000_000, + 0, + false, + )); + + let mid = get_last_message_id(); + + run_to_block(3, None); + + let msg = get_last_mail(USER_1); + assert_eq!(msg.payload_bytes(), b"for_reply"); + + assert_ok!(Gear::send_reply( + RuntimeOrigin::signed(USER_1), + msg.id(), + EMPTY_PAYLOAD.to_vec(), + 10_000_000_000, + 0, + false, + )); + + run_to_block(4, None); + + assert_eq!(MailboxOf::::iter_key(USER_1).last(), None); + let signal_msg_id = MessageId::generate_signal(mid); + let status = dispatch_status(signal_msg_id); + assert_eq!(status, Some(DispatchStatus::Failed)); + }); +} + mod utils { #![allow(unused)] @@ -15668,6 +15780,7 @@ mod utils { }) } + #[track_caller] pub(super) fn dispatch_status(message_id: MessageId) -> Option { let mut found_status: Option = None; System::events().iter().for_each(|e| { @@ -15692,6 +15805,7 @@ mod utils { assert_eq!(status, DispatchStatus::Success) } + #[track_caller] fn get_last_event_error_and_reply_code(message_id: MessageId) -> (String, ReplyCode) { let mut actual_error = None; From f7b8566d95fd0aff306f26280ce98d47b6f5cef0 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 19:17:33 +0300 Subject: [PATCH 30/37] Rename tests --- pallets/gear/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index c5acfa6fa06..56057aadaa2 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -15197,7 +15197,7 @@ fn test_constructor_if_else() { } #[test] -fn critical_section_works() { +fn critical_hook_works() { use demo_async_critical::{HandleAction, WASM_BINARY}; init_logger(); @@ -15255,7 +15255,7 @@ fn critical_section_works() { } #[test] -fn critical_section_with_panic() { +fn critical_hook_with_panic() { use demo_async_critical::{HandleAction, WASM_BINARY}; init_logger(); From 0cfe986c23d615316368cd3f14b9cfe9b932874a Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 19:20:35 +0300 Subject: [PATCH 31/37] Remove hook on successful execution --- gstd/src/async_runtime/futures.rs | 3 ++- gstd/src/async_runtime/mod.rs | 4 +++- gstd/src/critical.rs | 12 ++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/gstd/src/async_runtime/futures.rs b/gstd/src/async_runtime/futures.rs index bcf5f01e441..287cb182968 100644 --- a/gstd/src/async_runtime/futures.rs +++ b/gstd/src/async_runtime/futures.rs @@ -18,7 +18,7 @@ //! Module for future-management. -use crate::{prelude::Box, MessageId}; +use crate::{critical, prelude::Box, MessageId}; use core::{ future::Future, pin::Pin, @@ -87,6 +87,7 @@ where if Pin::new(&mut task.future).poll(&mut cx).is_ready() { super::futures().remove(&msg_id); super::locks().remove_message_entry(msg_id); + let _ = critical::take_hook(); } else { super::locks().wait(msg_id); } diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index 02dcd8703d0..33598003eab 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -60,7 +60,9 @@ pub fn handle_signal() { "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); - critical::execute_hook_once(); + if let Some(f) = critical::take_hook() { + f(); + } futures().remove(&msg_id); locks().remove_message_entry(msg_id); diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 8b991542a9e..3bf285a5831 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -77,7 +77,7 @@ pub fn set_hook(f: F) { hooks().insert(msg::id(), Box::new(f)); } -/// Executes critical hook and removes it. +/// Removes current hook and returns it. /// /// Must be called inside `handle_signal`: /// @@ -86,19 +86,19 @@ pub fn set_hook(f: F) { /// /// #[no_mangle] /// extern "C" fn handle_signal() { -/// critical::execute_hook_once(); +/// if let Some(f) = critical::take_hook() { +/// f(); +/// } /// } /// ``` /// /// or __don't__ be used at all if you use /// [`#[gstd::async_init]`](crate::async_init) or /// [`#[gstd::async_main]`](crate::async_main). -pub fn execute_hook_once() { +pub fn take_hook() -> Option> { let msg_id = msg::signal_from().expect( "`gstd::critical::execute_hook_once()` must be called only in `handle_signal` entrypoint", ); - if let Some(mut f) = hooks().remove(&msg_id) { - f(); - } + hooks().remove(&msg_id) } From 19cbd1725ebbb79570b9e9014f25286e9d153de4 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 19:22:50 +0300 Subject: [PATCH 32/37] Update module doc --- gstd/src/critical.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index 3bf285a5831..c4078ac3fb0 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -19,6 +19,8 @@ // TODO: describe denied sys-calls in entrypoint (#3580) //! Critical hook that guarantees code section execution. //! +//! __Hook is set on per-message basis.__ +//! //! Code is executed in `handle_signal` entry point in case of failure //! only across [`exec::wait()`] calls because hook has to be saved. //! From 18f3b92eb28968478e730581f0651546d5a2c70f Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 19:33:32 +0300 Subject: [PATCH 33/37] Fix missing `mut` attr --- gstd/src/async_runtime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index 33598003eab..297bfa5a8a7 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -60,7 +60,7 @@ pub fn handle_signal() { "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); - if let Some(f) = critical::take_hook() { + if let Some(mut f) = critical::take_hook() { f(); } From 948e15a6f7437e7c7451b6e542f6d18461957812 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 19:33:44 +0300 Subject: [PATCH 34/37] Update docs --- gstd/src/critical.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index c4078ac3fb0..b98b74ee8cd 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -81,22 +81,40 @@ pub fn set_hook(f: F) { /// Removes current hook and returns it. /// -/// Must be called inside `handle_signal`: +/// __Don't use it at all if you use +/// [`#[gstd::async_init]`](crate::async_init) or +/// [`#[gstd::async_main]`](crate::async_main).__ +/// +/// Must be called at the end of `init` or `handle` +/// to now blow up map because hook is set on per-message basis: +/// +/// ```rust,no_run +/// use gstd::critical; +/// +/// #[no_mangle] +/// extern "C" fn handle() { +/// critical::set_hook(|| { +/// // some code... +/// }); +/// +/// // handle code... +/// +/// let _ = critical::take_hook(); +/// } +/// ``` +/// +/// And must be called inside `handle_signal`: /// /// ```rust,no_run /// use gstd::critical; /// /// #[no_mangle] /// extern "C" fn handle_signal() { -/// if let Some(f) = critical::take_hook() { +/// if let Some(mut f) = critical::take_hook() { /// f(); /// } /// } /// ``` -/// -/// or __don't__ be used at all if you use -/// [`#[gstd::async_init]`](crate::async_init) or -/// [`#[gstd::async_main]`](crate::async_main). pub fn take_hook() -> Option> { let msg_id = msg::signal_from().expect( "`gstd::critical::execute_hook_once()` must be called only in `handle_signal` entrypoint", From 80b95a94ec3f80f6d9f4701dec66348e54b70912 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Mon, 11 Dec 2023 21:09:39 +0300 Subject: [PATCH 35/37] Fix `take_hook` wrong call --- examples/async-critical/src/wasm.rs | 12 ++++++++++-- gstd/src/async_runtime/mod.rs | 4 +--- gstd/src/critical.rs | 23 ++++++++++++++++------- pallets/gear/src/tests.rs | 7 +++---- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index 8ea5ba35842..74ed1a16f4a 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -88,8 +88,12 @@ async fn main() { fn my_handle_reply() { unsafe { if REPLY_SET_HOOK { + let source = msg::source(); + // should panic in this entrypoint - critical::set_hook(|| {}); + critical::set_hook(move || { + msg::send_bytes(source, b"from_handle_reply", 0).unwrap(); + }); } } } @@ -97,8 +101,12 @@ fn my_handle_reply() { fn my_handle_signal() { unsafe { if SIGNAL_SET_HOOK { + let source = msg::source(); + // should panic in this entrypoint - critical::set_hook(|| {}); + critical::set_hook(move || { + msg::send_bytes(source, b"from_handle_signal", 0).unwrap(); + }); } } } diff --git a/gstd/src/async_runtime/mod.rs b/gstd/src/async_runtime/mod.rs index 297bfa5a8a7..3b2697e223e 100644 --- a/gstd/src/async_runtime/mod.rs +++ b/gstd/src/async_runtime/mod.rs @@ -60,9 +60,7 @@ pub fn handle_signal() { "`gstd::async_runtime::handle_signal()` must be called only in `handle_signal` entrypoint", ); - if let Some(mut f) = critical::take_hook() { - f(); - } + critical::take_and_execute(); futures().remove(&msg_id); locks().remove_message_entry(msg_id); diff --git a/gstd/src/critical.rs b/gstd/src/critical.rs index b98b74ee8cd..134ff7f2cfb 100644 --- a/gstd/src/critical.rs +++ b/gstd/src/critical.rs @@ -86,7 +86,7 @@ pub fn set_hook(f: F) { /// [`#[gstd::async_main]`](crate::async_main).__ /// /// Must be called at the end of `init` or `handle` -/// to now blow up map because hook is set on per-message basis: +/// to not blow up map because hook is set on per-message basis: /// /// ```rust,no_run /// use gstd::critical; @@ -102,23 +102,32 @@ pub fn set_hook(f: F) { /// let _ = critical::take_hook(); /// } /// ``` +pub fn take_hook() -> Option> { + hooks().remove(&msg::id()) +} + +/// Removes current hook and executes it. /// -/// And must be called inside `handle_signal`: +/// __Don't use it at all if you use +/// [`#[gstd::async_init]`](crate::async_init) or +/// [`#[gstd::async_main]`](crate::async_main).__ +/// +/// Must be called inside `handle_signal`: /// /// ```rust,no_run /// use gstd::critical; /// /// #[no_mangle] /// extern "C" fn handle_signal() { -/// if let Some(mut f) = critical::take_hook() { -/// f(); -/// } +/// critical::take_and_execute(); /// } /// ``` -pub fn take_hook() -> Option> { +pub fn take_and_execute() { let msg_id = msg::signal_from().expect( "`gstd::critical::execute_hook_once()` must be called only in `handle_signal` entrypoint", ); - hooks().remove(&msg_id) + if let Some(mut f) = hooks().remove(&msg_id) { + f(); + } } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 56057aadaa2..fae52c3cd1d 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -15362,10 +15362,9 @@ fn critical_hook_in_handle_reply() { run_to_block(4, None); - assert_failed( - mid, - ErrorReplyReason::Execution(SimpleExecutionError::UserspacePanic), - ); + assert_eq!(MailboxOf::::iter_key(USER_1).last(), None); + let status = dispatch_status(mid); + assert_eq!(status, Some(DispatchStatus::Failed)); }); } From 3876e34be1cff1b44f43b4679d065e34fe6a390c Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Wed, 10 Jan 2024 17:06:32 +0300 Subject: [PATCH 36/37] Don't call `gr_source` in other entry points --- examples/async-critical/src/wasm.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index 74ed1a16f4a..9116c6966b2 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -17,13 +17,16 @@ // along with this program. If not, see . use crate::HandleAction; -use gstd::{critical, exec, msg, prelude::*}; +use gstd::{critical, exec, msg, prelude::*, ActorId}; static mut REPLY_SET_HOOK: bool = false; static mut SIGNAL_SET_HOOK: bool = false; +static mut INITIATOR: ActorId = ActorId::zero(); #[gstd::async_main(handle_reply = my_handle_reply, handle_signal = my_handle_signal)] async fn main() { + unsafe { INITIATOR = msg::source() }; + let action: HandleAction = msg::load().expect("Failed to read handle action"); match action { @@ -88,11 +91,9 @@ async fn main() { fn my_handle_reply() { unsafe { if REPLY_SET_HOOK { - let source = msg::source(); - // should panic in this entrypoint critical::set_hook(move || { - msg::send_bytes(source, b"from_handle_reply", 0).unwrap(); + msg::send_bytes(INITIATOR, b"from_handle_reply", 0).unwrap(); }); } } @@ -101,12 +102,11 @@ fn my_handle_reply() { fn my_handle_signal() { unsafe { if SIGNAL_SET_HOOK { - let source = msg::source(); - // should panic in this entrypoint critical::set_hook(move || { - msg::send_bytes(source, b"from_handle_signal", 0).unwrap(); + msg::send_bytes(INITIATOR, b"from_handle_signal", 0).unwrap(); }); } } } +t \ No newline at end of file From 56757cfd1e4785be9c812dbe9614948f806a5882 Mon Sep 17 00:00:00 2001 From: Arsenii Lyashenko Date: Wed, 10 Jan 2024 17:09:58 +0300 Subject: [PATCH 37/37] Remove accidental symbol --- examples/async-critical/src/wasm.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/async-critical/src/wasm.rs b/examples/async-critical/src/wasm.rs index 9116c6966b2..8304255138a 100644 --- a/examples/async-critical/src/wasm.rs +++ b/examples/async-critical/src/wasm.rs @@ -109,4 +109,3 @@ fn my_handle_signal() { } } } -t \ No newline at end of file