Skip to main content

maolan_plugin_protocol/
events.rs

1use std::io;
2use std::os::unix::io::RawFd;
3use std::time::Duration;
4
5/// Lightweight cross-process event signalling using Unix pipes.
6///
7/// Two independent pipes provide bidirectional wake-up:
8/// - `daw_to_host`: DAW writes a byte to wake the host process.
9/// - `host_to_daw`: Host writes a byte to signal completion.
10pub struct EventPair {
11    daw_to_host: [RawFd; 2],
12    host_to_daw: [RawFd; 2],
13}
14
15impl EventPair {
16    /// Create two pipes. Returns `Err` if `pipe(2)` fails.
17    pub fn new() -> io::Result<Self> {
18        let mut daw_to_host = [0; 2];
19        let mut host_to_daw = [0; 2];
20        if unsafe { libc::pipe(daw_to_host.as_mut_ptr()) } != 0 {
21            return Err(io::Error::last_os_error());
22        }
23        if unsafe { libc::pipe(host_to_daw.as_mut_ptr()) } != 0 {
24            unsafe {
25                libc::close(daw_to_host[0]);
26                libc::close(daw_to_host[1]);
27            }
28            return Err(io::Error::last_os_error());
29        }
30        Ok(Self {
31            daw_to_host,
32            host_to_daw,
33        })
34    }
35
36    /// # Safety
37    /// `daw_to_host_read` and `host_to_daw_write` must be valid,
38    /// already-open file descriptors inherited from the parent process.
39    pub unsafe fn from_fds(daw_to_host_read: RawFd, host_to_daw_write: RawFd) -> Self {
40        let mut pair = Self {
41            daw_to_host: [daw_to_host_read, -1],
42            host_to_daw: [-1, host_to_daw_write],
43        };
44        pair.close_host_unused();
45        pair
46    }
47
48    // --- DAW side ---
49
50    pub fn daw_write_fd(&self) -> RawFd {
51        self.daw_to_host[1]
52    }
53
54    pub fn daw_read_fd(&self) -> RawFd {
55        self.host_to_daw[0]
56    }
57
58    /// DAW wakes the host.
59    pub fn signal_host(&self) -> io::Result<()> {
60        write_byte(self.daw_to_host[1])
61    }
62
63    /// DAW waits for host completion (with timeout).
64    pub fn wait_host(&self, timeout: Duration) -> io::Result<()> {
65        read_byte(self.host_to_daw[0], timeout)
66    }
67
68    // --- Host side ---
69
70    pub fn host_read_fd(&self) -> RawFd {
71        self.daw_to_host[0]
72    }
73
74    pub fn host_write_fd(&self) -> RawFd {
75        self.host_to_daw[1]
76    }
77
78    /// Host waits for DAW wake (with timeout).
79    pub fn wait_daw(&self, timeout: Duration) -> io::Result<()> {
80        read_byte(self.daw_to_host[0], timeout)
81    }
82
83    /// Host signals completion to DAW.
84    pub fn signal_daw(&self) -> io::Result<()> {
85        write_byte(self.host_to_daw[1])
86    }
87
88    /// Close the file descriptors that the DAW side does not need.
89    /// Call this on the DAW after spawning the child.
90    pub fn close_daw_unused(&mut self) {
91        unsafe {
92            libc::close(self.daw_to_host[0]);
93            libc::close(self.host_to_daw[1]);
94        }
95        self.daw_to_host[0] = -1;
96        self.host_to_daw[1] = -1;
97    }
98
99    /// Close the file descriptors that the host side does not need.
100    /// Call this on the host after constructing from inherited fds.
101    pub fn close_host_unused(&mut self) {
102        unsafe {
103            libc::close(self.daw_to_host[1]);
104            libc::close(self.host_to_daw[0]);
105        }
106        self.daw_to_host[1] = -1;
107        self.host_to_daw[0] = -1;
108    }
109}
110
111impl Drop for EventPair {
112    fn drop(&mut self) {
113        unsafe {
114            libc::close(self.daw_to_host[0]);
115            libc::close(self.daw_to_host[1]);
116            libc::close(self.host_to_daw[0]);
117            libc::close(self.host_to_daw[1]);
118        }
119    }
120}
121
122fn write_byte(fd: RawFd) -> io::Result<()> {
123    let buf = [1u8];
124    let n = unsafe { libc::write(fd, buf.as_ptr().cast(), 1) };
125    if n < 0 {
126        Err(io::Error::last_os_error())
127    } else {
128        Ok(())
129    }
130}
131
132fn read_byte(fd: RawFd, timeout: Duration) -> io::Result<()> {
133    let mut pfd = libc::pollfd {
134        fd,
135        events: libc::POLLIN,
136        revents: 0,
137    };
138    let ms = timeout.as_millis().clamp(0, i32::MAX as u128) as i32;
139    let rc = unsafe { libc::poll(&mut pfd, 1, ms) };
140    if rc < 0 {
141        return Err(io::Error::last_os_error());
142    }
143    if rc == 0 {
144        return Err(io::Error::new(io::ErrorKind::TimedOut, "poll timeout"));
145    }
146    let mut buf = [0u8; 1];
147    let n = unsafe { libc::read(fd, buf.as_mut_ptr().cast(), 1) };
148    if n < 0 {
149        Err(io::Error::last_os_error())
150    } else {
151        Ok(())
152    }
153}