Skip to content

Commit 6128879

Browse files
committed
Handle context entrypoint functions returning
1 parent 249cf65 commit 6128879

File tree

2 files changed

+42
-30
lines changed

2 files changed

+42
-30
lines changed

lib/wasix/src/state/context_switching.rs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
WasiFunctionEnv,
2+
WasiError, WasiFunctionEnv,
33
utils::thread_local_executor::{
44
ThreadLocalExecutor, ThreadLocalSpawner, ThreadLocalSpawnerError,
55
},
@@ -19,6 +19,7 @@ use std::{
1919
use thiserror::Error;
2020
use tracing::trace;
2121
use wasmer::{RuntimeError, Store};
22+
use wasmer_wasix_types::wasi::ExitCode;
2223

2324
/// The context-switching environment represents all state for WASIX context-switching
2425
/// on a single host thread.
@@ -77,6 +78,16 @@ impl Drop for ContextCanceled {
7778
}
7879
}
7980

81+
/// Contexts will trap with this error as a RuntimeError::user when they entrypoint returns
82+
///
83+
/// It is not allowed for context entrypoints to return normally, they must always
84+
/// either get destroyed while suspended or trap with an error (like ContextCanceled)
85+
///
86+
/// This error will be picked up by the main context and cause it to trap as well.
87+
#[derive(Error, Debug)]
88+
#[error("The entrypoint of context {0} returned which is not allowed")]
89+
pub struct ContextEntrypointReturned(u64);
90+
8091
impl ContextSwitchingEnvironment {
8192
fn new(spawner: ThreadLocalSpawner) -> Self {
8293
Self {
@@ -115,6 +126,22 @@ impl ContextSwitchingEnvironment {
115126
let store_async = store.into_async();
116127
// Run function with the spawner
117128
let result = local_executor.run_until(entrypoint.call_async(&store_async, params));
129+
130+
// Process if this was terminated by a context entrypoint returning
131+
let result = match &result {
132+
Err(e) => match e.downcast_ref::<ContextEntrypointReturned>() {
133+
Some(ContextEntrypointReturned(id)) => {
134+
// Context entrypoint returned, which is not allowed
135+
// Exit with code 129
136+
tracing::error!("The entrypoint of context {id} returned which is not allowed");
137+
Err(RuntimeError::user(
138+
WasiError::Exit(ExitCode::from(129)).into(),
139+
))
140+
}
141+
_ => result,
142+
},
143+
_ => result,
144+
};
118145
// Drop the executor to ensure all spawned tasks are dropped, so we have no references to the StoreAsync left
119146
drop(local_executor);
120147

@@ -251,10 +278,9 @@ impl ContextSwitchingEnvironment {
251278
/// Otherwise, the error will be propagated to the main context.
252279
///
253280
/// If the context is cancelled before it is unblocked, the entrypoint will not be called
254-
pub(crate) fn create_context<T, F>(&self, entrypoint: T) -> u64
281+
pub(crate) fn create_context<F>(&self, entrypoint: F) -> u64
255282
where
256-
T: FnOnce(u64) -> F + 'static,
257-
F: Future<Output = RuntimeError> + 'static,
283+
F: Future<Output = Result<(), RuntimeError>> + 'static,
258284
{
259285
// Create a new context ID
260286
let new_context_id = self
@@ -320,12 +346,19 @@ impl ContextSwitchingEnvironment {
320346
drop(inner);
321347

322348
// Launch the context entrypoint
323-
let launch_result = entrypoint(new_context_id).await;
349+
let entrypoint_result = entrypoint.await;
350+
351+
// If that function returns, we need to resume the main context with an error
352+
// Take the underlying error, or create a new error if the context returned a value
353+
let entrypoint_result = entrypoint_result.map_or_else(
354+
|e| e,
355+
|_| RuntimeError::user(ContextEntrypointReturned(new_context_id).into()),
356+
);
324357

325358
// If that function returns something went wrong.
326359
// If it's a cancellation, we can just let this context run out.
327360
// If it's another error, we resume the main context with the error
328-
let error = match launch_result.downcast::<ContextCanceled>() {
361+
let error = match entrypoint_result.downcast::<ContextCanceled>() {
329362
Ok(canceled) => {
330363
tracing::trace!("Context {new_context_id} was successfully destroyed");
331364
// We know what we are doing, so we can prevent the panic on drop

lib/wasix/src/syscalls/wasix/context_create.rs

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -79,34 +79,13 @@ pub fn context_create<M: MemorySize>(
7979
// Lookup and check the entrypoint function
8080
let entrypoint = match lookup_typechecked_entrypoint(data, &mut store, entrypoint) {
8181
Ok(func) => func,
82-
Err(e) => {
83-
return Ok(e);
82+
Err(err) => {
83+
return Ok(err);
8484
}
8585
};
8686

8787
// Create the new context
88-
let new_context_id = environment.create_context(|new_context_id| {
89-
// Sync part (not needed for now, but will make it easier to work with more complex entrypoints later)
90-
async move {
91-
// Call the entrypoint function
92-
let result: Result<(), RuntimeError> = entrypoint.call_async(&async_store).await;
93-
94-
// If that function returns, we need to resume the main context with an error
95-
// Take the underlying error, or create a new error if the context returned a value
96-
result.map_or_else(
97-
|e| e,
98-
|v| {
99-
// TODO: Proper error type
100-
RuntimeError::user(
101-
format!(
102-
"Context {new_context_id} returned a value ({v:?}). This is not allowed for now"
103-
)
104-
.into(),
105-
)
106-
},
107-
)
108-
}
109-
});
88+
let new_context_id = environment.create_context(entrypoint.call_async(&async_store));
11089

11190
// Write the new context ID into memory
11291
let memory = unsafe { data.memory_view(&store) };

0 commit comments

Comments
 (0)