wasmer_wasix/syscalls/wasix/
proc_fork.rsuse super::*;
use crate::{
capture_store_snapshot,
os::task::OwnedTaskStatus,
runtime::task_manager::{TaskWasm, TaskWasmRunProperties},
syscalls::*,
WasiThreadHandle,
};
use serde::{Deserialize, Serialize};
use wasmer::Memory;
#[derive(Serialize, Deserialize)]
pub(crate) struct ForkResult {
pub pid: Pid,
pub ret: Errno,
}
#[instrument(level = "trace", skip_all, fields(pid = ctx.data().process.pid().raw()), ret)]
pub fn proc_fork<M: MemorySize>(
mut ctx: FunctionEnvMut<'_, WasiEnv>,
mut copy_memory: Bool,
pid_ptr: WasmPtr<Pid, M>,
) -> Result<Errno, WasiError> {
WasiEnv::do_pending_operations(&mut ctx)?;
wasi_try_ok!(ctx.data().ensure_static_module().map_err(|_| {
warn!("process forking not supported for dynamically linked modules");
Errno::Notsup
}));
if let Some(result) = unsafe { handle_rewind::<M, ForkResult>(&mut ctx) } {
if result.pid == 0 {
trace!("handle_rewind - i am child (ret={})", result.ret);
} else {
trace!(
"handle_rewind - i am parent (child={}, ret={})",
result.pid,
result.ret
);
}
let memory = unsafe { ctx.data().memory_view(&ctx) };
wasi_try_mem_ok!(pid_ptr.write(&memory, result.pid));
return Ok(result.ret);
}
trace!(%copy_memory, "capturing");
let (mut child_env, mut child_handle) = match ctx.data().fork() {
Ok(p) => p,
Err(err) => {
debug!("could not fork process: {err}");
return Ok(Errno::Perm);
}
};
let child_pid = child_env.process.pid();
let child_finished = child_env.process.finished.clone();
{
let mut inner = ctx.data().process.lock();
inner.children.push(child_env.process.clone());
}
let env = ctx.data();
let memory = unsafe { env.memory_view(&ctx) };
wasi_try_mem_ok!(pid_ptr.write(&memory, 0));
let pid = child_env.pid();
let tid = child_env.tid();
let pid_offset = pid_ptr.offset();
if copy_memory == Bool::False {
return unwind::<M, _>(ctx, move |mut ctx, mut memory_stack, rewind_stack| {
let store_data = crate::utils::store::capture_store_snapshot(&mut ctx.as_store_mut())
.serialize()
.unwrap();
let store_data = Bytes::from(store_data);
child_env.swap_inner(ctx.data_mut());
std::mem::swap(ctx.data_mut(), &mut child_env);
ctx.data_mut().vfork.replace(WasiVFork {
rewind_stack: rewind_stack.clone(),
store_data: store_data.clone(),
env: Box::new(child_env),
handle: child_handle,
is_64bit: M::is_64bit(),
});
match rewind::<M, _>(
ctx,
Some(memory_stack.freeze()),
rewind_stack.freeze(),
store_data,
ForkResult {
pid: 0,
ret: Errno::Success,
},
) {
Errno::Success => OnCalledAction::InvokeAgain,
err => {
warn!("failed - could not rewind the stack - errno={}", err);
OnCalledAction::Trap(Box::new(WasiError::Exit(err.into())))
}
}
});
}
let state = env.state.clone();
let bin_factory = env.bin_factory.clone();
let snapshot = capture_store_snapshot(&mut ctx.as_store_mut());
unwind::<M, _>(ctx, move |mut ctx, mut memory_stack, rewind_stack| {
let tasks = ctx.data().tasks().clone();
let span = debug_span!(
"unwind",
memory_stack_len = memory_stack.len(),
rewind_stack_len = rewind_stack.len()
);
let _span_guard = span.enter();
let memory_stack = memory_stack.freeze();
let rewind_stack = rewind_stack.freeze();
let store_data = snapshot.serialize().unwrap();
let store_data = Bytes::from(store_data);
let runtime = child_env.runtime.clone();
let tasks = child_env.tasks().clone();
let child_memory_stack = memory_stack.clone();
let child_rewind_stack = rewind_stack.clone();
let env_inner = ctx.data().inner();
let instance_handles = env_inner.static_module_instance_handles().unwrap();
let module = instance_handles.module_clone();
let memory = instance_handles.memory_clone();
let spawn_type = SpawnType::CopyMemory(memory, ctx.as_store_ref());
let signaler = Box::new(child_env.process.clone());
{
let runtime = runtime.clone();
let tasks = tasks.clone();
let tasks_outer = tasks.clone();
let store_data = store_data.clone();
let run = move |mut props: TaskWasmRunProperties| {
let ctx = props.ctx;
let mut store = props.store;
{
trace!("rewinding child");
let mut ctx = ctx.env.clone().into_mut(&mut store);
let (data, mut store) = ctx.data_and_store_mut();
match rewind::<M, _>(
ctx,
Some(child_memory_stack),
child_rewind_stack,
store_data.clone(),
ForkResult {
pid: 0,
ret: Errno::Success,
},
) {
Errno::Success => OnCalledAction::InvokeAgain,
err => {
warn!(
"wasm rewind failed - could not rewind the stack - errno={}",
err
);
return;
}
};
}
run::<M>(ctx, store, child_handle, None);
};
tasks_outer
.task_wasm(
TaskWasm::new(Box::new(run), child_env, module, false, false)
.with_globals(snapshot)
.with_memory(spawn_type),
)
.map_err(|err| {
warn!(
"failed to fork as the process could not be spawned - {}",
err
);
err
})
.ok();
};
match rewind::<M, _>(
ctx,
Some(memory_stack),
rewind_stack,
store_data,
ForkResult {
pid: child_pid.raw() as Pid,
ret: Errno::Success,
},
) {
Errno::Success => OnCalledAction::InvokeAgain,
err => {
warn!("failed - could not rewind the stack - errno={}", err);
OnCalledAction::Trap(Box::new(WasiError::Exit(err.into())))
}
}
})
}
fn run<M: MemorySize>(
ctx: WasiFunctionEnv,
mut store: Store,
child_handle: WasiThreadHandle,
rewind_state: Option<(RewindState, RewindResultType)>,
) -> ExitCode {
let env = ctx.data(&store);
let tasks = env.tasks().clone();
let pid = env.pid();
let tid = env.tid();
if let Some((rewind_state, rewind_result)) = rewind_state {
let mut ctx = ctx.env.clone().into_mut(&mut store);
let res = rewind_ext::<M>(
&mut ctx,
Some(rewind_state.memory_stack),
rewind_state.rewind_stack,
rewind_state.store_data,
rewind_result,
);
if res != Errno::Success {
return res.into();
}
}
let mut ret: ExitCode = Errno::Success.into();
let err = if ctx.data(&store).thread.is_main() {
trace!(%pid, %tid, "re-invoking main");
let start = ctx
.data(&store)
.inner()
.static_module_instance_handles()
.unwrap()
.start
.clone()
.unwrap();
start.call(&mut store)
} else {
trace!(%pid, %tid, "re-invoking thread_spawn");
let start = ctx
.data(&store)
.inner()
.static_module_instance_handles()
.unwrap()
.thread_spawn
.clone()
.unwrap();
start.call(&mut store, 0, 0)
};
if let Err(err) = err {
match err.downcast::<WasiError>() {
Ok(WasiError::Exit(exit_code)) => {
ret = exit_code;
}
Ok(WasiError::DeepSleep(deep)) => {
trace!(%pid, %tid, "entered a deep sleep");
let respawn = {
let tasks = tasks.clone();
let rewind_state = deep.rewind;
move |ctx, store, rewind_result| {
run::<M>(
ctx,
store,
child_handle,
Some((
rewind_state,
RewindResultType::RewindWithResult(rewind_result),
)),
);
}
};
unsafe {
tasks.resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger)
};
return Errno::Success.into();
}
_ => {}
}
}
trace!(%pid, %tid, "child exited (code = {})", ret);
ctx.on_exit((&mut store), Some(ret));
drop(child_handle);
ret
}