use ipc_channel::ipc::{
self, IpcError, IpcOneShotServer, IpcReceiver, IpcSender, OpaqueIpcReceiver, OpaqueIpcSender,
};
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::path::PathBuf;
use std::process::{ChildStderr, ChildStdin, ChildStdout};
use std::sync::atomic::{AtomicBool, Ordering};
use std::{env, mem, process};
mod builder;
pub use builder::*;
const ENV_NAME: &str = "MITOSIS_CONTENT_PROCESS_ID";
pub fn init() {
if let Ok(token) = env::var(ENV_NAME) {
std::env::remove_var(ENV_NAME);
bootstrap_ipc(token);
}
}
static IN_TEST_ENV: AtomicBool = AtomicBool::new(false);
pub fn init_test() {
IN_TEST_ENV.store(true, Ordering::Relaxed);
init();
}
#[derive(Serialize, Deserialize, Debug)]
struct BootstrapData {
wrapper_offset: isize,
args_receiver: OpaqueIpcReceiver,
return_sender: OpaqueIpcSender,
}
fn bootstrap_ipc(token: String) {
let connection_bootstrap: IpcSender<IpcSender<BootstrapData>> =
IpcSender::connect(token).unwrap();
let (tx, rx) = ipc::channel().unwrap();
connection_bootstrap.send(tx).unwrap();
let bootstrap_data = rx.recv().unwrap();
unsafe {
let ptr = bootstrap_data.wrapper_offset + init as *const () as isize;
let func: fn(OpaqueIpcReceiver, OpaqueIpcSender) = mem::transmute(ptr);
func(bootstrap_data.args_receiver, bootstrap_data.return_sender);
}
process::exit(0);
}
pub fn spawn<
F: FnOnce(A) -> R + Copy,
A: Serialize + for<'de> Deserialize<'de>,
R: Serialize + for<'de> Deserialize<'de>,
>(
args: A,
f: F,
) -> JoinHandle<R> {
Builder::new().spawn(args, f)
}
trait ZstAssert: Sized {
const MITOSIS_CLOSURE_CANNOT_BORROW_DATA: () = [()][(mem::size_of::<Self>() != 0) as usize];
}
impl<T: Sized> ZstAssert for T {}
impl Builder {
pub fn spawn<
F: FnOnce(A) -> R + Copy,
A: Serialize + for<'de> Deserialize<'de>,
R: Serialize + for<'de> Deserialize<'de>,
>(
self,
args: A,
_: F,
) -> JoinHandle<R> {
#[allow(path_statements)]
{
F::MITOSIS_CLOSURE_CANNOT_BORROW_DATA;
}
let (server, token) = IpcOneShotServer::<IpcSender<BootstrapData>>::new().unwrap();
let me = if cfg!(target_os = "linux") {
let path: PathBuf = "/proc/self/exe".into();
if path.is_file() {
path
} else {
env::current_exe().unwrap()
}
} else {
env::current_exe().unwrap()
};
let mut child = process::Command::new(me);
assert!(
!self.envs.contains_key(OsStr::new(ENV_NAME)),
"cannot spawn mitosis process with `{}` still set",
ENV_NAME
);
child.envs(self.envs.into_iter());
child.env(ENV_NAME, token);
if IN_TEST_ENV.load(Ordering::Relaxed) {
child.arg("mitosis");
child.arg("--exact");
child.arg("--test-threads=1");
child.arg("-q");
}
if let Some(stdin) = self.stdin {
child.stdin(stdin);
}
if let Some(stdout) = self.stdout {
child.stdout(stdout);
}
if let Some(stderr) = self.stderr {
child.stderr(stderr);
}
let process = child.spawn().unwrap();
let (_rx, tx) = server.accept().unwrap();
let (args_tx, args_rx) = ipc::channel().unwrap();
let (return_tx, return_rx) = ipc::channel().unwrap();
args_tx.send(args).unwrap();
let init_loc = init as *const () as isize;
let wrapper_offset = run_func::<F, A, R> as *const () as isize - init_loc;
let bootstrap = BootstrapData {
wrapper_offset,
args_receiver: args_rx.to_opaque(),
return_sender: return_tx.to_opaque(),
};
tx.send(bootstrap).unwrap();
JoinHandle {
recv: return_rx,
process,
}
}
}
unsafe fn run_func<
F: FnOnce(A) -> R,
A: Serialize + for<'de> Deserialize<'de>,
R: Serialize + for<'de> Deserialize<'de>,
>(
recv: OpaqueIpcReceiver,
sender: OpaqueIpcSender,
) {
let function: F = mem::zeroed();
let args = recv.to().recv().unwrap();
let ret = function(args);
let _ = sender.to().send(ret);
}
pub struct JoinHandle<T> {
recv: IpcReceiver<T>,
process: process::Child,
}
impl<T: Serialize + for<'de> Deserialize<'de>> JoinHandle<T> {
pub fn join(self) -> Result<T, IpcError> {
self.recv.recv()
}
pub fn kill(mut self) -> std::io::Result<()> {
self.process.kill()
}
pub fn stdin(&mut self) -> &mut Option<ChildStdin> {
&mut self.process.stdin
}
pub fn stdout(&mut self) -> &mut Option<ChildStdout> {
&mut self.process.stdout
}
pub fn stderr(&mut self) -> &mut Option<ChildStderr> {
&mut self.process.stderr
}
}