-
Notifications
You must be signed in to change notification settings - Fork 122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/engine no std #595
Changes from 3 commits
0b834f6
f875ce7
1dad679
9041616
40a2f3f
16dce1f
35043fe
1a7a7f9
b59c4f5
b0b721f
1423e95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,18 @@ | ||
use moka::sync::Cache; | ||
use radix_engine_interface::crypto::hash; | ||
use std::sync::Arc; | ||
use sbor::rust::cell::RefCell; | ||
use sbor::rust::collections::HashMap; | ||
use sbor::rust::sync::Arc; | ||
|
||
use crate::types::*; | ||
use crate::wasm::{WasmMeteringConfig, WasmModule}; | ||
|
||
pub struct WasmInstrumenter { | ||
cache: Cache<(Hash, Hash), Arc<Vec<u8>>>, | ||
cache: RefCell<HashMap<(Hash, Hash), Arc<Vec<u8>>>>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this will panic if multiple threads try to lock it at the same time - as can happen in the Node when we are accepting multiple mempool submissions at once. Can we wrap it in a Mutex instead of a RefCell? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mutex is not part of the |
||
} | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct InstrumenterOptions { | ||
#[allow(dead_code)] | ||
max_cache_size_bytes: u64, | ||
} | ||
|
||
|
@@ -28,17 +30,9 @@ pub struct InstrumentedCode { | |
} | ||
|
||
impl WasmInstrumenter { | ||
pub fn new(options: InstrumenterOptions) -> Self { | ||
let cache = Cache::builder() | ||
.weigher(|_key: &(Hash, Hash), value: &Arc<Vec<u8>>| -> u32 { | ||
value | ||
.len() | ||
.checked_add(Hash::LENGTH * 2) | ||
.and_then(|total| total.try_into().ok()) | ||
.unwrap_or(u32::MAX) | ||
}) | ||
.max_capacity(options.max_cache_size_bytes) | ||
.build(); | ||
pub fn new(_options: InstrumenterOptions) -> Self { | ||
// TODO: limit size | ||
let cache = RefCell::new(HashMap::new()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels like a definite reversion of behaviour. We want a safe and performant cache implementation here, for use in the node. Can we instead just fall back to this basic version in the no-std case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better yet, what about creating our own There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me try conditinal compiling first. |
||
|
||
Self { cache } | ||
} | ||
|
@@ -51,16 +45,20 @@ impl WasmInstrumenter { | |
let code_hash = hash(code); | ||
let cache_key = (code_hash, *wasm_metering_config.identifier()); | ||
|
||
if let Some(cached) = self.cache.get(&cache_key) { | ||
return InstrumentedCode { | ||
code: cached.clone(), | ||
code_hash, | ||
}; | ||
{ | ||
if let Some(cached) = self.cache.borrow().get(&cache_key) { | ||
return InstrumentedCode { | ||
code: cached.clone(), | ||
code_hash, | ||
}; | ||
} | ||
} | ||
|
||
let instrumented_ref = Arc::new(self.instrument_no_cache(code, wasm_metering_config)); | ||
|
||
self.cache.insert(cache_key, instrumented_ref.clone()); | ||
self.cache | ||
.borrow_mut() | ||
.insert(cache_key, instrumented_ref.clone()); | ||
|
||
InstrumentedCode { | ||
code: instrumented_ref, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We create a new Radix engine for each transaction and single thread it - it's the ScryptoInterpreter caches we're sharing.
#FearlessConcurrency should mean this is fine? 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the runtime shares pointer to the cache, they may run into illegal state due to memory volatility. Like I said, we need dedicated story to make sure multi-thread work rather pretend to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean I have already verified this - in the very least:
Separately, the only other interaction between engine threads is in the database layer - where RocksDB is threadsafe. (and we ensure there are no commits during execution of other transactions)
Is there anything I've missed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mainly the
wasmer
implementation which stores a pointer which could be volatible.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that's the
WasmerInstance
which contains the pointer, not theWasmerModule
. TheWasmerInstance
is not shared - it's created for each execution from the (shared)WasmerModule
template.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed - Wasmer is written in Rust, and they have good understanding of Send/Sync (It appears even Instance implements send!), so we can trust that if Module is Sync then it is Sync. Indeed - I believe it's essentially a data object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't know what Rust is doing with the data under that pointer though since Rust doesn't know that it's a mutable object potentially being written to across multiple threads.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you're referring to the pointer that we create ourselves. This pointer isn't in the object which is being shared. This pointer is in the
WasmerInstance
(not shared) not theWasmerModule
(shared).This pointer in
WasmerInstance
has no affect on whetherWasmerModule
isSync
.On a related note, I'm quite surprised that we use
runtime_ptr: Arc<Mutex<usize>>,
and useusize
, not a raw pointer type here, but anyway... I agree WasmerInstance possibly has an unsound impl ofSync
(without looking into it further)... But feeling slightly like a broken record, we're not sharingWasmerInstance
. And I've double-checked - theWasmerInstance
pointer does not point intoWasmerModule
(which would be unsound for sure if it did). :)