Skip to main content

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
318// Generic impl - works with any stream type
319impl<S> DealerSocket<S>
320where
321    S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
322{
323    /// Enable monitoring for this socket.
324    ///
325    /// Returns a receiver for socket lifecycle events. Once enabled, the socket
326    /// will emit events like Connected, Disconnected, etc.
327    ///
328    /// # Example
329    ///
330    /// ```rust,no_run
331    /// use monocoque::zmq::{DealerSocket, SocketEvent};
332    ///
333    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
334    /// let mut socket = DealerSocket::connect("127.0.0.1:5555").await?;
335    /// let monitor = socket.monitor();
336    ///
337    /// // Spawn task to handle events
338    /// monocoque::rt::spawn_detached(async move {
339    ///     while let Ok(event) = monitor.recv_async().await {
340    ///         println!("Socket event: {}", event);
341    ///     }
342    /// });
343    /// # Ok(())
344    /// # }
345    /// ```
346    pub fn monitor(&mut self) -> SocketMonitor {
347        let (sender, receiver) = create_monitor();
348        self.monitor = Some(sender);
349        receiver
350    }
351
352    /// Helper to emit monitoring events (if monitoring is enabled).
353    fn emit_event(&self, event: SocketEvent) {
354        if let Some(monitor) = &self.monitor {
355            monocoque_core::monitor::emit(monitor, event);
356        }
357    }
358
359    /// Send a multipart message.
360    ///
361    /// Messages are sent asynchronously - this returns immediately after
362    /// queuing the message for transmission.
363    ///
364    /// # Errors
365    ///
366    /// Returns an error if the underlying connection is closed or broken.
367    ///
368    /// # Example
369    ///
370    /// ```rust,no_run
371    /// # use monocoque::zmq::DealerSocket;
372    /// # use bytes::Bytes;
373    /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
374    /// socket.send(vec![
375    ///     Bytes::from("part1"),
376    ///     Bytes::from("part2"),
377    /// ]).await?;
378    /// # Ok(())
379    /// # }
380    /// ```
381    pub async fn send(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
382        channel_to_io_error(self.inner.send(msg).await)
383    }
384
385    /// Send a message to the internal buffer without flushing.
386    ///
387    /// Use this for batching multiple messages before a single flush.
388    /// Call `flush()` to send all buffered messages.
389    ///
390    /// # Example
391    ///
392    /// ```rust,no_run
393    /// # use monocoque::zmq::DealerSocket;
394    /// # use bytes::Bytes;
395    /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
396    /// // Batch 100 messages
397    /// for i in 0..100 {
398    ///     socket.send_buffered(vec![Bytes::from(format!("msg {}", i))])?;
399    /// }
400    /// // Single I/O operation for all 100 messages
401    /// socket.flush().await?;
402    /// # Ok(())
403    /// # }
404    /// ```
405    pub fn send_buffered(&mut self, msg: Vec<Bytes>) -> io::Result<()> {
406        channel_to_io_error(self.inner.send_buffered(msg))
407    }
408
409    /// Flush all buffered messages to the network.
410    ///
411    /// Sends all messages buffered by `send_buffered()` in a single I/O operation.
412    pub async fn flush(&mut self) -> io::Result<()> {
413        channel_to_io_error(self.inner.flush().await)
414    }
415
416    /// Send multiple messages in a single batch (convenience method).
417    ///
418    /// This is equivalent to calling `send_buffered()` for each message
419    /// followed by `flush()`, but more ergonomic.
420    ///
421    /// # Example
422    ///
423    /// ```rust,no_run
424    /// # use monocoque::zmq::DealerSocket;
425    /// # use bytes::Bytes;
426    /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
427    /// let messages = vec![
428    ///     vec![Bytes::from("msg1")],
429    ///     vec![Bytes::from("msg2")],
430    ///     vec![Bytes::from("msg3")],
431    /// ];
432    /// socket.send_batch(&messages).await?;
433    /// # Ok(())
434    /// # }
435    /// ```
436    pub async fn send_batch(&mut self, messages: &[Vec<Bytes>]) -> io::Result<()> {
437        channel_to_io_error(self.inner.send_batch(messages).await)
438    }
439
440    /// Get the number of bytes currently buffered.
441    #[inline]
442    pub fn buffered_bytes(&self) -> usize {
443        self.inner.buffered_bytes()
444    }
445
446    /// Get the socket type.
447    ///
448    /// # ZeroMQ Compatibility
449    /// Get current socket events (read/write readiness).
450    ///
451    /// Returns a bitmask:
452    /// - `1` (POLLIN): Can receive without blocking
453    /// - `2` (POLLOUT): Can send without blocking
454    ///
455    /// # ZeroMQ Compatibility
456    ///
457    /// Corresponds to `ZMQ_EVENTS` (15) option.
458    pub fn events(&self) -> u32 {
459        self.inner.events()
460    }
461
462    /// Receive a multipart message.
463    ///
464    /// Returns `None` if the connection is closed.
465    ///
466    /// # Example
467    ///
468    /// ```rust,no_run
469    /// # use monocoque::zmq::DealerSocket;
470    /// # async fn example(mut socket: DealerSocket) -> Result<(), Box<dyn std::error::Error>> {
471    /// while let Ok(Some(msg)) = socket.recv().await {
472    ///     println!("Received {} parts", msg.len());
473    /// }
474    /// # Ok(())
475    /// # }
476    /// ```
477    pub async fn recv(&mut self) -> io::Result<Option<Vec<Bytes>>> {
478        self.inner.recv().await
479    }
480}
481
482impl<S> DealerSocket<S>
483where
484    S: compio_io::AsyncRead + compio_io::AsyncWrite + Unpin,
485{
486    /// Get the socket type.
487    ///
488    /// Always returns `SocketType::Dealer` for DEALER sockets.
489    ///
490    /// # ZeroMQ Compatibility
491    ///
492    /// Corresponds to `ZMQ_TYPE` (16) socket option.
493    #[inline]
494    pub const fn socket_type(&self) -> monocoque_zmtp::session::SocketType {
495        self.inner.socket_type()
496    }
497
498    /// Get the last connected endpoint as a string.
499    ///
500    /// Returns the endpoint this socket connected to, if any.
501    ///
502    /// # ZeroMQ Compatibility
503    ///
504    /// Corresponds to `ZMQ_LAST_ENDPOINT` (32) socket option.
505    #[inline]
506    pub fn last_endpoint(&self) -> Option<&str> {
507        self.inner.last_endpoint_string()
508    }
509
510    /// Check if more message frames are expected.
511    ///
512    /// For multipart messages, this indicates if more frames follow.
513    ///
514    /// # ZeroMQ Compatibility
515    ///
516    /// Corresponds to `ZMQ_RCVMORE` (13) socket option.
517    #[inline]
518    pub fn has_more(&self) -> bool {
519        self.inner.has_more()
520    }
521
522    /// Get mutable access to socket options.
523    ///
524    /// Allows runtime modification of socket behavior.
525    #[inline]
526    pub fn options_mut(&mut self) -> &mut monocoque_core::options::SocketOptions {
527        self.inner.options_mut()
528    }
529
530    /// Get immutable access to socket options.
531    #[inline]
532    pub const fn options(&self) -> &monocoque_core::options::SocketOptions {
533        self.inner.options()
534    }
535}
536
537// Unix-specific impl for IPC support
538#[cfg(unix)]
539impl DealerSocket<monocoque_core::rt::UnixStream> {
540    /// Create a DEALER socket from an existing Unix domain socket stream (IPC).
541    pub async fn from_unix_stream(stream: monocoque_core::rt::UnixStream) -> io::Result<Self> {
542        Ok(Self {
543            inner: InternalDealer::new(stream).await?,
544            monitor: None,
545        })
546    }
547
548    /// Create a DEALER socket from an existing Unix stream with custom options.
549    pub async fn from_unix_stream_with_options(
550        stream: monocoque_core::rt::UnixStream,
551        options: monocoque_core::options::SocketOptions,
552    ) -> io::Result<Self> {
553        Ok(Self {
554            inner: InternalDealer::with_options(stream, options).await?,
555            monitor: None,
556        })
557    }
558}
559
560// Implement ProxySocket for the high-level DealerSocket wrapper
561impl monocoque_zmtp::proxy::ProxySocket for DealerSocket<TcpStream> {
562    fn recv_multipart<'life0, 'async_trait>(
563        &'life0 mut self,
564    ) -> ::core::pin::Pin<
565        Box<dyn ::core::future::Future<Output = io::Result<Option<Vec<Bytes>>>> + 'async_trait>,
566    >
567    where
568        'life0: 'async_trait,
569        Self: 'async_trait,
570    {
571        Box::pin(async move { self.recv().await })
572    }
573
574    fn send_multipart<'life0, 'async_trait>(
575        &'life0 mut self,
576        msg: Vec<Bytes>,
577    ) -> ::core::pin::Pin<Box<dyn ::core::future::Future<Output = io::Result<()>> + 'async_trait>>
578    where
579        'life0: 'async_trait,
580        Self: 'async_trait,
581    {
582        Box::pin(async move { self.send(msg).await })
583    }
584
585    fn socket_desc(&self) -> &'static str {
586        "DEALER"
587    }
588}