pub use crate::platform::ipc::{FFIRequest, FFIResponse, ZygoteFlag};
use crate::platform::ipc::{RuntimeSide as RuntimeSideTrait, Transport as TransportTrait};
use crate::platform::low;
use parking_lot::{Mutex, MutexGuard};
use std::cell::RefCell;
use std::env;
use std::io;
use std::sync::OnceLock;
use std::thread;
#[cfg(target_os = "linux")]
use crate::platform::ipc::linux as ipc;
#[cfg(target_os = "macos")]
use crate::platform::ipc::macos as ipc;
#[cfg(windows)]
use crate::platform::ipc::windows as ipc;
#[cfg(windows)]
pub use crate::platform::ipc::windows::runAsClone;
pub struct ZygoteHandle
{
pub inner: ipc::ZygoteHandle
}
impl ZygoteHandle
{
pub fn pid(&self) -> u32
{
self.inner.base.process.id()
}
}
pub static ZygoteState: OnceLock<Mutex<ZygoteHandle>> = OnceLock::new();
pub struct ClonedZygote
{
pub pid: u32,
pub(super) data: ipc::RuntimeSide
}
impl ClonedZygote
{
pub fn getMeClone() -> io::Result<Self>
{
let mutex: &Mutex<ZygoteHandle> = ZygoteState.get().ok_or_else(|| {
io::Error::new(io::ErrorKind::NotFound, "Zygote not initialized")
})?;
let guard: MutexGuard<ZygoteHandle> = mutex.lock();
let bootstrap: ipc::Bootstrap =
<ipc::Transport as TransportTrait>::sendSpawnClone(
&guard.inner
)
.map_err(|e| {
io::Error::new(
io::ErrorKind::BrokenPipe,
format!("SpawnClone failed: {e}")
)
})?;
let pid: u32 =
<ipc::Transport as TransportTrait>::bootstrapPid(
&bootstrap
);
if pid == 0
{
drop(guard);
return Err(io::Error::other(
"Main zygote failed to create a clone (pid=0)"
));
}
let data: ipc::RuntimeSide =
<ipc::Transport as TransportTrait>::runtimeConnect(
bootstrap
)?;
Ok(Self { pid, data })
}
pub(super) fn call(&self, request: FFIRequest) -> Result<FFIResponse, String>
{
self.data.send(&request)?;
self.data.recv()
}
}
impl Drop for ClonedZygote
{
fn drop(&mut self) -> ()
{
low::killProcess(self.pid);
}
}
thread_local! {
pub(crate) static ZygoteStack: RefCell<Vec<ClonedZygote>> = const { RefCell::new(Vec::new()) };
}
pub struct ZygoteGuard;
impl ZygoteGuard
{
pub fn enter(zygote: ClonedZygote) -> Self
{
ZygoteStack.with(|stack| {
stack.borrow_mut().push(zygote);
});
Self
}
}
impl Drop for ZygoteGuard
{
fn drop(&mut self) -> ()
{
ZygoteStack.with(|stack| {
stack.borrow_mut().pop();
});
}
}
pub fn runAsZygote() -> !
{
let flag: Option<String> = env::args().nth(2);
<ipc::Transport as TransportTrait>::zygoteControlLoop(flag)
}
pub fn initZygote() -> io::Result<()>
{
let inner: ipc::ZygoteHandle =
<ipc::Transport as TransportTrait>::spawnZygote()?;
let handle: ZygoteHandle = ZygoteHandle { inner };
ZygoteState
.set(Mutex::new(handle))
.map_err(|_| {
io::Error::new(io::ErrorKind::AlreadyExists, "Zygote already initialized")
})?;
thread::spawn(supervisorLoop);
Ok(())
}
fn supervisorLoop() -> ()
{
loop
{
let pidToWait: u32 = {
let mutex: &Mutex<ZygoteHandle> = match ZygoteState.get()
{
Some(m) => m,
None => return
};
mutex.lock().pid()
};
low::waitProcess(pidToWait);
let mutex: &Mutex<ZygoteHandle> = ZygoteState.get().unwrap();
let mut guard: MutexGuard<ZygoteHandle> = mutex.lock();
if guard.pid() == pidToWait {
match initZygoteInner()
{
Ok(newHandle) =>
{
*guard = newHandle;
}
Err(_) =>
{
drop(guard);
thread::sleep(std::time::Duration::from_millis(200));
}
}
}
}
}
fn initZygoteInner() -> io::Result<ZygoteHandle>
{
let inner: ipc::ZygoteHandle =
<ipc::Transport as TransportTrait>::spawnZygote()?;
Ok(ZygoteHandle { inner })
}
#[cfg(test)]
mod tests
{
use crate::ffi;
use crate::ffi::errors::FFIError;
use crate::platform::{platformExt, LibmPath};
#[test]
fn segfaultIsIsolated() -> ()
{
let result: Result<(), FFIError> = ffi!(|scope| {
scope.addSearchPath("examples/isolation");
let lib: Library = scope.load(platformExt!("libcrash"))?;
lib.call("triggerSegfault").void()
});
let err: FFIError =
result.expect_err("a segfaulting clone must not report success");
assert!(
matches!(err, FFIError::ZygoteCommunicationFailed(_)),
"unexpected error: {err:?}"
);
}
#[test]
fn abortIsIsolated() -> ()
{
let result: Result<(), FFIError> = ffi!(|scope| {
scope.addSearchPath("examples/isolation");
let lib: Library = scope.load(platformExt!("libcrash"))?;
lib.call("triggerAbort").void()
});
let err: FFIError =
result.expect_err("an aborting clone must not report success");
assert!(
matches!(err, FFIError::ZygoteCommunicationFailed(_)),
"unexpected error: {err:?}"
);
}
#[test]
fn runtimeSurvivesAfterCrash() -> ()
{
let crashed: Result<(), FFIError> = ffi!(|scope| {
scope.addSearchPath("examples/isolation");
let lib: Library = scope.load(platformExt!("libcrash"))?;
lib.call("triggerAbort").void()
});
assert!(crashed.is_err(), "sanity check: the setup call should have crashed");
let result: f64 = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
libm.call("sqrt").arg::<f64>(16.0).result()
})
.expect("runtime should survive a crashed clone");
assert!((result - 4.0).abs() < f64::EPSILON);
}
#[test]
fn sequentialCloneStress() -> ()
{
const Iterations: usize = 50;
for i in 0..Iterations
{
let result: f64 = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
libm.call("sqrt").arg::<f64>(4.0).result()
})
.unwrap_or_else(|e| {
panic!("sequential clone stress failed on iteration {i}: {e}")
});
assert!(
(result - 2.0).abs() < f64::EPSILON,
"unexpected sqrt result on iteration {i}"
);
}
}
#[test]
fn concurrentCloneStress() -> ()
{
use std::thread;
const Threads: usize = 8;
const PerThread: usize = 20;
let handles: Vec<thread::JoinHandle<()>> = (0..Threads)
.map(|t| {
thread::spawn(move || {
for i in 0..PerThread
{
let result: f64 = ffi!(|scope| {
let libm: Library = scope.load(LibmPath)?;
libm.call("sqrt").arg::<f64>(4.0).result()
})
.unwrap_or_else(|e| {
panic!("concurrent clone stress failed on thread {t} iteration {i}: {e}")
});
assert!(
(result - 2.0).abs() < f64::EPSILON,
"unexpected sqrt result on thread {t} iteration {i}"
);
}
})
})
.collect();
for handle in handles
{
handle.join().expect("concurrent clone stress thread panicked");
}
}
#[test]
fn rapidCloneCreateDrop() -> ()
{
const Iterations: usize = 100;
for i in 0..Iterations
{
let zygote: crate::zygote::ClonedZygote = crate::zygote::ClonedZygote::getMeClone()
.unwrap_or_else(|e| panic!("getMeClone failed on iteration {i}: {e}"));
drop(zygote);
}
}
}