use std::path::{Path, PathBuf};
use std::process::Stdio;
use tokio::io::AsyncWriteExt as _;
use super::wire::{RawCapture, SymbolReport};
pub const SYMBOLIZER_WORKER_ENV: &str = "KERNAL_API_SYMBOLIZER";
pub fn default_worker_path() -> std::io::Result<PathBuf> {
if let Some(path) = std::env::var_os(SYMBOLIZER_WORKER_ENV) {
if !path.is_empty() {
return Ok(path.into());
}
}
let mut path = std::env::current_exe()?;
path.set_file_name(if cfg!(windows) {
"kernal-symbolize.exe"
} else {
"kernal-symbolize"
});
Ok(path)
}
#[derive(Clone, Debug)]
pub struct SymbolizerWorker {
executable: PathBuf,
}
impl SymbolizerWorker {
pub fn new(executable: impl Into<PathBuf>) -> Self {
Self {
executable: executable.into(),
}
}
pub fn discover() -> Result<Self, WorkerError> {
Ok(Self::new(default_worker_path()?))
}
pub fn executable(&self) -> &Path {
&self.executable
}
pub async fn symbolize(&self, capture: &RawCapture) -> Result<SymbolReport, WorkerError> {
let input = capture.encode_wire();
let mut child = tokio::process::Command::new(&self.executable)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.kill_on_drop(true)
.spawn()
.map_err(|source| WorkerError::Spawn {
executable: self.executable.clone(),
source,
})?;
let mut stdin = child.stdin.take().ok_or(WorkerError::MissingStdin)?;
stdin.write_all(&input).await?;
drop(stdin);
let output = child.wait_with_output().await?;
if !output.status.success() {
return Err(WorkerError::Failed {
status: output.status.code(),
stderr: String::from_utf8_lossy(&output.stderr).trim().to_owned(),
});
}
Ok(SymbolReport::decode_wire(&output.stdout)?)
}
}
#[derive(Debug, thiserror::Error)]
pub enum WorkerError {
#[error("cannot determine the symbolizer worker path: {0}")]
Io(#[from] std::io::Error),
#[error("cannot start symbolizer worker {executable}: {source}", executable = executable.display())]
Spawn {
executable: PathBuf,
source: std::io::Error,
},
#[error("symbolizer worker started without piped stdin")]
MissingStdin,
#[error("symbolizer wire payload is invalid: {0}")]
Wire(#[from] super::wire::WireError),
#[error("symbolizer worker failed with status {status:?}: {stderr}")]
Failed {
status: Option<i32>,
stderr: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sibling_worker_has_the_platform_executable_suffix() {
let worker = default_worker_path().unwrap();
let expected = if cfg!(windows) {
"kernal-symbolize.exe"
} else {
"kernal-symbolize"
};
assert_eq!(worker.file_name().unwrap(), expected);
}
}