wasmer_wasix/syscalls/wasix/
context_switch.rs

1use crate::{WasiEnv, WasiError, state::context_switching::ContextSwitchError};
2use futures::FutureExt;
3use tracing::instrument;
4use wasmer::{AsyncFunctionEnvMut, FunctionEnvMut, RuntimeError};
5use wasmer_wasix_types::wasi::Errno;
6
7/// Suspend the active context and resume another
8///
9/// The resumed context continues from where it was last suspended, or from its
10/// entrypoint if it has never been resumed.
11///
12/// Refer to the wasix-libc [`wasix/context.h`] header for authoritative
13/// documentation.
14///
15/// [`wasix/context.h`]: https://github.com/wasix-org/wasix-libc/blob/main/libc-bottom-half/headers/public/wasix/context.h
16#[instrument(level = "trace", skip(ctx))]
17pub async fn context_switch(
18    mut ctx: AsyncFunctionEnvMut<WasiEnv>,
19    target_context_id: u64,
20) -> Result<Errno, RuntimeError> {
21    let mut write_lock = ctx.write().await;
22
23    let mut sync_env = write_lock.as_function_env_mut();
24    match WasiEnv::do_pending_operations(&mut sync_env) {
25        Ok(()) => {}
26        Err(e) => {
27            return Err(RuntimeError::user(e.into()));
28        }
29    }
30
31    let data = write_lock.data_mut();
32
33    // Verify that we are in an async context
34    let environment = match &data.context_switching_environment {
35        Some(c) => c,
36        None => {
37            tracing::warn!(
38                "The WASIX context-switching API is only available after entering the main function"
39            );
40            return Ok(Errno::Notsup);
41        }
42    };
43
44    // Get own context ID
45    let active_context_id = environment.active_context_id();
46
47    // If switching to self, do nothing
48    if active_context_id == target_context_id {
49        tracing::trace!("Switching context {active_context_id} to itself, which is a no-op");
50        return Ok(Errno::Success);
51    }
52
53    // Try to unblock the target and get future to wait until we are unblocked again
54    //
55    // We must be careful not to return after this point without awaiting the resulting future
56    let wait_for_unblock = match environment.switch_context(target_context_id) {
57        Ok(wait_for_unblock) => wait_for_unblock,
58        Err(ContextSwitchError::SwitchTargetMissing) => {
59            tracing::trace!(
60                "Context {active_context_id} tried to switch to context {target_context_id} but it does not exist or is not suspended"
61            );
62            return Ok(Errno::Inval);
63        }
64    };
65
66    // Drop the write lock before we suspend ourself, as that would cause a deadlock
67    drop(write_lock);
68    tracing::trace!("Suspending context {active_context_id} to switch to {target_context_id}");
69
70    // Wait until we are unblocked again
71    let result = wait_for_unblock.map(|v| v.map(|_| Errno::Success)).await;
72    tracing::trace!("Resumed context {active_context_id} after being switched back to");
73    if let Err(e) = &result {
74        tracing::trace!("But it has an error {e:?}");
75    }
76    result
77}
78
79/// This stub is used for context_switch, when the engine does not support async
80///
81/// It prints a warning and indicates that no context-switching environment is available.
82#[instrument(level = "trace", skip(ctx))]
83pub fn context_switch_not_supported(
84    mut ctx: FunctionEnvMut<'_, WasiEnv>,
85    _target_context_id: u64,
86) -> Result<Errno, WasiError> {
87    WasiEnv::do_pending_operations(&mut ctx)?;
88
89    tracing::warn!(
90        "The WASIX context-switching API is only available in engines supporting async execution"
91    );
92    // Indicate that no context-switching environment is available
93    Ok(Errno::Notsup)
94}