Skip to content

Commit

Permalink
[1.37] runtime: temporarily workaround the LimitedMemoryPool limiting…
Browse files Browse the repository at this point in the history
… the concurrency of the contract runtime too much (#10736)

This is a backport of #10733 for
inclusion in 1.37.x series.
  • Loading branch information
nagisa authored Mar 8, 2024
1 parent 3ecedff commit 2567e70
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 28 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ cpu-time = "1.0"
criterion = { version = "0.5.1", default_features = false, features = ["html_reports", "cargo_bench_support"] }
crossbeam = "0.8"
crossbeam-channel = "0.5.8"
crossbeam-queue = "0.3.8"
csv = "1.2.1"
curve25519-dalek = { version = "4.1.1", default-features = false, features = ["alloc", "precomputed-tables", "rand_core"] }
derive-enum-from-into = "0.1.1"
Expand Down
13 changes: 9 additions & 4 deletions runtime/near-vm-runner/src/near_vm_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ impl NearVM {
// that particular function call will be slower. Not to mention there isn't a
// strong guarantee on the upper bound of the memory that the contract runtime may
// require.
LimitedMemoryPool::new(8, 64 * 1024 * 1024).unwrap_or_else(|e| {
LimitedMemoryPool::new(256, 1 * 1024 * 1024).unwrap_or_else(|e| {
panic!("could not pre-allocate resources for the runtime: {e}");
})
})
Expand Down Expand Up @@ -719,7 +719,12 @@ impl crate::runner::VM for NearVM {
}
}

#[test]
fn test_memory_like() {
crate::logic::test_utils::test_memory_like(|| Box::new(NearVmMemory::new(1, 1).unwrap()));
#[cfg(test)]
mod tests {
#[test]
fn test_memory_like() {
crate::logic::test_utils::test_memory_like(|| {
Box::new(super::NearVmMemory::new(1, 1).unwrap())
});
}
}
36 changes: 36 additions & 0 deletions runtime/near-vm-runner/src/tests/regression_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,39 @@ fn gas_intrinsic_did_not_multiply_by_opcode_cost() {
"#]],
]);
}

#[test]
fn parallel_runtime_invocations() {
let mut join_handles = Vec::new();
for _ in 0..128 {
let handle = std::thread::spawn(|| {
for _ in 0..16 {
test_builder()
.only_near_vm()
.wat(
r#"
(module
(type (func))
(func (type 0)
loop
br 0
end
)
(export "foo" (func 0)))
"#,
)
.method("foo")
.expects(&[
expect![[r#"
VMOutcome: balance 4 storage_usage 12 return data None burnt gas 100000000000000 used gas 100000000000000
Err: Exceeded the prepaid gas.
"#]],
]);
}
});
join_handles.push(handle);
}
for handle in join_handles {
handle.join().unwrap();
}
}
1 change: 0 additions & 1 deletion runtime/near-vm/engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ target-lexicon.workspace = true
thiserror.workspace = true
cfg-if.workspace = true
tracing.workspace = true
crossbeam-queue.workspace = true
rustix = { workspace = true, features = ["param", "mm"] }

[badges]
Expand Down
36 changes: 15 additions & 21 deletions runtime/near-vm/engine/src/universal/code_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl<'a> CodeMemoryWriter<'a> {
/// Mappings to regions of memory storing the executable JIT code.
pub struct CodeMemory {
/// Where to return this memory to when dropped.
source_pool: Option<Arc<crossbeam_queue::ArrayQueue<Self>>>,
source_pool: Option<Arc<std::sync::Mutex<Vec<Self>>>>,

/// The mapping
map: *mut u8,
Expand Down Expand Up @@ -138,16 +138,10 @@ impl CodeMemory {
if self.size < size {
// Ideally we would use mremap, but see
// https://bugzilla.kernel.org/show_bug.cgi?id=8691
let source_pool = unsafe {
mm::munmap(self.map.cast(), self.size)?;
let source_pool = self.source_pool.take();
std::mem::forget(self);
source_pool
};
Self::create(size).map(|mut m| {
m.source_pool = source_pool;
m
})
let mut result = Self::create(size)?;
result.source_pool = self.source_pool.take();
drop(self);
Ok(result)
} else {
self.executable_end = 0;
Ok(self)
Expand Down Expand Up @@ -218,12 +212,13 @@ impl Drop for CodeMemory {
);
}
}
drop(source_pool.push(Self {
let mut guard = source_pool.lock().expect("unreachable due to panic=abort");
guard.push(Self {
source_pool: None,
map: self.map,
size: self.size,
executable_end: 0,
}));
});
} else {
unsafe {
if let Err(e) = mm::munmap(self.map.cast(), self.size) {
Expand All @@ -248,25 +243,24 @@ unsafe impl Send for CodeMemory {}
/// However it is possible for the mappings inside to grow to accomodate larger code.
#[derive(Clone)]
pub struct LimitedMemoryPool {
pool: Arc<crossbeam_queue::ArrayQueue<CodeMemory>>,
pool: Arc<std::sync::Mutex<Vec<CodeMemory>>>,
}

impl LimitedMemoryPool {
/// Create a new pool with `count` mappings initialized to `default_memory_size` each.
pub fn new(count: usize, default_memory_size: usize) -> rustix::io::Result<Self> {
let pool = Arc::new(crossbeam_queue::ArrayQueue::new(count));
let this = Self { pool };
let mut pool = Vec::with_capacity(count);
for _ in 0..count {
this.pool
.push(CodeMemory::create(default_memory_size)?)
.unwrap_or_else(|_| panic!("ArrayQueue could not accomodate {count} memories!"));
pool.push(CodeMemory::create(default_memory_size)?);
}
Ok(this)
let pool = Arc::new(std::sync::Mutex::new(pool));
Ok(Self { pool })
}

/// Get a memory mapping, at least `size` bytes large.
pub fn get(&self, size: usize) -> rustix::io::Result<CodeMemory> {
let mut memory = self.pool.pop().ok_or(rustix::io::Errno::NOMEM)?;
let mut guard = self.pool.lock().expect("unreachable due to panic=abort");
let mut memory = guard.pop().ok_or(rustix::io::Errno::NOMEM)?;
memory.source_pool = Some(Arc::clone(&self.pool));
if memory.size < size {
Ok(memory.resize(size)?)
Expand Down

0 comments on commit 2567e70

Please sign in to comment.