diff --git a/linera-execution/src/lib.rs b/linera-execution/src/lib.rs index e1923deb5215..27685c2f4464 100644 --- a/linera-execution/src/lib.rs +++ b/linera-execution/src/lib.rs @@ -56,18 +56,24 @@ use serde::{Deserialize, Serialize}; use std::{fmt, io, path::Path, str::FromStr, sync::Arc}; use thiserror::Error; -/// An implementation of [`UserContractModule`] +/// An implementation of [`UserContractModule`]. pub type UserContractCode = Arc; /// An implementation of [`UserServiceModule`]. pub type UserServiceCode = Arc; +/// An implementation of [`UserContract`]. +pub type UserContractInstance = Box; + +/// An implementation of [`UserService`]. +pub type UserServiceInstance = Box; + /// A factory trait to obtain a [`UserContract`] from a [`UserContractModule`] pub trait UserContractModule { fn instantiate( &self, runtime: ContractSyncRuntime, - ) -> Result, ExecutionError>; + ) -> Result; } /// A factory trait to obtain a [`UserService`] from a [`UserServiceModule`] @@ -75,7 +81,7 @@ pub trait UserServiceModule { fn instantiate( &self, runtime: ServiceSyncRuntime, - ) -> Result, ExecutionError>; + ) -> Result; } /// A type for errors happening during execution. diff --git a/linera-execution/src/runtime.rs b/linera-execution/src/runtime.rs index de5497584bf1..11e49c2a8500 100644 --- a/linera-execution/src/runtime.rs +++ b/linera-execution/src/runtime.rs @@ -6,8 +6,9 @@ use crate::{ execution_state_actor::{ExecutionStateSender, Request}, resources::{RuntimeCounts, RuntimeLimits}, util::{ReceiverExt, UnboundedSenderExt}, - BaseRuntime, CallOutcome, ContractRuntime, ExecutionError, ExecutionOutcome, ServiceRuntime, - SessionId, UserApplicationDescription, UserApplicationId, UserContractCode, UserServiceCode, + ApplicationCallOutcome, BaseRuntime, CallOutcome, CalleeContext, ContractRuntime, + ExecutionError, ExecutionOutcome, ServiceRuntime, SessionId, UserApplicationDescription, + UserApplicationId, UserContractCode, UserContractInstance, UserServiceInstance, }; use custom_debug_derive::Debug; use linera_base::{ @@ -18,25 +19,27 @@ use linera_base::{ use linera_views::batch::Batch; use oneshot::Receiver; use std::{ - collections::{BTreeMap, HashSet}, + collections::{hash_map, BTreeMap, HashMap, HashSet}, sync::{Arc, Mutex}, }; -#[derive(Clone, Debug)] -pub struct SyncRuntime(Arc>>); +#[derive(Debug)] +pub struct SyncRuntime(Arc>>); -pub type ContractSyncRuntime = SyncRuntime; -pub type ServiceSyncRuntime = SyncRuntime; +pub type ContractSyncRuntime = SyncRuntime; +pub type ServiceSyncRuntime = SyncRuntime; /// Runtime data tracked during the execution of a transaction on the synchronous thread. #[derive(Debug)] -pub struct SyncRuntimeInternal { +pub struct SyncRuntimeInternal { /// The current chain ID. chain_id: ChainId, /// How to interact with the storage view of the execution state. execution_state_sender: ExecutionStateSender, + /// Application instances loaded in this transaction. + loaded_applications: HashMap>, /// The current stack of application descriptions. call_stack: Vec, /// The set of the IDs of the applications that are in the `call_stack`. @@ -68,6 +71,34 @@ struct ApplicationStatus { signer: Option, } +/// A loaded application instance. +#[derive(Debug)] +struct LoadedApplication { + instance: Arc>, + parameters: Vec, +} + +impl LoadedApplication { + /// Creates a new [`LoadedApplication`] entry from the `instance` and its `description`. + fn new(instance: Instance, description: UserApplicationDescription) -> Self { + LoadedApplication { + instance: Arc::new(Mutex::new(instance)), + parameters: description.parameters, + } + } +} + +impl Clone for LoadedApplication { + // Manual implementation is needed to prevent the derive macro from adding an `Instance: Clone` + // bound + fn clone(&self) -> Self { + LoadedApplication { + instance: self.instance.clone(), + parameters: self.parameters.clone(), + } + } +} + #[derive(Debug, Default)] struct SessionManager { /// Track the next session index to be used for each application. @@ -193,7 +224,7 @@ struct SessionState { data: Vec, } -impl SyncRuntimeInternal { +impl SyncRuntimeInternal { fn new( chain_id: ChainId, execution_state_sender: ExecutionStateSender, @@ -207,6 +238,7 @@ impl SyncRuntimeInternal { Self { chain_id, execution_state_sender, + loaded_applications: HashMap::new(), call_stack: Vec::new(), active_applications: HashSet::new(), execution_outcomes: Vec::default(), @@ -218,24 +250,6 @@ impl SyncRuntimeInternal { } } - fn load_contract( - &mut self, - id: UserApplicationId, - ) -> Result<(UserContractCode, UserApplicationDescription), ExecutionError> { - self.execution_state_sender - .send_request(|callback| Request::LoadContract { id, callback })? - .recv_response() - } - - fn load_service( - &mut self, - id: UserApplicationId, - ) -> Result<(UserServiceCode, UserApplicationDescription), ExecutionError> { - self.execution_state_sender - .send_request(|callback| Request::LoadService { id, callback })? - .recv_response() - } - /// Returns the [`ApplicationStatus`] of the current application. /// /// The current application is the last to be pushed to the `call_stack`. @@ -264,12 +278,125 @@ impl SyncRuntimeInternal { /// # Panics /// /// If the call stack is empty. - fn pop_application(&mut self) { + fn pop_application(&mut self) -> ApplicationStatus { let status = self .call_stack .pop() .expect("Can't remove application from empty call stack"); assert!(self.active_applications.remove(&status.id)); + status + } + + /// Ensures that a call to `application_id` is not-reentrant. + /// + /// Returns an error if there already is an entry for `application_id` in the call stack. + fn check_for_reentrancy( + &mut self, + application_id: UserApplicationId, + ) -> Result<(), ExecutionError> { + ensure!( + !self.active_applications.contains(&application_id), + ExecutionError::ReentrantCall(application_id) + ); + Ok(()) + } +} + +impl SyncRuntimeInternal { + fn load_contract( + &mut self, + id: UserApplicationId, + ) -> Result<(UserContractCode, UserApplicationDescription), ExecutionError> { + self.execution_state_sender + .send_request(|callback| Request::LoadContract { id, callback })? + .recv_response() + } + + /// Loads a contract instance, initializing it with this runtime if needed. + fn load_contract_instance( + &mut self, + this: Arc>, + id: UserApplicationId, + ) -> Result, ExecutionError> { + match self.loaded_applications.entry(id) { + hash_map::Entry::Vacant(entry) => { + let (code, description) = self + .execution_state_sender + .send_request(|callback| Request::LoadContract { id, callback })? + .recv_response()?; + + let instance = code.instantiate(SyncRuntime(this))?; + Ok(entry + .insert(LoadedApplication::new(instance, description)) + .clone()) + } + hash_map::Entry::Occupied(entry) => Ok(entry.get().clone()), + } + } + + /// Configures the runtime for executing a call to a different contract. + fn prepare_for_call( + &mut self, + this: Arc>, + authenticated: bool, + callee_id: UserApplicationId, + forwarded_sessions: &[SessionId], + ) -> Result<(Arc>, CalleeContext), ExecutionError> { + self.check_for_reentrancy(callee_id)?; + + // Load the application. + let application = self.load_contract_instance(this, callee_id)?; + + let caller = self.current_application(); + let caller_id = caller.id; + let caller_signer = caller.signer; + // Change the owners of forwarded sessions. + self.forward_sessions(forwarded_sessions, caller_id, callee_id)?; + // Make the call to user code. + let authenticated_signer = match caller_signer { + Some(signer) if authenticated => Some(signer), + _ => None, + }; + let authenticated_caller_id = authenticated.then_some(caller_id); + let callee_context = CalleeContext { + chain_id: self.chain_id, + authenticated_signer, + authenticated_caller_id, + }; + self.push_application(ApplicationStatus { + id: callee_id, + parameters: application.parameters, + // Allow further nested calls to be authenticated if this one is. + signer: authenticated_signer, + }); + Ok((application.instance, callee_context)) + } + + /// Cleans up the runtime after the execution of a call to a different contract. + fn finish_call( + &mut self, + raw_outcome: ApplicationCallOutcome, + ) -> Result { + let ApplicationStatus { + id: callee_id, + signer, + .. + } = self.pop_application(); + + // Interpret the results of the call. + self.execution_outcomes.push(ExecutionOutcome::User( + callee_id, + raw_outcome + .execution_outcome + .with_authenticated_signer(signer), + )); + let caller_id = self.application_id()?; + let sessions = self.make_sessions(raw_outcome.create_sessions, callee_id, caller_id); + let outcome = CallOutcome { + value: raw_outcome.value, + sessions, + }; + Ok(outcome) } fn forward_sessions( @@ -399,138 +526,149 @@ impl SyncRuntimeInternal { } } -impl SyncRuntime { - fn new(runtime: SyncRuntimeInternal) -> Self { +impl SyncRuntimeInternal { + /// Initializes a service instance with this runtime. + fn load_service_instance( + &mut self, + this: Arc>, + id: UserApplicationId, + ) -> Result, ExecutionError> { + match self.loaded_applications.entry(id) { + hash_map::Entry::Vacant(entry) => { + let (code, description) = self + .execution_state_sender + .send_request(|callback| Request::LoadService { id, callback })? + .recv_response()?; + + let instance = code.instantiate(SyncRuntime(this))?; + Ok(entry + .insert(LoadedApplication::new(instance, description)) + .clone()) + } + hash_map::Entry::Occupied(entry) => Ok(entry.get().clone()), + } + } +} + +impl SyncRuntime { + fn new(runtime: SyncRuntimeInternal) -> Self { SyncRuntime(Arc::new(Mutex::new(runtime))) } - fn into_inner(self) -> Option> { + fn into_inner(self) -> Option> { let runtime = Arc::into_inner(self.0)? .into_inner() .expect("thread should not have panicked"); Some(runtime) } - fn as_inner(&mut self) -> std::sync::MutexGuard<'_, SyncRuntimeInternal> { + fn inner(&mut self) -> std::sync::MutexGuard<'_, SyncRuntimeInternal> { self.0 .try_lock() .expect("Synchronous runtimes run on a single execution thread") } - - /// Ensures that a call to `application_id` is not-reentrant. - /// - /// Returns an error if there already is an entry for `application_id` in the call stack. - fn check_for_reentrancy( - &mut self, - application_id: UserApplicationId, - ) -> Result<(), ExecutionError> { - let this = self.as_inner(); - ensure!( - !this.active_applications.contains(&application_id), - ExecutionError::ReentrantCall(application_id) - ); - Ok(()) - } } -impl BaseRuntime for SyncRuntime { - type Read = as BaseRuntime>::Read; - type ReadValueBytes = as BaseRuntime>::ReadValueBytes; - type ContainsKey = as BaseRuntime>::ContainsKey; - type ReadMultiValuesBytes = as BaseRuntime>::ReadMultiValuesBytes; - type FindKeysByPrefix = as BaseRuntime>::FindKeysByPrefix; - type FindKeyValuesByPrefix = as BaseRuntime>::FindKeyValuesByPrefix; +impl BaseRuntime for SyncRuntime { + type Read = as BaseRuntime>::Read; + type ReadValueBytes = as BaseRuntime>::ReadValueBytes; + type ContainsKey = as BaseRuntime>::ContainsKey; + type ReadMultiValuesBytes = + as BaseRuntime>::ReadMultiValuesBytes; + type FindKeysByPrefix = as BaseRuntime>::FindKeysByPrefix; + type FindKeyValuesByPrefix = + as BaseRuntime>::FindKeyValuesByPrefix; fn chain_id(&mut self) -> Result { - self.as_inner().chain_id() + self.inner().chain_id() } fn application_id(&mut self) -> Result { - self.as_inner().application_id() + self.inner().application_id() } fn application_parameters(&mut self) -> Result, ExecutionError> { - self.as_inner().application_parameters() + self.inner().application_parameters() } fn read_system_balance(&mut self) -> Result { - self.as_inner().read_system_balance() + self.inner().read_system_balance() } fn read_system_timestamp(&mut self) -> Result { - self.as_inner().read_system_timestamp() + self.inner().read_system_timestamp() } fn write_batch(&mut self, batch: Batch) -> Result<(), ExecutionError> { - self.as_inner().write_batch(batch) + self.inner().write_batch(batch) } fn contains_key_new(&mut self, key: Vec) -> Result { - self.as_inner().contains_key_new(key) + self.inner().contains_key_new(key) } fn contains_key_wait(&mut self, promise: &Self::ContainsKey) -> Result { - self.as_inner().contains_key_wait(promise) + self.inner().contains_key_wait(promise) } fn read_multi_values_bytes_new( &mut self, keys: Vec>, ) -> Result { - self.as_inner().read_multi_values_bytes_new(keys) + self.inner().read_multi_values_bytes_new(keys) } fn read_multi_values_bytes_wait( &mut self, promise: &Self::ReadMultiValuesBytes, ) -> Result>>, ExecutionError> { - self.as_inner().read_multi_values_bytes_wait(promise) + self.inner().read_multi_values_bytes_wait(promise) } fn read_value_bytes_new( &mut self, key: Vec, ) -> Result { - self.as_inner().read_value_bytes_new(key) + self.inner().read_value_bytes_new(key) } fn read_value_bytes_wait( &mut self, promise: &Self::ReadValueBytes, ) -> Result>, ExecutionError> { - self.as_inner().read_value_bytes_wait(promise) + self.inner().read_value_bytes_wait(promise) } fn find_keys_by_prefix_new( &mut self, key_prefix: Vec, ) -> Result { - self.as_inner().find_keys_by_prefix_new(key_prefix) + self.inner().find_keys_by_prefix_new(key_prefix) } fn find_keys_by_prefix_wait( &mut self, promise: &Self::FindKeysByPrefix, ) -> Result>, ExecutionError> { - self.as_inner().find_keys_by_prefix_wait(promise) + self.inner().find_keys_by_prefix_wait(promise) } fn find_key_values_by_prefix_new( &mut self, key_prefix: Vec, ) -> Result { - self.as_inner().find_key_values_by_prefix_new(key_prefix) + self.inner().find_key_values_by_prefix_new(key_prefix) } fn find_key_values_by_prefix_wait( &mut self, promise: &Self::FindKeyValuesByPrefix, ) -> Result, Vec)>, ExecutionError> { - self.as_inner().find_key_values_by_prefix_wait(promise) + self.inner().find_key_values_by_prefix_wait(promise) } } -impl BaseRuntime for SyncRuntimeInternal { +impl BaseRuntime for SyncRuntimeInternal { type Read = (); type ReadValueBytes = u32; type ContainsKey = u32; @@ -721,6 +859,12 @@ impl BaseRuntime for SyncRuntimeInternal { } } +impl Clone for SyncRuntime { + fn clone(&self) -> Self { + SyncRuntime(self.0.clone()) + } +} + impl ContractSyncRuntime { /// Main entry point to start executing a user action. pub(crate) fn run_action( @@ -744,20 +888,24 @@ impl ContractSyncRuntime { parameters: description.parameters, signer, }); - let runtime = ContractSyncRuntime::new(runtime); - let execution_outcome = { + let mut runtime = ContractSyncRuntime::new(runtime); + let execution_result = { let mut code = code.instantiate(runtime.clone())?; match action { - UserAction::Initialize(context, argument) => code.initialize(context, argument)?, + UserAction::Initialize(context, argument) => code.initialize(context, argument), UserAction::Operation(context, operation) => { - code.execute_operation(context, operation)? + code.execute_operation(context, operation) } - UserAction::Message(context, message) => code.execute_message(context, message)?, + UserAction::Message(context, message) => code.execute_message(context, message), } }; + // Ensure the `loaded_applications` are cleared to prevent circular references in the + // `runtime` + runtime.inner().loaded_applications.clear(); let mut runtime = runtime .into_inner() .expect("Runtime clones should have been freed by now"); + let execution_outcome = execution_result?; assert_eq!(runtime.call_stack.len(), 1); assert_eq!(runtime.call_stack[0].id, application_id); assert_eq!(runtime.active_applications.len(), 1); @@ -777,13 +925,11 @@ impl ContractSyncRuntime { impl ContractRuntime for ContractSyncRuntime { fn remaining_fuel(&mut self) -> Result { - let this = self.as_inner(); - Ok(this.runtime_counts.remaining_fuel) + Ok(self.inner().runtime_counts.remaining_fuel) } fn set_remaining_fuel(&mut self, remaining_fuel: u64) -> Result<(), ExecutionError> { - let mut this = self.as_inner(); - this.runtime_counts.remaining_fuel = remaining_fuel; + self.inner().runtime_counts.remaining_fuel = remaining_fuel; Ok(()) } @@ -794,57 +940,20 @@ impl ContractRuntime for ContractSyncRuntime { argument: Vec, forwarded_sessions: Vec, ) -> Result { - self.check_for_reentrancy(callee_id)?; - let (callee_context, authenticated_signer, code) = { - let mut this = self.as_inner(); - let caller = this.current_application(); - let caller_id = caller.id; - let caller_signer = caller.signer; - // Load the application. - let (code, description) = this.load_contract(callee_id)?; - // Change the owners of forwarded sessions. - this.forward_sessions(&forwarded_sessions, caller_id, callee_id)?; - // Make the call to user code. - let authenticated_signer = match caller_signer { - Some(signer) if authenticated => Some(signer), - _ => None, - }; - let authenticated_caller_id = authenticated.then_some(caller_id); - let callee_context = crate::CalleeContext { - chain_id: this.chain_id, - authenticated_signer, - authenticated_caller_id, - }; - this.push_application(ApplicationStatus { - id: callee_id, - parameters: description.parameters, - // Allow further nested calls to be authenticated if this one is. - signer: authenticated_signer, - }); - (callee_context, authenticated_signer, code) - }; - let mut code = code.instantiate(self.clone())?; - let raw_outcome = - code.handle_application_call(callee_context, argument, forwarded_sessions)?; - { - let mut this = self.as_inner(); - this.pop_application(); - - // Interpret the results of the call. - this.execution_outcomes.push(ExecutionOutcome::User( - callee_id, - raw_outcome - .execution_outcome - .with_authenticated_signer(authenticated_signer), - )); - let caller_id = this.application_id()?; - let sessions = this.make_sessions(raw_outcome.create_sessions, callee_id, caller_id); - let outcome = CallOutcome { - value: raw_outcome.value, - sessions, - }; - Ok(outcome) - } + let cloned_self = self.clone().0; + let (contract, callee_context) = self.inner().prepare_for_call( + cloned_self, + authenticated, + callee_id, + &forwarded_sessions, + )?; + + let raw_outcome = contract + .try_lock() + .expect("Applications should not have reentrant calls") + .handle_application_call(callee_context, argument, forwarded_sessions)?; + + self.inner().finish_call(raw_outcome) } fn try_call_session( @@ -854,46 +963,33 @@ impl ContractRuntime for ContractSyncRuntime { argument: Vec, forwarded_sessions: Vec, ) -> Result { - self.check_for_reentrancy(session_id.application_id)?; - let (callee_context, authenticated_signer, session_state, code) = { - let mut this = self.as_inner(); - let callee_id = session_id.application_id; - let caller = this.current_application(); - let caller_id = caller.id; - let caller_signer = caller.signer; - // Load the application. - let (code, description) = this.load_contract(callee_id)?; - // Change the owners of forwarded sessions. - this.forward_sessions(&forwarded_sessions, caller_id, callee_id)?; + let callee_id = session_id.application_id; + + let (contract, callee_context, session_state) = { + let cloned_self = self.clone().0; + let mut this = self.inner(); + // Load the session. + let caller_id = this.application_id()?; let session_state = this.try_load_session(session_id, caller_id)?; - // Make the call to user code. - let authenticated_signer = match caller_signer { - Some(signer) if authenticated => Some(signer), - _ => None, - }; - let authenticated_caller_id = authenticated.then_some(caller_id); - let callee_context = crate::CalleeContext { - chain_id: this.chain_id, - authenticated_signer, - authenticated_caller_id, - }; - this.push_application(ApplicationStatus { - id: callee_id, - parameters: description.parameters, - // Allow further nested calls to be authenticated if this one is. - signer: authenticated_signer, - }); - (callee_context, authenticated_signer, session_state, code) - }; - let mut code = code.instantiate(self.clone())?; - let (raw_outcome, session_state) = - code.handle_session_call(callee_context, session_state, argument, forwarded_sessions)?; + + let (contract, callee_context) = + this.prepare_for_call(cloned_self, authenticated, callee_id, &forwarded_sessions)?; + + Ok::<_, ExecutionError>((contract, callee_context, session_state)) + }?; + + let (raw_outcome, session_state) = contract + .try_lock() + .expect("Applications should not have reentrant calls") + .handle_session_call(callee_context, session_state, argument, forwarded_sessions)?; + { - let mut this = self.as_inner(); - this.pop_application(); + let mut this = self.inner(); - // Interpret the results of the call. + let outcome = this.finish_call(raw_outcome.inner)?; + + // Update the session. let caller_id = this.application_id()?; if raw_outcome.close_session { // Terminate the session. @@ -902,19 +998,7 @@ impl ContractRuntime for ContractSyncRuntime { // Save the session. this.try_save_session(session_id, caller_id, session_state)?; } - let inner_outcome = raw_outcome.inner; - let callee_id = session_id.application_id; - this.execution_outcomes.push(ExecutionOutcome::User( - callee_id, - inner_outcome - .execution_outcome - .with_authenticated_signer(authenticated_signer), - )); - let sessions = this.make_sessions(inner_outcome.create_sessions, callee_id, caller_id); - let outcome = CallOutcome { - value: inner_outcome.value, - sessions, - }; + Ok(outcome) } } @@ -927,13 +1011,20 @@ impl ServiceSyncRuntime { context: crate::QueryContext, query: Vec, ) -> Result, ExecutionError> { - let runtime = SyncRuntimeInternal::new( + let runtime_internal = SyncRuntimeInternal::new( context.chain_id, execution_state_sender, RuntimeLimits::default(), 0, ); - ServiceSyncRuntime::new(runtime).try_query_application(application_id, query) + let mut runtime = ServiceSyncRuntime::new(runtime_internal); + + let result = runtime.try_query_application(application_id, query); + + // Ensure the `loaded_applications` are cleared to remove circular references in + // `runtime_internal` + runtime.inner().loaded_applications.clear(); + result } } @@ -944,28 +1035,28 @@ impl ServiceRuntime for ServiceSyncRuntime { queried_id: UserApplicationId, argument: Vec, ) -> Result, ExecutionError> { - let (query_context, code) = { - let mut this = self.as_inner(); + let (query_context, service) = { + let cloned_self = self.clone().0; + let mut this = self.inner(); // Load the application. - let (code, description) = this.load_service(queried_id)?; + let application = this.load_service_instance(cloned_self, queried_id)?; // Make the call to user code. let query_context = crate::QueryContext { chain_id: this.chain_id, }; this.push_application(ApplicationStatus { id: queried_id, - parameters: description.parameters, + parameters: application.parameters, signer: None, }); - (query_context, code) + (query_context, application.instance) }; - let mut code = code.instantiate(self.clone())?; - let response = code.handle_query(query_context, argument)?; - { - let mut this = self.as_inner(); - this.pop_application(); - } + let response = service + .try_lock() + .expect("Applications should not have reentrant calls") + .handle_query(query_context, argument)?; + self.inner().pop_application(); Ok(response) } } diff --git a/linera-execution/src/wasm/mod.rs b/linera-execution/src/wasm/mod.rs index eec49cf10565..9399f62a0402 100644 --- a/linera-execution/src/wasm/mod.rs +++ b/linera-execution/src/wasm/mod.rs @@ -23,8 +23,8 @@ mod wasmtime; use self::sanitizer::sanitize; use crate::{ - Bytecode, ContractSyncRuntime, ExecutionError, ServiceSyncRuntime, UserContract, - UserContractModule, UserService, UserServiceModule, WasmRuntime, + Bytecode, ContractSyncRuntime, ExecutionError, ServiceSyncRuntime, UserContractInstance, + UserContractModule, UserServiceInstance, UserServiceModule, WasmRuntime, }; use std::{path::Path, sync::Arc}; use thiserror::Error; @@ -91,7 +91,7 @@ impl UserContractModule for WasmContractModule { fn instantiate( &self, runtime: ContractSyncRuntime, - ) -> Result, ExecutionError> { + ) -> Result { match self { #[cfg(feature = "wasmtime")] WasmContractModule::Wasmtime { module } => Ok(Box::new( @@ -152,7 +152,7 @@ impl UserServiceModule for WasmServiceModule { fn instantiate( &self, runtime: ServiceSyncRuntime, - ) -> Result, ExecutionError> { + ) -> Result { match self { #[cfg(feature = "wasmtime")] WasmServiceModule::Wasmtime { module } => { diff --git a/linera-execution/tests/test_execution.rs b/linera-execution/tests/test_execution.rs index fd44825ebafb..4d90116d88a9 100644 --- a/linera-execution/tests/test_execution.rs +++ b/linera-execution/tests/test_execution.rs @@ -938,7 +938,7 @@ async fn test_multiple_messages_from_different_applications() -> anyhow::Result< pub async fn register_mock_applications( state: &mut ExecutionStateView, count: u64, -) -> anyhow::Result)>> +) -> anyhow::Result> where C: Context + Clone + Send + Sync + 'static, ViewError: From, diff --git a/linera-execution/tests/utils/mock_application.rs b/linera-execution/tests/utils/mock_application.rs index 159e415fd4b7..c3cf837cfae2 100644 --- a/linera-execution/tests/utils/mock_application.rs +++ b/linera-execution/tests/utils/mock_application.rs @@ -14,38 +14,28 @@ use linera_execution::{ use std::{ collections::VecDeque, fmt::{self, Display, Formatter}, + mem, sync::{Arc, Mutex}, }; /// A mocked implementation of a user application. /// -/// Implements [`UserContractModule`], [`UserServiceModule`], [`UserContract`] and [`UserService`]. -#[derive(Clone)] -pub struct MockApplication { - runtime: Runtime, +/// Should be configured with any expected calls, and can then be used to create a +/// [`MockApplicationInstance`] that implements [`UserContract`] and [`UserService`]. +#[derive(Clone, Default)] +pub struct MockApplication { expected_calls: Arc>>, } -impl Default for MockApplication<()> { - fn default() -> Self { - MockApplication { - runtime: (), - expected_calls: Arc::new(Mutex::new(VecDeque::new())), - } - } -} - -impl MockApplication<()> { - /// Configures the `runtime` to use with the [`MockApplication`] instance. - pub fn with_runtime(self, runtime: NewRuntime) -> MockApplication { - MockApplication { - runtime, - expected_calls: self.expected_calls, - } - } +/// A mocked implementation of a user application instance. +/// +/// Will expect certain calls previously configured through [`MockApplication`]. +pub struct MockApplicationInstance { + expected_calls: VecDeque, + runtime: Runtime, } -impl MockApplication { +impl MockApplication { /// Queues an expected call to the [`MockApplication`]. pub fn expect_call(&self, expected_call: ExpectedCall) { self.expected_calls @@ -53,15 +43,16 @@ impl MockApplication { .expect("Mutex is poisoned") .push_back(expected_call); } -} -impl MockApplication { - /// Retrieves the next [`ExpectedCall`] in the queue. - fn next_expected_call(&mut self) -> Option { - self.expected_calls - .lock() - .expect("Mutex is poisoned") - .pop_front() + /// Creates a new [`MockApplicationInstance`], forwarding the configured expected calls. + pub fn create_mock_instance( + &self, + runtime: Runtime, + ) -> MockApplicationInstance { + MockApplicationInstance { + expected_calls: mem::take(&mut self.expected_calls.lock().expect("Mutex is poisoned")), + runtime, + } } } @@ -119,7 +110,7 @@ type HandleQueryHandler = Box< + Sync, >; -/// An expected call to a [`MockApplication`]. +/// An expected call to a [`MockApplicationInstance`]. pub enum ExpectedCall { /// An expected call to [`UserContract::initialize`]. Initialize(InitializeHandler), @@ -151,8 +142,8 @@ impl Display for ExpectedCall { } impl ExpectedCall { - /// Creates an [`ExpectedCall`] to the [`MockApplication`]'s [`UserContract::initialize`] - /// implementation, which is handled by the provided `handler`. + /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s + /// [`UserContract::initialize`] implementation, which is handled by the provided `handler`. pub fn initialize( handler: impl FnOnce( &mut ContractSyncRuntime, @@ -166,7 +157,7 @@ impl ExpectedCall { ExpectedCall::Initialize(Box::new(handler)) } - /// Creates an [`ExpectedCall`] to the [`MockApplication`]'s + /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s /// [`UserContract::execute_operation`] implementation, which is handled by the provided /// `handler`. pub fn execute_operation( @@ -182,7 +173,7 @@ impl ExpectedCall { ExpectedCall::ExecuteOperation(Box::new(handler)) } - /// Creates an [`ExpectedCall`] to the [`MockApplication`]'s + /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s /// [`UserContract::execute_message`] implementation, which is handled by the provided /// `handler`. pub fn execute_message( @@ -198,7 +189,7 @@ impl ExpectedCall { ExpectedCall::ExecuteMessage(Box::new(handler)) } - /// Creates an [`ExpectedCall`] to the [`MockApplication`]'s + /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s /// [`UserContract::handle_application_call`] implementation, which is handled by the provided /// `handler`. pub fn handle_application_call( @@ -215,7 +206,7 @@ impl ExpectedCall { ExpectedCall::HandleApplicationCall(Box::new(handler)) } - /// Creates an [`ExpectedCall`] to the [`MockApplication`]'s + /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s /// [`UserContract::handle_session_call`] implementation, which is handled by the provided /// `handler`. pub fn handle_session_call( @@ -233,8 +224,8 @@ impl ExpectedCall { ExpectedCall::HandleSessionCall(Box::new(handler)) } - /// Creates an [`ExpectedCall`] to the [`MockApplication`]'s [`UserService::handle_query`] - /// implementation, which is handled by the provided `handler`. + /// Creates an [`ExpectedCall`] to the [`MockApplicationInstance`]'s + /// [`UserService::handle_query`] implementation, which is handled by the provided `handler`. pub fn handle_query( handler: impl FnOnce( &mut ServiceSyncRuntime, @@ -249,25 +240,32 @@ impl ExpectedCall { } } -impl UserContractModule for MockApplication<()> { +impl UserContractModule for MockApplication { fn instantiate( &self, runtime: ContractSyncRuntime, ) -> Result, ExecutionError> { - Ok(Box::new(self.clone().with_runtime(runtime))) + Ok(Box::new(self.create_mock_instance(runtime))) } } -impl UserServiceModule for MockApplication<()> { +impl UserServiceModule for MockApplication { fn instantiate( &self, runtime: ServiceSyncRuntime, ) -> Result, ExecutionError> { - Ok(Box::new(self.clone().with_runtime(runtime))) + Ok(Box::new(self.create_mock_instance(runtime))) + } +} + +impl MockApplicationInstance { + /// Retrieves the next [`ExpectedCall`] in the queue. + fn next_expected_call(&mut self) -> Option { + self.expected_calls.pop_front() } } -impl UserContract for MockApplication { +impl UserContract for MockApplicationInstance { fn initialize( &mut self, context: OperationContext, @@ -352,7 +350,7 @@ impl UserContract for MockApplication { } } -impl UserService for MockApplication { +impl UserService for MockApplicationInstance { fn handle_query( &mut self, context: QueryContext, diff --git a/linera-sdk/src/log.rs b/linera-sdk/src/log.rs index 26cb10a716b1..f5c551e415a7 100644 --- a/linera-sdk/src/log.rs +++ b/linera-sdk/src/log.rs @@ -3,11 +3,16 @@ use crate::{contract, service}; use log::{LevelFilter, Log, Metadata, Record}; -use std::panic::{self, PanicInfo}; +use std::{ + panic::{self, PanicInfo}, + sync::Once, +}; static CONTRACT_LOGGER: ContractLogger = ContractLogger; static SERVICE_LOGGER: ServiceLogger = ServiceLogger; +static INSTALL_LOGGER: Once = Once::new(); + /// A logger that uses the system API for contracts. #[derive(Clone, Copy, Debug)] pub struct ContractLogger; @@ -15,9 +20,11 @@ pub struct ContractLogger; impl ContractLogger { /// Configures [`log`] to use the log system API for contracts. pub fn install() { - log::set_logger(&CONTRACT_LOGGER).expect("Failed to initialize contract logger"); - log::set_max_level(LevelFilter::Trace); - panic::set_hook(Box::new(log_panic)); + INSTALL_LOGGER.call_once(|| { + log::set_logger(&CONTRACT_LOGGER).expect("Failed to initialize contract logger"); + log::set_max_level(LevelFilter::Trace); + panic::set_hook(Box::new(log_panic)); + }); } } @@ -40,9 +47,11 @@ pub struct ServiceLogger; impl ServiceLogger { /// Configures [`log`] to use the log system API for services. pub fn install() { - log::set_logger(&SERVICE_LOGGER).expect("Failed to initialize service logger"); - log::set_max_level(LevelFilter::Trace); - panic::set_hook(Box::new(log_panic)); + INSTALL_LOGGER.call_once(|| { + log::set_logger(&SERVICE_LOGGER).expect("Failed to initialize service logger"); + log::set_max_level(LevelFilter::Trace); + panic::set_hook(Box::new(log_panic)); + }); } }