2024-04-23 00:03:18 +00:00
|
|
|
use std::future::Future;
|
2024-04-16 09:39:52 +00:00
|
|
|
use std::net::{Ipv4Addr, SocketAddr};
|
|
|
|
use std::time::Duration;
|
|
|
|
use std::sync::Once;
|
|
|
|
use std::process::Stdio;
|
|
|
|
use std::io::Read;
|
|
|
|
|
|
|
|
use rocket::fairing::AdHoc;
|
|
|
|
use rocket::listener::{Bind, DefaultListener};
|
|
|
|
use rocket::serde::{Deserialize, DeserializeOwned, Serialize};
|
|
|
|
use rocket::{Build, Ignite, Rocket};
|
2024-05-15 03:20:06 +00:00
|
|
|
use rocket::trace::Traceable;
|
2024-04-16 09:39:52 +00:00
|
|
|
|
|
|
|
use ipc_channel::ipc::{IpcOneShotServer, IpcReceiver, IpcSender};
|
|
|
|
|
|
|
|
use crate::{Result, Error};
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Server {
|
|
|
|
proc: procspawn::JoinHandle<Launched>,
|
|
|
|
pub tls: bool,
|
|
|
|
pub port: u16,
|
|
|
|
_rx: IpcReceiver<Message>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
#[serde(crate = "rocket::serde")]
|
|
|
|
pub enum Message {
|
|
|
|
Liftoff(bool, u16),
|
|
|
|
Failure,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(crate = "rocket::serde")]
|
|
|
|
pub struct Token(String);
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(crate = "rocket::serde")]
|
|
|
|
pub struct Launched(());
|
|
|
|
|
|
|
|
fn stdio() -> Stdio {
|
|
|
|
std::env::var_os("NOCAPTURE")
|
|
|
|
.map(|_| Stdio::inherit())
|
|
|
|
.unwrap_or_else(Stdio::piped)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn read<T: Read>(io: Option<T>) -> Result<String> {
|
|
|
|
if let Some(mut io) = io {
|
|
|
|
let mut string = String::new();
|
|
|
|
io.read_to_string(&mut string)?;
|
|
|
|
return Ok(string);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(String::new())
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Server {
|
|
|
|
pub fn spawn<T>(ctxt: T, f: fn((Token, T)) -> Launched) -> Result<Server>
|
|
|
|
where T: Serialize + DeserializeOwned
|
|
|
|
{
|
|
|
|
static INIT: Once = Once::new();
|
|
|
|
INIT.call_once(procspawn::init);
|
|
|
|
|
|
|
|
let (ipc, server) = IpcOneShotServer::new()?;
|
|
|
|
let mut proc = procspawn::Builder::new()
|
|
|
|
.stdin(Stdio::null())
|
|
|
|
.stdout(stdio())
|
|
|
|
.stderr(stdio())
|
|
|
|
.spawn((Token(server), ctxt), f);
|
|
|
|
|
|
|
|
let (rx, _) = ipc.accept().unwrap();
|
|
|
|
match rx.recv()? {
|
|
|
|
Message::Liftoff(tls, port) => {
|
|
|
|
Ok(Server { proc, tls, port, _rx: rx })
|
|
|
|
},
|
|
|
|
Message::Failure => {
|
|
|
|
Err(Error::Liftoff(read(proc.stdout())?, read(proc.stderr())?))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn socket_addr(&self) -> SocketAddr {
|
|
|
|
let ip = Ipv4Addr::LOCALHOST;
|
|
|
|
SocketAddr::new(ip.into(), self.port)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn read_stdout(&mut self) -> Result<String> {
|
|
|
|
read(self.proc.stdout())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn read_stderr(&mut self) -> Result<String> {
|
|
|
|
read(self.proc.stderr())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn kill(&mut self) -> Result<()> {
|
|
|
|
Ok(self.proc.kill()?)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn terminate(&mut self) -> Result<()> {
|
|
|
|
use nix::{sys::signal, unistd::Pid};
|
|
|
|
|
|
|
|
let pid = Pid::from_raw(self.proc.pid().unwrap() as i32);
|
|
|
|
Ok(signal::kill(pid, signal::SIGTERM)?)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn join(&mut self, duration: Duration) -> Result<()> {
|
|
|
|
match self.proc.join_timeout(duration) {
|
|
|
|
Ok(_) => Ok(()),
|
|
|
|
Err(e) if e.is_remote_close() => Ok(()),
|
|
|
|
Err(e) => Err(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Token {
|
2024-04-23 00:03:18 +00:00
|
|
|
pub fn with_launch<F, Fut>(self, rocket: Rocket<Build>, launch: F) -> Launched
|
|
|
|
where F: FnOnce(Rocket<Ignite>) -> Fut + Send + Sync + 'static,
|
|
|
|
Fut: Future<Output = Result<Rocket<Ignite>, rocket::Error>> + Send
|
2024-04-16 09:39:52 +00:00
|
|
|
{
|
|
|
|
let server = self.0.clone();
|
|
|
|
let rocket = rocket.attach(AdHoc::on_liftoff("Liftoff", move |rocket| Box::pin(async move {
|
|
|
|
let tcp = rocket.endpoints().find_map(|e| e.tcp()).unwrap();
|
|
|
|
let tls = rocket.endpoints().any(|e| e.is_tls());
|
|
|
|
let sender = IpcSender::<Message>::connect(server).unwrap();
|
|
|
|
let _ = sender.send(Message::Liftoff(tls, tcp.port()));
|
|
|
|
let _ = sender.send(Message::Liftoff(tls, tcp.port()));
|
|
|
|
})));
|
|
|
|
|
|
|
|
let server = self.0.clone();
|
2024-04-23 00:03:18 +00:00
|
|
|
let launch = async move {
|
|
|
|
let rocket = rocket.ignite().await?;
|
|
|
|
launch(rocket).await
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Err(e) = rocket::execute(launch) {
|
2024-04-16 09:39:52 +00:00
|
|
|
let sender = IpcSender::<Message>::connect(server).unwrap();
|
|
|
|
let _ = sender.send(Message::Failure);
|
|
|
|
let _ = sender.send(Message::Failure);
|
2024-05-15 03:20:06 +00:00
|
|
|
e.trace_error();
|
2024-04-16 09:39:52 +00:00
|
|
|
std::process::exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
Launched(())
|
|
|
|
}
|
|
|
|
|
2024-04-23 00:03:18 +00:00
|
|
|
pub fn launch_with<B: Bind>(self, rocket: Rocket<Build>) -> Launched
|
|
|
|
where B: Send + Sync + 'static
|
|
|
|
{
|
|
|
|
self.with_launch(rocket, |rocket| rocket.launch_with::<B>())
|
|
|
|
}
|
|
|
|
|
2024-04-16 09:39:52 +00:00
|
|
|
pub fn launch(self, rocket: Rocket<Build>) -> Launched {
|
|
|
|
self.launch_with::<DefaultListener>(rocket)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for Server {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
let _ = self.terminate();
|
|
|
|
if self.join(Duration::from_secs(3)).is_err() {
|
|
|
|
let _ = self.kill();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! spawn {
|
|
|
|
($($arg:ident : $t:ty),* => $rocket:block) => {{
|
|
|
|
#[allow(unused_parens)]
|
|
|
|
fn _server((token, $($arg),*): ($crate::Token, $($t),*)) -> $crate::Launched {
|
|
|
|
let rocket: rocket::Rocket<rocket::Build> = $rocket;
|
|
|
|
token.launch(rocket)
|
|
|
|
}
|
|
|
|
|
|
|
|
Server::spawn(($($arg),*), _server)
|
|
|
|
}};
|
|
|
|
|
|
|
|
($($token:tt)*) => {{
|
|
|
|
let _unit = ();
|
|
|
|
spawn!(_unit: () => { $($token)* } )
|
|
|
|
}};
|
|
|
|
}
|