Skip to main content

monocoque/zmq/
pull.rs

1//! PULL socket implementation.
2//!
3//! PULL sockets are used in pipeline patterns for receiving tasks.
4
5use monocoque_core::monitor::{SocketEventSender, SocketMonitor, create_monitor};
6use monocoque_core::options::SocketOptions;
7use monocoque_core::rt::{TcpListener, TcpStream};
8use monocoque_zmtp::PullSocket as InternalPull;
9use std::io;
10
11/// PULL socket for receiving tasks in a pipeline.
12///
13/// PULL sockets receive messages from connected PUSH sockets.
14pub struct PullSocket<S = TcpStream>
15where
16    S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
17{
18    inner: InternalPull<S>,
19    monitor: Option<SocketEventSender>,
20}
21
22impl PullSocket<TcpStream> {
23    /// Bind to `addr`, accept one connection, and return a ready PULL socket.
24    ///
25    /// Returns the `TcpListener` so the caller can accept further PUSH connections.
26    ///
27    /// # Example
28    ///
29    /// ```rust,no_run
30    /// use monocoque::zmq::PullSocket;
31    ///
32    /// # async fn example() -> std::io::Result<()> {
33    /// let (_listener, mut socket) = PullSocket::bind("127.0.0.1:5555").await?;
34    /// while let Ok(Some(msg)) = socket.recv().await {
35    ///     println!("Got task: {:?}", msg);
36    /// }
37    /// # Ok(())
38    /// # }
39    /// ```
40    pub async fn bind(
41        addr: impl monocoque_core::rt::ToSocketAddrs,
42    ) -> io::Result<(TcpListener, Self)> {
43        let listener = TcpListener::bind(addr).await?;
44        let (stream, _) = listener.accept().await?;
45        let socket = Self::from_tcp(stream).await?;
46        Ok((listener, socket))
47    }
48
49    /// Connect to a PUSH socket at `addr`.
50    ///
51    /// # Example
52    ///
53    /// ```rust,no_run
54    /// use monocoque::zmq::PullSocket;
55    ///
56    /// # async fn example() -> std::io::Result<()> {
57    /// let mut socket = PullSocket::connect("127.0.0.1:5555").await?;
58    /// while let Ok(Some(msg)) = socket.recv().await {
59    ///     println!("Got task: {:?}", msg);
60    /// }
61    /// # Ok(())
62    /// # }
63    /// ```
64    pub async fn connect(addr: impl monocoque_core::rt::ToSocketAddrs) -> io::Result<Self> {
65        Ok(Self {
66            inner: InternalPull::connect(addr).await?,
67            monitor: None,
68        })
69    }
70
71    /// Connect with custom options, storing the endpoint for automatic reconnection.
72    pub async fn connect_with_options(
73        addr: impl monocoque_core::rt::ToSocketAddrs,
74        options: SocketOptions,
75    ) -> io::Result<Self> {
76        Ok(Self {
77            inner: InternalPull::connect_with_options(addr, options).await?,
78            monitor: None,
79        })
80    }
81
82    /// Check if the socket is currently connected.
83    #[inline]
84    pub fn is_connected(&self) -> bool {
85        self.inner.is_connected()
86    }
87
88    /// Try to reconnect to the stored endpoint.
89    pub async fn try_reconnect(&mut self) -> io::Result<()> {
90        self.inner.try_reconnect().await
91    }
92
93    /// Receive with automatic reconnection on EOF or network error.
94    pub async fn recv_with_reconnect(&mut self) -> io::Result<Option<Vec<bytes::Bytes>>> {
95        self.inner.recv_with_reconnect().await
96    }
97
98    /// Create a PULL socket from a TCP stream.
99    pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
100        Ok(Self {
101            inner: InternalPull::from_tcp(stream).await?,
102            monitor: None,
103        })
104    }
105
106    /// Create a PULL socket from a TCP stream with custom options.
107    pub async fn from_tcp_with_options(
108        stream: TcpStream,
109        options: SocketOptions,
110    ) -> io::Result<Self> {
111        Ok(Self {
112            inner: InternalPull::from_tcp_with_options(stream, options).await?,
113            monitor: None,
114        })
115    }
116}
117
118impl<S> PullSocket<S>
119where
120    S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
121{
122    /// Create a PULL socket from any stream.
123    pub async fn new(stream: S) -> io::Result<Self> {
124        Ok(Self {
125            inner: InternalPull::new(stream).await?,
126            monitor: None,
127        })
128    }
129
130    /// Create a PULL socket from any stream with custom options.
131    pub async fn with_options(stream: S, options: SocketOptions) -> io::Result<Self> {
132        Ok(Self {
133            inner: InternalPull::with_options(stream, options).await?,
134            monitor: None,
135        })
136    }
137
138    /// Try to receive a message from the already-buffered input without a kernel read.
139    ///
140    /// Returns `Ok(None)` immediately when the receive buffer is empty. Use
141    /// after `recv()` to drain all messages delivered in one kernel read before
142    /// going back to the event loop. This reduces io_uring submissions for
143    /// throughput-bound pull loops.
144    ///
145    /// ```rust,no_run
146    /// # async fn example(pull: &mut monocoque::zmq::PullSocket) -> std::io::Result<()> {
147    /// if let Some(first) = pull.recv().await? {
148    ///     drop(first);
149    ///     while let Some(msg) = pull.try_recv()? {
150    ///         drop(msg);
151    ///     }
152    /// }
153    /// # Ok(())
154    /// # }
155    /// ```
156    pub fn try_recv(&mut self) -> io::Result<Option<Vec<bytes::Bytes>>> {
157        self.inner.try_recv()
158    }
159
160    /// Receive a message.
161    pub async fn recv(&mut self) -> io::Result<Option<Vec<bytes::Bytes>>> {
162        self.inner.recv().await
163    }
164
165    /// Receive a message into a caller-provided buffer, reusing its allocation.
166    ///
167    /// Like [`recv`](Self::recv) but the frames are written into `out` (cleared
168    /// first) instead of a freshly allocated `Vec`. Passing the same `out` on
169    /// every call removes the per-message allocation from a steady recv loop,
170    /// which is the dominant per-message cost for small messages. Returns
171    /// `Ok(true)` when a message was read, `Ok(false)` when the connection closed.
172    pub async fn recv_into(&mut self, out: &mut Vec<bytes::Bytes>) -> io::Result<bool> {
173        self.inner.recv_into(out).await
174    }
175
176    /// Try to receive a message into a caller-provided buffer without a kernel read.
177    ///
178    /// The allocation-free counterpart to [`try_recv`](Self::try_recv): returns
179    /// `Ok(true)` with the frames moved into `out` (reusing its capacity) when a
180    /// complete message is already buffered, or `Ok(false)` leaving `out` untouched
181    /// when none is. Use it with [`recv_into`](Self::recv_into) to drain a burst
182    /// from one kernel read without allocating per message.
183    pub fn try_recv_into(&mut self, out: &mut Vec<bytes::Bytes>) -> io::Result<bool> {
184        self.inner.try_recv_into(out)
185    }
186
187    /// Receive a batch of messages with a single `.await`.
188    ///
189    /// Blocks until at least one message is available, then drains every further
190    /// message already decoded from the same kernel read. Returning a burst of
191    /// small messages from one `.await` amortizes per-await overhead; it is the
192    /// receive-side counterpart to [`PushSocket::send_batch`](crate::zmq::PushSocket::send_batch).
193    pub async fn recv_batch(&mut self) -> io::Result<Option<Vec<Vec<bytes::Bytes>>>> {
194        self.inner.recv_batch().await
195    }
196
197    /// Enable monitoring for this socket.
198    pub fn monitor(&mut self) -> SocketMonitor {
199        let (sender, receiver) = create_monitor();
200        self.monitor = Some(sender);
201        receiver
202    }
203
204    /// Get a mutable reference to this socket's options.
205    #[inline]
206    pub fn options_mut(&mut self) -> &mut SocketOptions {
207        self.inner.options_mut()
208    }
209}
210
211#[cfg(unix)]
212impl PullSocket<monocoque_core::rt::UnixStream> {
213    /// Create a PULL socket from a Unix domain socket stream (IPC).
214    pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
215        Ok(Self {
216            inner: InternalPull::new(stream).await?,
217            monitor: None,
218        })
219    }
220
221    /// Create a PULL socket from a Unix domain socket stream with custom options.
222    pub async fn from_unix_stream_with_options(
223        stream: monocoque_core::rt::UnixStream,
224        options: SocketOptions,
225    ) -> io::Result<Self> {
226        Ok(Self {
227            inner: InternalPull::with_options(stream, options).await?,
228            monitor: None,
229        })
230    }
231}