Packages versions
miden-vm: 0.21.0 (upcoming)
Bug description
Processor API: Should it be async or sync?
We're hitting test failures in miden-base on miden-vm next (v0.21.0, unreleased) when using the P3 VM processor from async test functions. This raises a question about what the processor API should look like.
The test failure happens at processor/src/fast/mod.rs:923:
pub fn execute_for_trace_sync(...) -> Result<...> {
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
rt.block_on(self.execute_for_trace(program, host, fragment_size))
}
When called from an async test function, it panics:
thread 'test_ecdsa_acl' panicked at processor/src/fast/mod.rs:925:12:
Cannot start a runtime from within a runtime.
Here's the call chain in miden-base:
#[tokio::test]
async fn test_ecdsa_acl() -> Result<()> {
// ... setup ...
prove_and_verify_transaction(tx)?; // calls prover.prove()
// which calls processor.execute_for_trace_sync()
// which tries to create a new Tokio runtime
// panic!
}
The processor has both async methods like execute_for_trace() and sync wrappers like execute_for_trace_sync(). The sync wrappers create their own Tokio runtime and call block_on(). This works from sync code but breaks from async contexts.
A few options:
Async only
Remove the *_sync() wrappers. Users who need sync can create their own runtime:
let rt = tokio::runtime::Runtime::new()?;
rt.block_on(processor.execute_for_trace(...))?;
Callers control async execution.
Detect existing runtime
Keep sync wrappers but check if we're already in a runtime:
match tokio::runtime::Handle::try_current() {
Ok(_handle) => {
// We're in a runtime - use block_in_place to avoid blocking the worker thread
tokio::task::block_in_place(|| {
// Create a new runtime in this blocking context
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
rt.block_on(self.execute(program, host))
})
},
Err(_) => {
// No runtime exists - create one and use it
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
rt.block_on(self.execute(program, host))
},
}
Works in both contexts. Calling block_on() from within an existing runtime isn't ideal but technically works.
Spawn blocking
Use spawn_blocking() to move work to another thread when already in a runtime. More complex. Thread overhead.
What makes the most sense? The Winterfell VM was fully synchronous, so sync wrappers were probably added to match that API. Now that P3 uses async internally, maybe it's worth rethinking this.
The same pattern appears in execute_sync() and execute_sync_mut() in the same file.
Packages versions
miden-vm: 0.21.0 (upcoming)
Bug description
Processor API: Should it be async or sync?
We're hitting test failures in miden-base on miden-vm next (v0.21.0, unreleased) when using the P3 VM processor from async test functions. This raises a question about what the processor API should look like.
The test failure happens at
processor/src/fast/mod.rs:923:When called from an async test function, it panics:
Here's the call chain in miden-base:
The processor has both async methods like
execute_for_trace()and sync wrappers likeexecute_for_trace_sync(). The sync wrappers create their own Tokio runtime and callblock_on(). This works from sync code but breaks from async contexts.A few options:
Async only
Remove the
*_sync()wrappers. Users who need sync can create their own runtime:Callers control async execution.
Detect existing runtime
Keep sync wrappers but check if we're already in a runtime:
Works in both contexts. Calling
block_on()from within an existing runtime isn't ideal but technically works.Spawn blocking
Use
spawn_blocking()to move work to another thread when already in a runtime. More complex. Thread overhead.What makes the most sense? The Winterfell VM was fully synchronous, so sync wrappers were probably added to match that API. Now that P3 uses async internally, maybe it's worth rethinking this.
The same pattern appears in
execute_sync()andexecute_sync_mut()in the same file.