monocoque/zmq/push.rs
1//! PUSH socket implementation.
2//!
3//! PUSH sockets are used in pipeline patterns for distributing tasks.
4
5use monocoque_core::monitor::{SocketEventSender, SocketMonitor, create_monitor};
6use monocoque_core::options::SocketOptions;
7use monocoque_core::rt::{TcpListener, TcpStream};
8use monocoque_zmtp::PushSocket as InternalPush;
9use std::io;
10
11/// PUSH socket for distributing tasks in a pipeline.
12///
13/// PUSH sockets send messages in a round-robin fashion to connected PULL sockets.
14pub struct PushSocket<S = TcpStream>
15where
16 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
17{
18 inner: InternalPush<S>,
19 monitor: Option<SocketEventSender>,
20}
21
22impl PushSocket<TcpStream> {
23 /// Bind to `addr`, accept one connection, and return a ready PUSH socket.
24 ///
25 /// Returns the `TcpListener` so the caller can accept further PULL connections.
26 ///
27 /// # Example
28 ///
29 /// ```rust,no_run
30 /// use monocoque::zmq::PushSocket;
31 /// use bytes::Bytes;
32 ///
33 /// # async fn example() -> std::io::Result<()> {
34 /// let (_listener, mut socket) = PushSocket::bind("127.0.0.1:5555").await?;
35 /// socket.send(vec![Bytes::from("task")]).await?;
36 /// # Ok(())
37 /// # }
38 /// ```
39 pub async fn bind(
40 addr: impl monocoque_core::rt::ToSocketAddrs,
41 ) -> io::Result<(TcpListener, Self)> {
42 let listener = TcpListener::bind(addr).await?;
43 let (stream, _) = listener.accept().await?;
44 let socket = Self::from_tcp(stream).await?;
45 Ok((listener, socket))
46 }
47
48 /// Connect to a PULL socket at `addr`.
49 ///
50 /// # Example
51 ///
52 /// ```rust,no_run
53 /// use monocoque::zmq::PushSocket;
54 /// use bytes::Bytes;
55 ///
56 /// # async fn example() -> std::io::Result<()> {
57 /// let mut socket = PushSocket::connect("127.0.0.1:5555").await?;
58 /// socket.send(vec![Bytes::from("task")]).await?;
59 /// # Ok(())
60 /// # }
61 /// ```
62 pub async fn connect(addr: impl monocoque_core::rt::ToSocketAddrs) -> io::Result<Self> {
63 Ok(Self {
64 inner: InternalPush::connect(addr).await?,
65 monitor: None,
66 })
67 }
68
69 /// Connect with custom options, storing the endpoint for automatic reconnection.
70 pub async fn connect_with_options(
71 addr: impl monocoque_core::rt::ToSocketAddrs,
72 options: SocketOptions,
73 ) -> io::Result<Self> {
74 Ok(Self {
75 inner: InternalPush::connect_with_options(addr, options).await?,
76 monitor: None,
77 })
78 }
79
80 /// Check if the socket is currently connected.
81 #[inline]
82 pub fn is_connected(&self) -> bool {
83 self.inner.is_connected()
84 }
85
86 /// Try to reconnect to the stored endpoint.
87 pub async fn try_reconnect(&mut self) -> io::Result<()> {
88 self.inner.try_reconnect().await
89 }
90
91 /// Send with automatic reconnection on network error.
92 pub async fn send_with_reconnect(&mut self, msg: Vec<bytes::Bytes>) -> io::Result<()> {
93 self.inner.send_with_reconnect(msg).await
94 }
95
96 /// Create a PUSH socket from a TCP stream.
97 pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
98 Ok(Self {
99 inner: InternalPush::from_tcp(stream).await?,
100 monitor: None,
101 })
102 }
103
104 /// Create a PUSH socket from a TCP stream with custom options.
105 pub async fn from_tcp_with_options(
106 stream: TcpStream,
107 options: SocketOptions,
108 ) -> io::Result<Self> {
109 Ok(Self {
110 inner: InternalPush::from_tcp_with_options(stream, options).await?,
111 monitor: None,
112 })
113 }
114}
115
116impl<S> PushSocket<S>
117where
118 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
119{
120 /// Create a PUSH socket from any stream.
121 pub async fn new(stream: S) -> io::Result<Self> {
122 Ok(Self {
123 inner: InternalPush::new(stream).await?,
124 monitor: None,
125 })
126 }
127
128 /// Create a PUSH socket from any stream with custom options.
129 pub async fn with_options(stream: S, options: SocketOptions) -> io::Result<Self> {
130 Ok(Self {
131 inner: InternalPush::with_options(stream, options).await?,
132 monitor: None,
133 })
134 }
135
136 /// Send a message.
137 ///
138 /// By default each call writes to the kernel immediately (eager mode). For
139 /// throughput-bound pipelines, enable write coalescing via
140 /// [`SocketOptions::with_write_coalescing`] and call [`flush`](Self::flush) after
141 /// the last send in each burst. See `docs/performance.md` for measured numbers and
142 /// an explanation of when each mode is appropriate.
143 pub async fn send(&mut self, msg: Vec<bytes::Bytes>) -> io::Result<()> {
144 self.inner.send(msg).await
145 }
146
147 /// Send a single-frame message without allocating a multipart `Vec`.
148 ///
149 /// Equivalent to `send(vec![frame])`, but avoids the per-message container
150 /// allocation in single-frame hot paths.
151 pub async fn send_one(&mut self, frame: bytes::Bytes) -> io::Result<()> {
152 self.inner.send_one(frame).await
153 }
154
155 /// Flush any messages still buffered by write coalescing.
156 ///
157 /// Required after the last `send()` in a burst when `write_coalescing` is enabled.
158 /// In coalesced mode, bytes accumulate in a userspace buffer and are not guaranteed
159 /// to reach the peer until this is called (or the 64 KB threshold fills naturally).
160 /// Has no effect in eager mode (the default).
161 pub async fn flush(&mut self) -> io::Result<()> {
162 self.inner.flush().await
163 }
164
165 /// Send a batch of messages in a single kernel write.
166 ///
167 /// Encodes all messages into the send buffer and flushes once, regardless of
168 /// whether `write_coalescing` is enabled. Use this when you have a collection
169 /// of messages ready to send and want to minimize syscall count without managing
170 /// `flush()` calls manually:
171 ///
172 /// ```rust,no_run
173 /// # async fn example(push: &mut monocoque::zmq::PushSocket) -> std::io::Result<()> {
174 /// use bytes::Bytes;
175 /// let batch: Vec<Vec<Bytes>> = (0..100)
176 /// .map(|i| vec![Bytes::from(format!("msg-{i}").into_bytes())])
177 /// .collect();
178 /// push.send_batch(batch).await?;
179 /// # Ok(())
180 /// # }
181 /// ```
182 ///
183 /// Returns the number of messages sent.
184 pub async fn send_batch<I>(&mut self, msgs: I) -> io::Result<usize>
185 where
186 I: IntoIterator<Item = Vec<bytes::Bytes>>,
187 {
188 self.inner.send_batch(msgs).await
189 }
190
191 /// Enable monitoring for this socket.
192 pub fn monitor(&mut self) -> SocketMonitor {
193 let (sender, receiver) = create_monitor();
194 self.monitor = Some(sender);
195 receiver
196 }
197
198 /// Get a mutable reference to this socket's options.
199 #[inline]
200 pub fn options_mut(&mut self) -> &mut SocketOptions {
201 self.inner.options_mut()
202 }
203}
204
205#[cfg(unix)]
206impl PushSocket<monocoque_core::rt::UnixStream> {
207 /// Create a PUSH socket from a Unix domain socket stream (IPC).
208 pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
209 Ok(Self {
210 inner: InternalPush::new(stream).await?,
211 monitor: None,
212 })
213 }
214
215 /// Create a PUSH socket from a Unix domain socket stream with custom options.
216 pub async fn from_unix_stream_with_options(
217 stream: monocoque_core::rt::UnixStream,
218 options: SocketOptions,
219 ) -> io::Result<Self> {
220 Ok(Self {
221 inner: InternalPush::with_options(stream, options).await?,
222 monitor: None,
223 })
224 }
225}