use super::Transport as TransportTrait;
use super::{
CloneSide as CloneSideTrait, FFIRequest, FFIResponse,
RuntimeSide as RuntimeSideTrait, ZygoteHandleBase
};
use crate::platform::low;
use crate::worker::executeFFI;
use crate::worker::{takeLastErrno, takeLastOsError};
use crate::zygote::ZygoteFlag;
use fxhash::FxHashMap;
use ipc_channel::ipc::{self, IpcOneShotServer, IpcReceiver, IpcSender};
use libloading::Library;
use serde::{Deserialize, Serialize};
use std::env;
use std::io;
use std::path::PathBuf;
use std::process::{Child, Command, Stdio};
const BackendName: &str = "macos-ipc-channel";
#[derive(Serialize, Deserialize)]
pub enum ZygoteCommand
{
SpawnClone
}
#[derive(Serialize, Deserialize)]
pub enum ZygoteReply
{
Clone {
pid: u32,
requestTx: IpcSender<FFIRequest>,
responseRx: IpcReceiver<FFIResponse>
},
SpawnFailed
}
#[derive(Serialize, Deserialize)]
struct BootstrapToRuntime
{
commandTx: IpcSender<ZygoteCommand>,
replyRx: IpcReceiver<ZygoteReply>
}
#[derive(Serialize, Deserialize)]
struct CloneBootstrap
{
requestTx: IpcSender<FFIRequest>,
responseRx: IpcReceiver<FFIResponse>
}
pub struct Transport;
pub struct ZygoteHandle
{
pub base: ZygoteHandleBase,
pub commandTx: IpcSender<ZygoteCommand>,
pub replyRx: IpcReceiver<ZygoteReply>
}
pub struct RuntimeSide
{
pub requestTx: IpcSender<FFIRequest>,
pub responseRx: IpcReceiver<FFIResponse>
}
pub struct CloneSide
{
pub requestRx: IpcReceiver<FFIRequest>,
pub responseTx: IpcSender<FFIResponse>
}
#[derive(Serialize, Deserialize)]
pub struct Bootstrap
{
pub pid: u32,
pub requestTx: IpcSender<FFIRequest>,
pub responseRx: IpcReceiver<FFIResponse>
}
impl TransportTrait for Transport
{
type RuntimeSide = RuntimeSide;
type CloneSide = CloneSide;
type Bootstrap = Bootstrap;
type ZygoteHandle = ZygoteHandle;
#[allow(dead_code)]
fn name() -> &'static str
{
BackendName
}
fn spawnZygote() -> io::Result<Self::ZygoteHandle>
{
let (server, serverName): (
IpcOneShotServer<BootstrapToRuntime>,
String
) = IpcOneShotServer::new().map_err(io::Error::other)?;
let currentExe: PathBuf = env::current_exe()?;
let process: Child = Command::new(currentExe)
.arg(ZygoteFlag)
.arg(&serverName)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()?;
let (_rx, bootstrap): (
IpcReceiver<BootstrapToRuntime>,
BootstrapToRuntime
) = server.accept().map_err(|e| {
io::Error::other(format!("zygote bootstrap accept: {e}"))
})?;
Ok(ZygoteHandle {
base: ZygoteHandleBase { process },
commandTx: bootstrap.commandTx,
replyRx: bootstrap.replyRx
})
}
fn sendSpawnClone(handle: &Self::ZygoteHandle) -> io::Result<Self::Bootstrap>
{
handle
.commandTx
.send(ZygoteCommand::SpawnClone)
.map_err(|e| {
io::Error::new(
io::ErrorKind::BrokenPipe,
format!("SpawnClone send failed: {e}")
)
})?;
let reply: ZygoteReply = handle.replyRx.recv().map_err(|e| {
io::Error::new(
io::ErrorKind::BrokenPipe,
format!("SpawnClone reply failed: {e}")
)
})?;
match reply
{
ZygoteReply::Clone { pid, requestTx, responseRx } => Ok(Bootstrap {
pid,
requestTx,
responseRx
}),
ZygoteReply::SpawnFailed => Err(io::Error::other(
"Main zygote failed to create a clone (channel/fork failed)"
))
}
}
fn bootstrapPid(bootstrap: &Self::Bootstrap) -> u32
{
bootstrap.pid
}
fn zygoteControlLoop(flag: Option<String>) -> !
{
let serverName: String =
flag.expect("macos::zygoteControlLoop: missing IpcOneShotServer name");
zygoteLoop(serverName)
}
#[allow(dead_code)]
fn cloneEnter(flag: Option<String>) -> io::Result<(Self::CloneSide, Self::Bootstrap)>
{
let serverName: String =
flag.expect("macos::cloneEnter: missing IpcOneShotServer name");
cloneBootstrapLoop(serverName)
}
fn runtimeConnect(bootstrap: Self::Bootstrap) -> io::Result<Self::RuntimeSide>
{
Ok(RuntimeSide {
requestTx: bootstrap.requestTx,
responseRx: bootstrap.responseRx
})
}
}
impl RuntimeSideTrait for RuntimeSide
{
fn send(&self, request: &FFIRequest) -> Result<(), String>
{
self
.requestTx
.send(request.clone())
.map_err(|e| format!("Zygote clone IPC failed while sending request: {e}"))
}
fn recv(&self) -> Result<FFIResponse, String>
{
self
.responseRx
.recv()
.map_err(|e| format!("Zygote clone IPC failed while reading response: {e}"))
}
}
impl CloneSideTrait for CloneSide
{
#[allow(dead_code)]
fn run(self, cache: &mut FxHashMap<String, Library>) -> !
{
let Self { requestRx, responseTx } = self;
let mut libraryCache: FxHashMap<String, Library> = std::mem::take(cache);
loop
{
let request: FFIRequest = match requestRx.recv()
{
Ok(r) => r,
Err(_) => std::process::exit(0)
};
let response: FFIResponse = handleRequest(request, &mut libraryCache);
if responseTx.send(response).is_err() {
std::process::exit(0);
}
}
}
}
fn handleRequest(request: FFIRequest, cache: &mut FxHashMap<String, Library>) -> FFIResponse
{
match executeFFI(request, cache)
{
Ok(v) => FFIResponse::Ok(v, takeLastErrno(), takeLastOsError()),
Err(e) => FFIResponse::Err(e)
}
}
fn zygoteLoop(serverName: String) -> !
{
low::ignoreChildExits();
let (commandTx, commandRx): (
IpcSender<ZygoteCommand>,
IpcReceiver<ZygoteCommand>
) = match ipc::channel::<ZygoteCommand>() {
Ok(p) => p,
Err(_) => std::process::exit(1)
};
let (replyTx, replyRx): (IpcSender<ZygoteReply>, IpcReceiver<ZygoteReply>) =
match ipc::channel::<ZygoteReply>() {
Ok(p) => p,
Err(_) => std::process::exit(1)
};
let bootstrapTx: IpcSender<BootstrapToRuntime> =
match IpcSender::connect(serverName) {
Ok(tx) => tx,
Err(_) => std::process::exit(1)
};
if bootstrapTx
.send(BootstrapToRuntime { commandTx, replyRx })
.is_err()
{
std::process::exit(1);
}
drop(bootstrapTx);
loop
{
let cmd: ZygoteCommand = match commandRx.recv() {
Ok(c) => c,
Err(_) => std::process::exit(0) };
match cmd
{
ZygoteCommand::SpawnClone =>
{
let (cloneServer, cloneServerName): (
IpcOneShotServer<CloneBootstrap>,
String
) = match IpcOneShotServer::new() {
Ok(s) => s,
Err(_) => {
let _ = replyTx.send(ZygoteReply::SpawnFailed);
continue;
}
};
let spawned: Option<u32> = match unsafe{ libc::fork() } {
-1 => None,
0 => {
std::mem::forget(cloneServer);
std::mem::forget(commandRx);
std::mem::forget(replyTx);
cloneBootstrapLoop(cloneServerName)
}
pid => Some(pid as u32)
};
let Some(pid) = spawned else {
let _ = replyTx.send(ZygoteReply::SpawnFailed);
continue;
};
let (_rx, bootstrap): (
IpcReceiver<CloneBootstrap>,
CloneBootstrap
) = match cloneServer.accept() {
Ok(v) => v,
Err(_) => {
low::killProcess(pid);
let _ = replyTx.send(ZygoteReply::SpawnFailed);
continue;
}
};
let _ = replyTx.send(ZygoteReply::Clone {
pid,
requestTx: bootstrap.requestTx,
responseRx: bootstrap.responseRx
});
}
}
}
}
fn cloneBootstrapLoop(serverName: String) -> !
{
let (requestTx, requestRx): (
IpcSender<FFIRequest>,
IpcReceiver<FFIRequest>
) = match ipc::channel::<FFIRequest>() {
Ok(p) => p,
Err(_) => std::process::exit(1)
};
let (responseTx, responseRx): (
IpcSender<FFIResponse>,
IpcReceiver<FFIResponse>
) = match ipc::channel::<FFIResponse>() {
Ok(p) => p,
Err(_) => std::process::exit(1)
};
let bootstrapTx: IpcSender<CloneBootstrap> =
match IpcSender::connect(serverName) {
Ok(tx) => tx,
Err(_) => std::process::exit(1)
};
if bootstrapTx
.send(CloneBootstrap { requestTx, responseRx })
.is_err()
{
std::process::exit(1);
}
drop(bootstrapTx);
let cache: &mut FxHashMap<String, Library> =
Box::leak(Box::new(FxHashMap::default()));
CloneSide { requestRx, responseTx }.run(cache);
}