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}