use super::GameProcess;
use crate::error::{LaunchError, LaunchResult};
use async_trait::async_trait;
use fork::{fork, Fork};
use ipc_channel::ipc::{bytes_channel, IpcBytesReceiver, IpcBytesSender, TryRecvError};
use nix::sys::signal::{kill, Signal};
use nix::sys::wait::waitpid;
use nix::unistd::Pid;
use parking_lot::Mutex;
use std::process::{exit, Command};
use std::sync::Arc;
use std::thread::sleep;
use std::time::Duration;
use tokio::task;
#[cfg_attr(doc_cfg, doc(cfg(unix)))]
#[derive(Clone, Debug)]
pub struct GameProcessHandle {
pid: Arc<Mutex<Option<Pid>>>,
stop: Arc<Mutex<Option<IpcBytesSender>>>,
stopped: Arc<Mutex<Option<IpcBytesReceiver>>>,
}
#[async_trait]
impl GameProcess for GameProcessHandle {
fn launch(mut command: Command) -> LaunchResult<Self> {
let (stop_sender, stop_receiver) = bytes_channel()?;
let (stopped_sender, stopped_receiver) = bytes_channel()?;
match fork() {
Ok(Fork::Child) => {
let mut child = match command.spawn() {
Ok(child) => child,
Err(err) => {
error!("{}", err);
exit(1);
}
};
let pid = Pid::from_raw(child.id() as i32);
loop {
match child.try_wait() {
Ok(Some(status)) => {
match status.success() {
true => debug!("Game exited successfully"),
false => warn!("Game exited with status '{:?}'", status.code()),
}
if let Err(err) = stopped_sender.send(&[]) {
error!("{}", err);
exit(1);
}
exit(0);
}
Err(err) => {
error!("Could not check if game is still running: {}", err);
exit(1);
}
_ => {}
}
match stop_receiver.try_recv() {
Ok(_) => {
let signal = Signal::SIGINT;
if let Err(err) = kill(pid, signal) {
error!("{}", err);
exit(1);
}
}
Err(TryRecvError::Empty) => {}
Err(err) => {
error!("{}", err);
exit(1);
}
}
sleep(Duration::from_millis(500));
}
}
Ok(Fork::Parent(pid)) => {
debug!("Child process forked with PID {}", pid);
Ok(Self {
pid: Arc::new(Mutex::new(Some(Pid::from_raw(pid)))),
stop: Arc::new(Mutex::new(Some(stop_sender))),
stopped: Arc::new(Mutex::new(Some(stopped_receiver))),
})
}
Err(err) => {
error!("{}", err);
Err(LaunchError::ProcessForking)
}
}
}
async fn stop(&self) -> LaunchResult<()> {
let sender = self.stop.clone();
task::spawn_blocking(move || {
let sender = sender.lock();
if let Some(sender) = sender.as_ref() {
sender.send(&[])?;
}
Ok(())
})
.await
.unwrap()
}
async fn wait(&self) -> LaunchResult<()> {
let handle = Self {
pid: self.pid.clone(),
stop: self.stop.clone(),
stopped: self.stopped.clone(),
};
task::spawn_blocking(move || {
loop {
if handle.is_stopped_blocking()? {
break;
}
sleep(Duration::from_millis(500));
}
let mut pid = handle.pid.lock();
let mut stop = handle.stop.lock();
let mut stopped = handle.stopped.lock();
if let Some(pid) = pid.as_ref() {
waitpid(Some(*pid), None).map_err(|err| {
error!("{}", err);
LaunchError::WaitPid
})?;
}
*pid = None;
*stop = None;
*stopped = None;
Ok(())
})
.await
.unwrap()
}
async fn is_stopped(&self) -> LaunchResult<bool> {
let receiver = self.stopped.clone();
task::spawn_blocking(move || {
let receiver = receiver.lock();
match receiver.as_ref() {
Some(receiver) => match receiver.try_recv() {
Ok(_) => Ok(true),
Err(TryRecvError::Empty) => Ok(false),
Err(TryRecvError::IpcError(err)) => Err(LaunchError::Ipc(err)),
},
None => Ok(true),
}
})
.await
.unwrap()
}
fn is_stopped_blocking(&self) -> LaunchResult<bool> {
let receiver = self.stopped.lock();
match receiver.as_ref() {
Some(receiver) => match receiver.try_recv() {
Ok(_) => Ok(true),
Err(TryRecvError::Empty) => Ok(false),
Err(TryRecvError::IpcError(err)) => Err(LaunchError::Ipc(err)),
},
None => Ok(true),
}
}
}