Skip to content

Commit

Permalink
Reuse contract and service instances inside transaction (#1506)
Browse files Browse the repository at this point in the history
* Add missing sentence period to documentation

Ensure consistency.

* Create `User{Contract,Service}Instance` aliases

Make it easier to use the dynamically dispatched types.

* Rename `as_inner` into `inner`

Try to be closer to the Rust API naming conventions.

* Simplify code when accessing inner only once

No need to create a separate binding for `this` if it's only going to be
used once.

* Refactor to split `SyncRuntimeInternal` methods

Create separate blocks for the contract-specific methods and the
service-specific methods.

* Refactor to add a `load_contract_instance` method

Prepare to use it to reload active contract instances without having to
create a new instance.

Add a `Weak` reference in `SyncRuntimeInternal` to reference the handle
used to reference itself.

* Refactor runtime set-up for cross-app. calls

Place common code in new methods so that there's only one place to
update.

* Replace `WRITABLE: bool` with `ContractOrService`

Use a generic type parameter instead of a const parameter in order to
have a single map of loaded application instances (either contracts or
services).

* Wrap contract instances in an `Arc<Mutex<_>>`

Prepare to share the instances in the runtime.

* Create a `LoadedApplication` helper type

Store the data necessary for running an application instance.

* Return `LoadedApplication` directly

Prepare to return the value stored inside a map.

* Reuse contract instances inside a transaction

Store the loaded instances in a map, and reuse them if there are more
calls to them.

* Refactor to create `load_service_instance` method

Like the `load_contract_instance` method, return an `Arc<Mutex<_>>` with
the instance and the parameters directly, in preparation to share the
instance.

* Reuse service instances inside a query

Store the loaded instances in a map, and reuse them if there are more
calls to them.

* Fix deadlock caused by circular references

The `ExecutionStateView` starts a thread to execute the application with
the synchronous runtime, and in its original thread it loops listening
for requests from the application. When the execution finishes, the
thread ends and when the runtime is dropped it also drops the sender of
requests to the `ExecutionStateView`. Therefore the `ExecutionStateView`
then detects that the channel has closed and finishes the loop
continuing execution.

However, with the addition of the `loaded_applications` map, each loaded
application has a strong reference to the runtime, so there's a circular
dependency which prevents the `SyncRuntimeInternal` type from dropping.
This prevents the channel from closing, and causes a deadlock.

To avoid this situation, the `loaded_applications` map must be cleared
at the end of the execution, independently if it succeeded or if it
failed.

* Ensure that the logger is only installed once

Prepare to allow the same Wasm guest instance to be reused for different
calls inside the same transaction.

* Refactor to split the `MockApplication` type

Create a separate `MockApplicationInstance` that does not share the
expected calls list. This means that now the application forwards the
list to the created instance, and each instance has a separate call
list.

This tests if the instances are reused or not. If they are not reused,
session calls will always fail.

* replace ContractOrService by UserInstance

* remove weak reference

---------

Co-authored-by: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com>
  • Loading branch information
jvff and ma2bd authored Jan 18, 2024
1 parent 34f4b74 commit 46d1f5c
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 253 deletions.
12 changes: 9 additions & 3 deletions linera-execution/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,32 @@ 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<dyn UserContractModule + Send + Sync + 'static>;

/// An implementation of [`UserServiceModule`].
pub type UserServiceCode = Arc<dyn UserServiceModule + Send + Sync + 'static>;

/// An implementation of [`UserContract`].
pub type UserContractInstance = Box<dyn UserContract + Send + Sync + 'static>;

/// An implementation of [`UserService`].
pub type UserServiceInstance = Box<dyn UserService + Send + Sync + 'static>;

/// A factory trait to obtain a [`UserContract`] from a [`UserContractModule`]
pub trait UserContractModule {
fn instantiate(
&self,
runtime: ContractSyncRuntime,
) -> Result<Box<dyn UserContract + Send + Sync + 'static>, ExecutionError>;
) -> Result<UserContractInstance, ExecutionError>;
}

/// A factory trait to obtain a [`UserService`] from a [`UserServiceModule`]
pub trait UserServiceModule {
fn instantiate(
&self,
runtime: ServiceSyncRuntime,
) -> Result<Box<dyn UserService + Send + Sync + 'static>, ExecutionError>;
) -> Result<UserServiceInstance, ExecutionError>;
}

/// A type for errors happening during execution.
Expand Down
Loading

0 comments on commit 46d1f5c

Please sign in to comment.