monocoque/zmq/dealer.rs
1//! DEALER socket implementation.
2
3use super::common::{channel_to_io_error, parse_tcp_endpoint};
4use bytes::Bytes;
5use monocoque_core::monitor::{SocketEvent, SocketEventSender, SocketMonitor, create_monitor};
6use monocoque_core::rt::TcpStream;
7use monocoque_zmtp::dealer::DealerSocket as InternalDealer;
8use std::io;
9
10/// A DEALER socket for asynchronous request-reply patterns.
11///
12/// DEALER sockets are fair-queuing clients that distribute messages
13/// across multiple server endpoints. They're used for:
14///
15/// - Load-balanced request-reply
16/// - Async RPC clients
17/// - Worker pools
18///
19/// ## ZeroMQ Compatibility
20///
21/// Compatible with `zmq::DEALER` and `zmq::ROUTER` sockets from libzmq.
22///
23/// ## Example
24///
25/// ```rust,no_run
26/// use monocoque::zmq::DealerSocket;
27/// use bytes::Bytes;
28///
29/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
30/// // Connect to server
31/// let mut socket = DealerSocket::connect("127.0.0.1:5555").await?;
32///
33/// // Send request
34/// socket.send(vec![Bytes::from("REQUEST")]).await?;
35///
36/// // Receive reply
37/// if let Ok(Some(reply)) = socket.recv().await {
38/// println!("Got reply: {:?}", reply);
39/// }
40/// # Ok(())
41/// # }
42/// ```
43pub struct DealerSocket<S = TcpStream>
44where
45 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
46{
47 inner: InternalDealer<S>,
48 monitor: Option<SocketEventSender>,
49}
50
51impl DealerSocket {
52 /// Connect to a ZeroMQ peer and create a DEALER socket.
53 ///
54 /// Supports both TCP and IPC endpoints:
55 /// - TCP: `"tcp://127.0.0.1:5555"` or `"127.0.0.1:5555"`
56 /// - IPC: `"ipc:///tmp/socket.sock"` (Unix only)
57 ///
58 /// # Arguments
59 ///
60 /// * `endpoint` - Endpoint to connect to
61 ///
62 /// # Errors
63 ///
64 /// Returns an error if:
65 /// - The connection fails (network unreachable, connection refused, etc.)
66 /// - DNS resolution fails for TCP endpoints
67 /// - Invalid endpoint format
68 ///
69 /// # Example
70 ///
71 /// ```rust,no_run
72 /// use monocoque::zmq::DealerSocket;
73 ///
74 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
75 /// // TCP connection
76 /// let socket1 = DealerSocket::connect("tcp://127.0.0.1:5555").await?;
77 ///
78 /// // IPC connection (Unix only)
79 /// #[cfg(unix)]
80 /// let socket2 = DealerSocket::connect("ipc:///tmp/socket.sock").await?;
81 /// # Ok(())
82 /// # }
83 /// ```
84 pub async fn connect(endpoint: &str) -> io::Result<Self> {
85 let addr = parse_tcp_endpoint(endpoint)?;
86 let inner = InternalDealer::connect_with_options(
87 addr,
88 monocoque_core::options::SocketOptions::default(),
89 )
90 .await?;
91 let sock = Self {
92 inner,
93 monitor: None,
94 };
95 sock.emit_event(SocketEvent::Connected(
96 monocoque_core::endpoint::Endpoint::Tcp(addr),
97 ));
98 Ok(sock)
99 }
100
101 /// Connect to a ZeroMQ peer with custom socket options.
102 ///
103 /// Stores the endpoint so the socket can reconnect automatically after failures.
104 ///
105 /// # Example
106 ///
107 /// ```rust,no_run
108 /// use monocoque::zmq::{DealerSocket, SocketOptions};
109 /// use std::time::Duration;
110 ///
111 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
112 /// let mut socket = DealerSocket::connect_with_options(
113 /// "tcp://127.0.0.1:5555",
114 /// SocketOptions::default().with_send_hwm(100),
115 /// ).await?;
116 /// # Ok(())
117 /// # }
118 /// ```
119 pub async fn connect_with_options(
120 endpoint: &str,
121 options: monocoque_core::options::SocketOptions,
122 ) -> io::Result<Self> {
123 let addr = parse_tcp_endpoint(endpoint)?;
124 let inner = InternalDealer::connect_with_options(addr, options).await?;
125 let sock = Self {
126 inner,
127 monitor: None,
128 };
129 sock.emit_event(SocketEvent::Connected(
130 monocoque_core::endpoint::Endpoint::Tcp(addr),
131 ));
132 Ok(sock)
133 }
134
135 /// Connect to a ZeroMQ peer via IPC (Unix domain sockets).
136 ///
137 /// Unix-only. Accepts IPC paths with or without `ipc://` prefix.
138 ///
139 /// # Example
140 ///
141 /// ```rust,no_run
142 /// # #[cfg(unix)]
143 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
144 /// use monocoque::zmq::DealerSocket;
145 ///
146 /// let mut socket = DealerSocket::connect_ipc("/tmp/dealer.sock").await?;
147 /// # Ok(())
148 /// # }
149 /// ```
150 #[cfg(unix)]
151 pub async fn connect_ipc(
152 path: &str,
153 ) -> io::Result<DealerSocket<monocoque_core::rt::UnixStream>> {
154 use std::path::PathBuf;
155
156 let clean_path = path.strip_prefix("ipc://").unwrap_or(path);
157 let ipc_path = PathBuf::from(clean_path);
158
159 let stream = monocoque_core::ipc::connect(&ipc_path).await?;
160 let sock = DealerSocket::from_unix_stream(stream).await?;
161 sock.emit_event(SocketEvent::Connected(
162 monocoque_core::endpoint::Endpoint::Ipc(ipc_path),
163 ));
164 Ok(sock)
165 }
166
167 /// Bind to an address and accept the first connection.
168 ///
169 /// This creates a server-side DEALER socket that accepts incoming connections.
170 /// Useful for broker patterns where workers (REP sockets) connect to a DEALER backend.
171 ///
172 /// # Returns
173 ///
174 /// A tuple of `(listener, socket)` where:
175 /// - `listener` can be used to accept additional connections
176 /// - `socket` is ready to send/receive with the first peer
177 ///
178 /// # Errors
179 ///
180 /// Returns an error if:
181 /// - The address is already in use
182 /// - Permission denied (e.g., binding to privileged port without root)
183 /// - Invalid address format
184 ///
185 /// # Example
186 ///
187 /// ```rust,no_run
188 /// use monocoque::zmq::DealerSocket;
189 ///
190 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
191 /// // Bind DEALER backend for worker connections
192 /// let (listener, socket) = DealerSocket::bind("127.0.0.1:5556").await?;
193 ///
194 /// // Use socket for first connection
195 /// // Accept more connections from listener if needed:
196 /// // let (stream, _) = listener.accept().await?;
197 /// // let socket2 = DealerSocket::from_tcp(stream).await?;
198 /// # Ok(())
199 /// # }
200 /// ```
201 pub async fn bind(
202 addr: impl monocoque_core::rt::ToSocketAddrs,
203 ) -> io::Result<(monocoque_core::rt::TcpListener, Self)> {
204 let listener = monocoque_core::rt::TcpListener::bind(addr).await?;
205 let (stream, _) = listener.accept().await?;
206 let socket = Self::from_tcp(stream).await?;
207 Ok((listener, socket))
208 }
209
210 /// Create a DEALER socket from a TCP stream with TCP_NODELAY enabled.
211 ///
212 /// This method automatically enables TCP_NODELAY for optimal performance,
213 /// preventing Nagle's algorithm from buffering small packets.
214 ///
215 /// Uses default buffer sizes (8KB) and socket options. For custom configuration,
216 /// use `with_options()`.
217 ///
218 /// # Example
219 ///
220 /// ```rust,no_run
221 /// use monocoque::zmq::DealerSocket;
222 /// use monocoque_core::rt::TcpStream;
223 ///
224 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
225 /// let stream = TcpStream::connect("127.0.0.1:5555").await?;
226 /// let socket = DealerSocket::from_tcp(stream).await?;
227 /// # Ok(())
228 /// # }
229 /// ```
230 pub async fn from_tcp(stream: TcpStream) -> io::Result<Self> {
231 Ok(Self {
232 inner: InternalDealer::from_tcp(stream).await?,
233 monitor: None,
234 })
235 }
236
237 /// Create a DEALER socket from a TCP stream with custom options.
238 ///
239 /// Provides full control over buffer sizes, HWM, timeouts, etc. through SocketOptions.
240 ///
241 /// # Examples
242 ///
243 /// ```rust,no_run
244 /// use monocoque::zmq::{DealerSocket, SocketOptions};
245 /// use monocoque_core::rt::TcpStream;
246 ///
247 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
248 /// let stream = TcpStream::connect("127.0.0.1:5555").await?;
249 ///
250 /// // Customize HWM only (uses default 8KB buffers)
251 /// let socket = DealerSocket::from_tcp_with_options(
252 /// stream,
253 /// SocketOptions::default().with_send_hwm(100)
254 /// ).await?;
255 /// # Ok(())
256 /// # }
257 /// ```
258 ///
259 /// ```rust,no_run
260 /// # use monocoque::zmq::{DealerSocket, SocketOptions};
261 /// # use monocoque_core::rt::TcpStream;
262 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
263 /// # let stream = TcpStream::connect("127.0.0.1:5555").await?;
264 /// // Customize both buffers and HWM
265 /// let socket = DealerSocket::from_tcp_with_options(
266 /// stream,
267 /// SocketOptions::default()
268 /// .with_buffer_sizes(4096, 4096) // 4KB buffers for low latency
269 /// .with_send_hwm(100)
270 /// ).await?;
271 /// # Ok(())
272 /// # }
273 /// ```
274 pub async fn from_tcp_with_options(
275 stream: TcpStream,
276 options: monocoque_core::options::SocketOptions,
277 ) -> io::Result<Self> {
278 Ok(Self {
279 inner: InternalDealer::from_tcp_with_options(stream, options).await?,
280 monitor: None,
281 })
282 }
283
284 /// Create a DEALER socket from any stream with custom options.
285 ///
286 /// This is the most flexible constructor - works with TCP, Unix, or in-memory streams.
287 /// Useful for testing with duplex streams.
288 ///
289 /// # Example
290 ///
291 /// ```rust,no_run
292 /// use monocoque::zmq::{DealerSocket, SocketOptions};
293 /// use monocoque_core::rt::TcpStream;
294 ///
295 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
296 /// let stream = TcpStream::connect("127.0.0.1:5555").await?;
297 /// let socket = DealerSocket::with_options(
298 /// stream,
299 /// SocketOptions::default().with_send_hwm(10)
300 /// ).await?;
301 /// # Ok(())
302 /// # }
303 /// ```
304 pub async fn with_options<Stream>(
305 stream: Stream,
306 options: monocoque_core::options::SocketOptions,
307 ) -> io::Result<DealerSocket<Stream>>
308 where
309 Stream: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
310 {
311 Ok(DealerSocket {
312 inner: InternalDealer::with_options(stream, options).await?,
313 monitor: None,
314 })
315 }
316
317 /// Try to reconnect to the stored endpoint.
318 ///
319 /// Only sockets built with [`connect`](Self::connect) or
320 /// [`connect_with_options`](Self::connect_with_options) store an endpoint;
321 /// one built from a raw stream has nothing to reconnect to.
322 ///
323 /// # Errors
324 ///
325 /// Returns an error if no endpoint is stored or the reconnection fails.
326 pub async fn try_reconnect(&mut self) -> io::Result<()> {
327 self.inner.try_reconnect().await
328 }
329
330 /// Send with automatic reconnection on network error.
331 ///
332 /// Retries up to `max_reconnect_attempts`, applying the configured backoff
333 /// between attempts.
334 ///
335 /// # Errors
336 ///
337 /// Returns an error if the send fails and reconnection does not recover it.
338 pub async fn send_with_reconnect(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
339 self.inner.send_with_reconnect(msg).await
340 }
341
342 /// Receive with automatic reconnection on EOF or network error.
343 ///
344 /// # Errors
345 ///
346 /// Returns an error if the receive fails and reconnection does not recover
347 /// it.
348 pub async fn recv_with_reconnect(&mut self) -> io::Result<Option<Vec<Bytes>>> {
349 self.inner.recv_with_reconnect().await
350 }
351}
352
353// Generic impl - works with any stream type
354impl<S> DealerSocket<S>
355where
356 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
357{
358 /// Enable monitoring for this socket.
359 ///
360 /// Returns a receiver for socket lifecycle events. Once enabled, the socket
361 /// will emit events like Connected, Disconnected, etc.
362 ///
363 /// # Example
364 ///
365 /// ```rust,no_run
366 /// use monocoque::zmq::{DealerSocket, SocketEvent};
367 ///
368 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
369 /// let mut socket = DealerSocket::connect("127.0.0.1:5555").await?;
370 /// let monitor = socket.monitor();
371 ///
372 /// // Spawn task to handle events
373 /// monocoque::rt::spawn_detached(async move {
374 /// while let Ok(event) = monitor.recv_async().await {
375 /// println!("Socket event: {}", event);
376 /// }
377 /// });
378 /// # Ok(())
379 /// # }
380 /// ```
381 pub fn monitor(&mut self) -> SocketMonitor {
382 let (sender, receiver) = create_monitor();
383 self.monitor = Some(sender);
384 receiver
385 }
386
387 /// Helper to emit monitoring events (if monitoring is enabled).
388 fn emit_event(&self, event: SocketEvent) {
389 if let Some(monitor) = &self.monitor {
390 monocoque_core::monitor::emit(monitor, event);
391 }
392 }
393
394 /// Send a multipart message.
395 ///
396 /// Messages are sent asynchronously - this returns immediately after
397 /// queuing the message for transmission.
398 ///
399 /// # Errors
400 ///
401 /// Returns an error if the underlying connection is closed or broken.
402 ///
403 /// # Example
404 ///
405 /// ```rust,no_run
406 /// # use monocoque::zmq::DealerSocket;
407 /// # use bytes::Bytes;
408 /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
409 /// socket.send(vec![
410 /// Bytes::from("part1"),
411 /// Bytes::from("part2"),
412 /// ]).await?;
413 /// # Ok(())
414 /// # }
415 /// ```
416 pub async fn send(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
417 channel_to_io_error(self.inner.send(msg).await)
418 }
419
420 /// Send a message to the internal buffer without flushing.
421 ///
422 /// Use this for batching multiple messages before a single flush.
423 /// Call `flush()` to send all buffered messages.
424 ///
425 /// # Example
426 ///
427 /// ```rust,no_run
428 /// # use monocoque::zmq::DealerSocket;
429 /// # use bytes::Bytes;
430 /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
431 /// // Batch 100 messages
432 /// for i in 0..100 {
433 /// socket.send_buffered(vec![Bytes::from(format!("msg {}", i))])?;
434 /// }
435 /// // Single I/O operation for all 100 messages
436 /// socket.flush().await?;
437 /// # Ok(())
438 /// # }
439 /// ```
440 pub fn send_buffered(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
441 channel_to_io_error(self.inner.send_buffered(msg))
442 }
443
444 /// Flush all buffered messages to the network.
445 ///
446 /// Sends all messages buffered by `send_buffered()` in a single I/O operation.
447 pub async fn flush(&mut self) -> io::Result<()> {
448 channel_to_io_error(self.inner.flush().await)
449 }
450
451 /// Send multiple messages in a single batch (convenience method).
452 ///
453 /// This is equivalent to calling `send_buffered()` for each message
454 /// followed by `flush()`, but more ergonomic.
455 ///
456 /// # Example
457 ///
458 /// ```rust,no_run
459 /// # use monocoque::zmq::DealerSocket;
460 /// # use bytes::Bytes;
461 /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
462 /// let messages = vec![
463 /// vec![Bytes::from("msg1")],
464 /// vec![Bytes::from("msg2")],
465 /// vec![Bytes::from("msg3")],
466 /// ];
467 /// socket.send_batch(&messages).await?;
468 /// # Ok(())
469 /// # }
470 /// ```
471 pub async fn send_batch(&mut self, messages: &[Vec<Bytes>]) -> io::Result<()> {
472 channel_to_io_error(self.inner.send_batch(messages).await)
473 }
474
475 /// Get the number of bytes currently buffered.
476 #[inline]
477 pub fn buffered_bytes(&self) -> usize {
478 self.inner.buffered_bytes()
479 }
480
481 /// Get the socket type.
482 ///
483 /// # ZeroMQ Compatibility
484 /// Get current socket events (read/write readiness).
485 ///
486 /// Returns a bitmask:
487 /// - `1` (POLLIN): Can receive without blocking
488 /// - `2` (POLLOUT): Can send without blocking
489 ///
490 /// # ZeroMQ Compatibility
491 ///
492 /// Corresponds to `ZMQ_EVENTS` (15) option.
493 pub fn events(&self) -> u32 {
494 self.inner.events()
495 }
496
497 /// Receive a multipart message.
498 ///
499 /// Returns `None` if the connection is closed.
500 ///
501 /// # Example
502 ///
503 /// ```rust,no_run
504 /// # use monocoque::zmq::DealerSocket;
505 /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
506 /// while let Ok(Some(msg)) = socket.recv().await {
507 /// println!("Received {} parts", msg.len());
508 /// }
509 /// # Ok(())
510 /// # }
511 /// ```
512 pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
513 self.inner.recv().await
514 }
515
516 /// Receive a message into a caller-provided buffer, reusing its allocation.
517 ///
518 /// Allocation-free counterpart to [`recv`](Self::recv). Returns `Ok(true)`
519 /// when a complete message was read into `out` (cleared on entry), or
520 /// `Ok(false)` on a closed connection.
521 pub async fn recv_into(&mut self, out: &mut Vec<Bytes>) -> io::Result<bool> {
522 self.inner.recv_into(out).await
523 }
524
525 /// Try to receive a message into `out` without a kernel read.
526 pub fn try_recv_into(&mut self, out: &mut Vec<Bytes>) -> io::Result<bool> {
527 self.inner.try_recv_into(out)
528 }
529
530 /// Receive a single-frame message, returning just its frame.
531 pub async fn recv_one(&mut self) -> io::Result<Option<Bytes>> {
532 self.inner.recv_one().await
533 }
534}
535
536impl<S> DealerSocket<S>
537where
538 S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
539{
540 /// Get the socket type.
541 ///
542 /// Always returns `SocketType::Dealer` for DEALER sockets.
543 ///
544 /// # ZeroMQ Compatibility
545 ///
546 /// Corresponds to `ZMQ_TYPE` (16) socket option.
547 #[inline]
548 pub const fn socket_type(&self) -> monocoque_zmtp::session::SocketType {
549 self.inner.socket_type()
550 }
551
552 /// Get the last connected endpoint as a string.
553 ///
554 /// Returns the endpoint this socket connected to, if any.
555 ///
556 /// # ZeroMQ Compatibility
557 ///
558 /// Corresponds to `ZMQ_LAST_ENDPOINT` (32) socket option.
559 #[inline]
560 pub fn last_endpoint(&self) -> Option<&str> {
561 self.inner.last_endpoint_string()
562 }
563
564 /// Check if more message frames are expected.
565 ///
566 /// For multipart messages, this indicates if more frames follow.
567 ///
568 /// # ZeroMQ Compatibility
569 ///
570 /// Corresponds to `ZMQ_RCVMORE` (13) socket option.
571 #[inline]
572 pub fn has_more(&self) -> bool {
573 self.inner.has_more()
574 }
575
576 /// Get mutable access to socket options.
577 ///
578 /// Allows runtime modification of socket behavior.
579 #[inline]
580 pub fn options_mut(&mut self) -> &mut monocoque_core::options::SocketOptions {
581 self.inner.options_mut()
582 }
583
584 /// Get immutable access to socket options.
585 #[inline]
586 pub const fn options(&self) -> &monocoque_core::options::SocketOptions {
587 self.inner.options()
588 }
589}
590
591// Unix-specific impl for IPC support
592#[cfg(unix)]
593impl DealerSocket<monocoque_core::rt::UnixStream> {
594 /// Create a DEALER socket from an existing Unix domain socket stream (IPC).
595 pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
596 Ok(Self {
597 inner: InternalDealer::new(stream).await?,
598 monitor: None,
599 })
600 }
601
602 /// Create a DEALER socket from an existing Unix stream with custom options.
603 pub async fn from_unix_stream_with_options(
604 stream: monocoque_core::rt::UnixStream,
605 options: monocoque_core::options::SocketOptions,
606 ) -> io::Result<Self> {
607 Ok(Self {
608 inner: InternalDealer::with_options(stream, options).await?,
609 monitor: None,
610 })
611 }
612}
613
614// Implement ProxySocket for the high-level DealerSocket wrapper. With native
615// async-fn-in-trait on ProxySocket this is a plain forward, no hand-written
616// Box::pin desugaring or per-call heap allocation.
617impl monocoque_zmtp::proxy::ProxySocket for DealerSocket<TcpStream> {
618 async fn recv_multipart(&mut self) -> io::Result<Option<Vec<Bytes>>> {
619 self.recv().await
620 }
621
622 async fn send_multipart(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
623 self.send(msg).await
624 }
625
626 fn socket_desc(&self) -> &'static str {
627 "DEALER"
628 }
629}