Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bin/node-template/node/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ pub fn new_partial(
config.wasm_method,
config.default_heap_pages,
config.max_runtime_instances,
config.runtime_cache_size,
);

let (client, backend, keystore_container, task_manager) =
Expand Down
1 change: 1 addition & 0 deletions bin/node/cli/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ pub fn new_partial(
config.wasm_method,
config.default_heap_pages,
config.max_runtime_instances,
config.runtime_cache_size,
);

let (client, backend, keystore_container, task_manager) =
Expand Down
1 change: 1 addition & 0 deletions bin/node/inspect/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl InspectCmd {
config.wasm_method,
config.default_heap_pages,
config.max_runtime_instances,
config.runtime_cache_size,
);

let client = new_full_client::<B, RA, _>(&config, None, executor)?;
Expand Down
2 changes: 1 addition & 1 deletion bin/node/testing/src/bench.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ impl BenchDb {
let backend = sc_service::new_db_backend(db_config).expect("Should not fail");
let client = sc_service::new_client(
backend.clone(),
NativeElseWasmExecutor::new(WasmExecutionMethod::Compiled, None, 8),
NativeElseWasmExecutor::new(WasmExecutionMethod::Compiled, None, 8, 2),
&keyring.generate_genesis(),
None,
None,
Expand Down
10 changes: 10 additions & 0 deletions client/cli/src/commands/run_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ pub struct RunCmd {
#[structopt(long)]
pub max_runtime_instances: Option<usize>,

/// Maximum number of different runtimes that can be cached.
///
/// The default value is 8 and the values higher than 256 are ignored.
#[structopt(long)]
pub runtime_cache_size: Option<usize>,
Comment thread
librelois marked this conversation as resolved.
Outdated

/// Run a temporary node.
///
/// A temporary directory will be created to store the configuration and will be deleted
Expand Down Expand Up @@ -450,6 +456,10 @@ impl CliConfiguration for RunCmd {
Ok(self.max_runtime_instances.map(|x| x.min(256)))
}

fn runtime_cache_size(&self) -> Result<Option<usize>> {
Ok(self.runtime_cache_size.map(|x| x.min(256)))
Comment thread
librelois marked this conversation as resolved.
Outdated
}

fn base_path(&self) -> Result<Option<BasePath>> {
Ok(if self.tmp {
Some(BasePath::new_temp_dir()?)
Expand Down
9 changes: 9 additions & 0 deletions client/cli/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,13 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
Ok(Default::default())
}

/// Get maximum different runtimes in cache
///
/// By default this is `None`.
fn runtime_cache_size(&self) -> Result<Option<usize>> {
Ok(Default::default())
}

/// Activate or not the automatic announcing of blocks after import
///
/// By default this is `false`.
Expand Down Expand Up @@ -482,6 +489,7 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
let is_validator = role.is_authority();
let (keystore_remote, keystore) = self.keystore_config(&config_dir)?;
let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?;
let runtime_cache_size = self.runtime_cache_size()?.unwrap_or(2);

let unsafe_pruning = self.import_params().map(|p| p.unsafe_pruning).unwrap_or(false);

Expand Down Expand Up @@ -534,6 +542,7 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
role,
base_path: Some(base_path),
informant_output_format: Default::default(),
runtime_cache_size,
})
}

Expand Down
1 change: 1 addition & 0 deletions client/executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ parking_lot = "0.11.1"
log = "0.4.8"
libsecp256k1 = "0.6"
sp-core-hashing-proc-macro = { version = "4.0.0-dev", path = "../../primitives/core/hashing/proc-macro" }
lru = "0.6.6"

[dev-dependencies]
wat = "1.0"
Expand Down
10 changes: 9 additions & 1 deletion client/executor/src/native_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,17 @@ impl WasmExecutor {
host_functions: Vec<&'static dyn Function>,
max_runtime_instances: usize,
cache_path: Option<PathBuf>,
runtime_cache_size: usize,
) -> Self {
WasmExecutor {
method,
default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES),
host_functions: Arc::new(host_functions),
cache: Arc::new(RuntimeCache::new(max_runtime_instances, cache_path.clone())),
cache: Arc::new(RuntimeCache::new(
max_runtime_instances,
cache_path.clone(),
runtime_cache_size,
)),
cache_path,
}
}
Expand Down Expand Up @@ -330,6 +335,7 @@ impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
fallback_method: WasmExecutionMethod,
default_heap_pages: Option<u64>,
max_runtime_instances: usize,
runtime_cache_size: usize,
) -> Self {
let extended = D::ExtendHostFunctions::host_functions();
let mut host_functions = sp_io::SubstrateHostFunctions::host_functions()
Expand All @@ -351,6 +357,7 @@ impl<D: NativeExecutionDispatch> NativeElseWasmExecutor<D> {
host_functions,
max_runtime_instances,
None,
runtime_cache_size,
);

NativeElseWasmExecutor {
Expand Down Expand Up @@ -635,6 +642,7 @@ mod tests {
WasmExecutionMethod::Interpreted,
None,
8,
2,
);
my_interface::HostFunctions::host_functions().iter().for_each(|function| {
assert_eq!(executor.wasm.host_functions.iter().filter(|f| f == &function).count(), 2);
Expand Down
169 changes: 103 additions & 66 deletions client/executor/src/wasm_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

use crate::error::{Error, WasmError};
use codec::Decode;
use lru::LruCache;
use parking_lot::Mutex;
use sc_executor_common::{
runtime_blob::RuntimeBlob,
Expand Down Expand Up @@ -54,7 +55,27 @@ impl Default for WasmExecutionMethod {
}
}

#[derive(Debug, PartialEq, Eq, Hash, Clone)]
struct VersionedRuntimeId {
/// Runtime code hash.
code_hash: Vec<u8>,
/// Wasm runtime type.
wasm_method: WasmExecutionMethod,
/// The number of WebAssembly heap pages this instance was created with.
heap_pages: u64,
}

struct VersionedRuntimeValue {
/// Shared runtime that can spawn instances.
module: Arc<dyn WasmModule>,
/// Runtime version according to `Core_version` if any.
Comment thread
librelois marked this conversation as resolved.
Outdated
version: Option<RuntimeVersion>,
/// Cached instance pool.
instances: Arc<Vec<Mutex<Option<Box<dyn WasmInstance>>>>>,
}

/// A Wasm runtime object along with its cached runtime version.
#[derive(Clone)]
Comment thread
librelois marked this conversation as resolved.
Outdated
Comment thread
librelois marked this conversation as resolved.
Outdated
struct VersionedRuntime {
/// Runtime code hash.
code_hash: Vec<u8>,
Expand All @@ -67,10 +88,21 @@ struct VersionedRuntime {
/// Runtime version according to `Core_version` if any.
version: Option<RuntimeVersion>,
/// Cached instance pool.
instances: Vec<Mutex<Option<Box<dyn WasmInstance>>>>,
instances: Arc<Vec<Mutex<Option<Box<dyn WasmInstance>>>>>,
}

impl VersionedRuntime {
fn from_cache(id: VersionedRuntimeId, value: &VersionedRuntimeValue) -> Self {
Self {
code_hash: id.code_hash,
wasm_method: id.wasm_method,
heap_pages: id.heap_pages,
module: value.module.clone(),
version: value.version.clone(),
instances: value.instances.clone(),
}
}

/// Run the given closure `f` with an instance of this runtime.
fn with_instance<'c, R, F>(&self, ext: &mut dyn Externalities, f: F) -> Result<R, Error>
where
Expand Down Expand Up @@ -137,8 +169,6 @@ impl VersionedRuntime {
}
}

const MAX_RUNTIMES: usize = 2;

/// Cache for the runtimes.
///
/// When an instance is requested for the first time it is added to this cache. Metadata is kept
Expand All @@ -149,12 +179,12 @@ const MAX_RUNTIMES: usize = 2;
/// the memory reset to the initial memory. So, one runtime instance is reused for every fetch
/// request.
///
/// The size of cache is equal to `MAX_RUNTIMES`.
/// The size of cache is configurable via the cli option `--runtime-cache-size`.
pub struct RuntimeCache {
/// A cache of runtimes along with metadata.
///
/// Runtimes sorted by recent usage. The most recently used is at the front.
Comment thread
librelois marked this conversation as resolved.
runtimes: Mutex<[Option<Arc<VersionedRuntime>>; MAX_RUNTIMES]>,
runtimes: Mutex<LruCache<VersionedRuntimeId, VersionedRuntimeValue>>,
Comment thread
librelois marked this conversation as resolved.
Outdated
/// The size of the instances cache for each runtime.
max_runtime_instances: usize,
cache_path: Option<PathBuf>,
Expand All @@ -168,8 +198,16 @@ impl RuntimeCache {
///
/// `cache_path` allows to specify an optional directory where the executor can store files
/// for caching.
pub fn new(max_runtime_instances: usize, cache_path: Option<PathBuf>) -> RuntimeCache {
RuntimeCache { runtimes: Default::default(), max_runtime_instances, cache_path }
pub fn new(
Comment thread
librelois marked this conversation as resolved.
max_runtime_instances: usize,
cache_path: Option<PathBuf>,
runtime_cache_size: usize,
) -> RuntimeCache {
RuntimeCache {
runtimes: Mutex::new(LruCache::new(runtime_cache_size)),
max_runtime_instances,
cache_path,
}
}

/// Prepares a WASM module instance and executes given function for it.
Expand Down Expand Up @@ -221,68 +259,60 @@ impl RuntimeCache {
let code_hash = &runtime_code.hash;
let heap_pages = runtime_code.heap_pages.unwrap_or(default_heap_pages);

let versioned_runtime_id =
VersionedRuntimeId { code_hash: code_hash.clone(), heap_pages, wasm_method };

let mut runtimes = self.runtimes.lock(); // this must be released prior to calling f
let pos = runtimes.iter().position(|r| {
r.as_ref().map_or(false, |r| {
r.wasm_method == wasm_method &&
r.code_hash == *code_hash &&
r.heap_pages == heap_pages
})
});
let runtime_value = runtimes.get(&versioned_runtime_id);
let runtime = if let Some(runtime_value) = runtime_value {
VersionedRuntime::from_cache(versioned_runtime_id, runtime_value)
} else {
let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?;

let time = std::time::Instant::now();

let result = create_versioned_wasm_runtime(
&code,
code_hash.clone(),
ext,
wasm_method,
heap_pages,
host_functions.into(),
allow_missing_func_imports,
self.max_runtime_instances,
self.cache_path.as_deref(),
);

match result {
Ok(ref result) => {
log::debug!(
target: "wasm-runtime",
"Prepared new runtime version {:?} in {} ms.",
result.version,
time.elapsed().as_millis(),
);
},
Err(ref err) => {
log::warn!(target: "wasm-runtime", "Cannot create a runtime: {:?}", err);
},
}

let runtime = match pos {
Some(n) => runtimes[n]
.clone()
.expect("`position` only returns `Some` for entries that are `Some`"),
None => {
let code = runtime_code.fetch_runtime_code().ok_or(WasmError::CodeNotFound)?;

let time = std::time::Instant::now();

let result = create_versioned_wasm_runtime(
&code,
code_hash.clone(),
ext,
wasm_method,
heap_pages,
host_functions.into(),
allow_missing_func_imports,
self.max_runtime_instances,
self.cache_path.as_deref(),
);

match result {
Ok(ref result) => {
log::debug!(
target: "wasm-runtime",
"Prepared new runtime version {:?} in {} ms.",
result.version,
time.elapsed().as_millis(),
);
},
Err(ref err) => {
log::warn!(target: "wasm-runtime", "Cannot create a runtime: {:?}", err);
},
}
let versioned_runtime = result?;

Arc::new(result?)
},
// Save new versioned wasm runtime in cache
runtimes.put(
versioned_runtime_id,
VersionedRuntimeValue {
module: versioned_runtime.module.clone(),
version: versioned_runtime.version.clone(),
instances: versioned_runtime.instances.clone(),
},
);

versioned_runtime
};

// Rearrange runtimes by last recently used.
match pos {
Some(0) => {},
Some(n) =>
for i in (1..n + 1).rev() {
runtimes.swap(i, i - 1);
},
None => {
runtimes[MAX_RUNTIMES - 1] = Some(runtime.clone());
for i in (1..MAX_RUNTIMES).rev() {
runtimes.swap(i, i - 1);
}
},
}
// Lock must be released prior to calling f
drop(runtimes);

Ok(runtime.with_instance(ext, f))
Expand Down Expand Up @@ -344,7 +374,7 @@ fn decode_version(mut version: &[u8]) -> Result<RuntimeVersion, WasmError> {
})?
.into();

let core_api_id = sp_core_hashing_proc_macro::blake2b_64!(b"Core");
let core_api_id = sp_core::hashing::blake2_64(b"Core");
Comment thread
librelois marked this conversation as resolved.
Outdated
if v.has_api_with(&core_api_id, |v| v >= 3) {
sp_api::RuntimeVersion::decode(&mut version).map_err(|_| {
WasmError::Instantiation("failed to decode \"Core_version\" result".into())
Expand Down Expand Up @@ -450,7 +480,14 @@ fn create_versioned_wasm_runtime(
let mut instances = Vec::with_capacity(max_instances);
instances.resize_with(max_instances, || Mutex::new(None));

Ok(VersionedRuntime { code_hash, module: runtime, version, heap_pages, wasm_method, instances })
Ok(VersionedRuntime {
code_hash,
module: runtime,
version,
heap_pages,
wasm_method,
instances: Arc::new(instances),
})
}

#[cfg(test)]
Expand Down
2 changes: 2 additions & 0 deletions client/service/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ pub struct Configuration {
pub base_path: Option<BasePath>,
/// Configuration of the output format that the informant uses.
pub informant_output_format: sc_informant::OutputFormat,
/// Maximum number of different runtime versions that can be cached.
pub runtime_cache_size: usize,
}

/// Type for tasks spawned by the executor.
Expand Down
Loading