acktor_ipc/ipc_method/
pipe.rs1use std::io::{Error, ErrorKind};
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use bytes::Bytes;
7use futures_util::{SinkExt, StreamExt};
8use interprocess::local_socket::{
9 GenericNamespaced, ListenerOptions,
10 tokio::{RecvHalf, SendHalf, prelude::*},
11};
12use tokio_util::codec::{FramedRead, FramedWrite, length_delimited::LengthDelimitedCodec};
13use tracing::info;
14
15use super::{IoFuture, IpcConnection, IpcListener};
16
17#[derive(Debug)]
19pub struct PipeListener {
20 name: String,
21 listener: LocalSocketListener,
22 accept_counter: AtomicU64,
23}
24
25impl PipeListener {
26 pub fn new(name: &str) -> Result<Self, Error> {
28 let name_string = name.to_string();
29 let name = name.to_ns_name::<GenericNamespaced>()?;
30
31 let opts = ListenerOptions::new().name(name);
32 let listener = opts.create_tokio()?;
33
34 Ok(Self {
35 name: name_string,
36 listener,
37 accept_counter: AtomicU64::new(0),
38 })
39 }
40}
41
42impl IpcListener for PipeListener {
43 fn local_endpoint(&self) -> &str {
44 self.name.as_str()
45 }
46
47 fn accept(&self) -> IoFuture<'_, Box<dyn IpcConnection>> {
48 Box::pin(async move {
49 let stream = self.listener.accept().await?;
50
51 let id = self.accept_counter.fetch_add(1, Ordering::Relaxed);
52 let name = format!("{}#{}", self.name, id);
53
54 info!("Accepted a new pipe connection: {}", name);
55
56 Ok(Box::new(PipeConnection::new(stream, name)) as Box<dyn IpcConnection>)
57 })
58 }
59}
60
61#[derive(Debug)]
63pub struct PipeConnection {
64 name: String,
65 tx: FramedWrite<SendHalf, LengthDelimitedCodec>,
66 rx: FramedRead<RecvHalf, LengthDelimitedCodec>,
67}
68
69impl PipeConnection {
70 fn new(stream: LocalSocketStream, name: String) -> Self {
71 let (rx, tx) = stream.split();
72 let codec = LengthDelimitedCodec::new();
73
74 Self {
75 name,
76 tx: FramedWrite::new(tx, codec.clone()),
77 rx: FramedRead::new(rx, codec),
78 }
79 }
80}
81
82impl IpcConnection for PipeConnection {
83 async fn connect(name: &str) -> Result<Self, Error> {
84 let stream = LocalSocketStream::connect(name.to_ns_name::<GenericNamespaced>()?).await?;
85
86 info!("Connected to pipe {}", name);
87
88 Ok(Self::new(stream, name.to_string()))
89 }
90
91 fn peer_endpoint(&self) -> &str {
92 self.name.as_str()
93 }
94
95 fn close(&mut self) -> IoFuture<'_, ()> {
96 Box::pin(async move { Ok(()) })
97 }
98
99 fn send(&mut self, buf: Bytes) -> IoFuture<'_, ()> {
100 Box::pin(self.tx.send(buf))
101 }
102
103 fn recv(&mut self) -> IoFuture<'_, Bytes> {
104 Box::pin(async move {
105 let frame = self
106 .rx
107 .next()
108 .await
109 .ok_or_else(|| Error::from(ErrorKind::ConnectionAborted))??;
110
111 Ok(frame.freeze())
112 })
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 fn unique_pipe_name() -> String {
121 static COUNTER: AtomicU64 = AtomicU64::new(0);
122 format!(
123 "acktor-ipc-pipe-test-{}-{}",
124 std::process::id(),
125 COUNTER.fetch_add(1, Ordering::Relaxed)
126 )
127 }
128
129 #[tokio::test]
130 async fn test_listener() {
131 let name = unique_pipe_name();
133 let listener = PipeListener::new(&name).unwrap();
134 assert_eq!(listener.local_endpoint(), name);
135
136 PipeListener::new(&name).expect_err("second bind to the same name must fail");
138
139 let missing = unique_pipe_name();
141 PipeConnection::connect(&missing)
142 .await
143 .expect_err("connect to non-existent pipe must fail");
144 }
145
146 #[tokio::test]
147 async fn test_connection() {
148 let name = unique_pipe_name();
149 let listener = PipeListener::new(&name).unwrap();
150 let server_name = name.clone();
151
152 let server = tokio::spawn(async move {
153 let mut c1 = listener.accept().await.expect("accept c1");
155 assert_eq!(c1.peer_endpoint(), format!("{server_name}#0"));
156 let msg = c1.recv().await.expect("recv c1");
157 c1.send(msg).await.expect("send c1");
158 c1.close().await.expect("close c1");
159
160 let c2 = listener.accept().await.expect("accept c2");
163 assert_eq!(c2.peer_endpoint(), format!("{server_name}#1"));
164 drop(c2);
165 });
166
167 let mut client1 = PipeConnection::connect(&name).await.expect("connect c1");
169 assert_eq!(client1.peer_endpoint(), name);
170 let payload = Bytes::from_static(b"hello");
171 client1.send(payload.clone()).await.expect("send");
172 assert_eq!(client1.recv().await.expect("recv"), payload);
173 client1.close().await.expect("close");
174
175 let mut client2 = PipeConnection::connect(&name).await.expect("connect c2");
177 server.await.unwrap();
178 let err = client2
179 .recv()
180 .await
181 .expect_err("recv should fail after peer drop");
182 assert_eq!(err.kind(), ErrorKind::ConnectionAborted);
183 }
184}