samply 0.13.1

A command line profiler for macOS and Linux.
use std::error::Error;
use std::fmt::Debug;

use tempfile::TempDir;

use super::file_channel::{BidiChannelCreator, Receiver, Sender};
use super::shared::{
    ChildToParentMsgWrapper, InitMessage, ParentToChildMsgWrapper, PKG_NAME, PKG_VERSION,
};
use super::traits::{UtilityProcess, UtilityProcessParent};

#[derive(Debug)]
pub struct UtilityProcessSession<T: UtilityProcess> {
    _ipc_dir: TempDir,
    spawn_thread: std::thread::JoinHandle<()>,
    sender: Sender<ParentToChildMsgWrapper<T::ParentToChildMsg>>,
    receiver: Receiver<ChildToParentMsgWrapper<T::ChildToParentMsg>>,
}

impl<T: UtilityProcess> UtilityProcessSession<T> {
    pub fn spawn_process(parent: T::Parent) -> Result<Self, Box<dyn Error + Send + Sync>> {
        let ipc_dir = TempDir::with_prefix("samply-elevated-helper-")?;
        let channel = BidiChannelCreator::create_in_parent(ipc_dir.path())?;

        let ipc_dir_path = ipc_dir.path().to_owned();
        let spawn_thread = std::thread::Builder::new()
            .name("UtilityProcessSession".into())
            .spawn(move || parent.spawn_child(&ipc_dir_path))?;
        let (receiver, sender) = channel.wait_for_child_to_connect()?;

        let mut session = Self {
            _ipc_dir: ipc_dir,
            spawn_thread,
            sender,
            receiver,
        };

        let init_reply = session.send_msg_and_wait_for_response_impl(
            ParentToChildMsgWrapper::Init(InitMessage {
                helper_type: T::PROCESS_TYPE.into(),
                pkg_name: PKG_NAME.into(),
                pkg_version: PKG_VERSION.into(),
            }),
        )?;
        match init_reply {
            ChildToParentMsgWrapper::AckInit => Ok(session),
            ChildToParentMsgWrapper::Err(err) => Err(err.into()),
            other_reply => Err(format!("Unexpected reply to init msg: {other_reply:?}").into()),
        }
    }

    pub fn send_msg_and_wait_for_response(
        &mut self,
        msg: T::ParentToChildMsg,
    ) -> Result<T::ChildToParentMsg, Box<dyn Error + Send + Sync>> {
        let reply = self.send_msg_and_wait_for_response_impl(ParentToChildMsgWrapper::Msg(msg))?;
        match reply {
            ChildToParentMsgWrapper::AckMsg(reply) => Ok(reply),
            ChildToParentMsgWrapper::Err(err) => Err(err.into()),
            _ => Err(format!("Unexpected reply from helper: {reply:?}").into()),
        }
    }

    fn send_msg_and_wait_for_response_impl(
        &mut self,
        msg: ParentToChildMsgWrapper<T::ParentToChildMsg>,
    ) -> Result<ChildToParentMsgWrapper<T::ChildToParentMsg>, Box<dyn Error + Send + Sync>> {
        log::info!("Sending message to elevated helper: {msg:?}");
        self.sender.send(msg)?;
        let reply: ChildToParentMsgWrapper<T::ChildToParentMsg> = self.receiver.recv_blocking()?;
        log::info!("Received reply from elevated helper: {reply:?}");
        Ok(reply)
    }

    pub fn shutdown(mut self) {
        let reply_res = self.send_msg_and_wait_for_response_impl(ParentToChildMsgWrapper::Shutdown);
        match reply_res {
            Ok(ChildToParentMsgWrapper::AckShutdown) => {}
            other_msg => log::warn!("Unexpected reply to Shutdown msg: {other_msg:?}"),
        }

        self.spawn_thread.join().unwrap();
    }
}