Skip to main content

grpc_core/
transport.rs

1//! Synchronous byte transports for blocking callers and externally driven event loops.
2//!
3//! Memory pipes are bounded and allocate their buffers at construction. Socket
4//! transports drive a calling-thread io_uring proactor in blocking mode.
5
6#[cfg(unix)]
7use std::os::unix::net::{SocketAddr as UnixSocketAddr, UnixListener, UnixStream};
8use std::{
9    io::{self, Read, Write},
10    net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs},
11    time::Duration,
12};
13
14#[path = "transport/memory.rs"]
15mod memory;
16#[path = "transport/proactor.rs"]
17mod proactor;
18pub use memory::MemoryPipe;
19
20/// Synchronous HTTP/2 backend used by transport and session implementations.
21pub use crate::http2;
22
23/// A bidirectional byte stream with explicit blocking and timeout controls.
24///
25/// Nonblocking operations return [`io::ErrorKind::WouldBlock`] when no progress
26/// is possible. A zero-length read result on a nonempty buffer indicates EOF.
27/// Timeout settings apply to blocking operations; zero durations are invalid.
28/// Callers must retry interrupted operations when using `read` or `write` directly.
29pub trait SyncTransport: Read + Write {
30    /// Select blocking or nonblocking I/O.
31    ///
32    /// # Errors
33    /// Returns an error if the underlying transport cannot change its mode.
34    fn set_nonblocking(&mut self, nonblocking: bool) -> io::Result<()>;
35
36    /// Set the blocking read deadline relative to the start of each operation.
37    ///
38    /// # Errors
39    /// Returns an error for a zero timeout or an OS configuration failure.
40    fn set_read_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()>;
41
42    /// Set the blocking write deadline relative to the start of each operation.
43    ///
44    /// # Errors
45    /// Returns an error for a zero timeout or an OS configuration failure.
46    fn set_write_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()>;
47
48    /// Returns the current blocking read timeout for temporary deadline overrides.
49    ///
50    /// # Errors
51    /// Returns an error if the current transport setting cannot be queried.
52    fn read_timeout(&self) -> io::Result<Option<Duration>>;
53
54    /// Returns the current blocking write timeout for temporary deadline overrides.
55    ///
56    /// # Errors
57    /// Returns an error if the current transport setting cannot be queried.
58    fn write_timeout(&self) -> io::Result<Option<Duration>>;
59
60    /// Return a diagnostic description of the remote endpoint.
61    ///
62    /// # Errors
63    /// Returns an error if the underlying peer address cannot be queried.
64    fn peer_endpoint(&self) -> io::Result<String>;
65}
66
67/// Socket settings applied before exposing a TCP transport.
68#[derive(Debug, Clone, Copy)]
69pub struct TcpTransportOptions {
70    /// Idle interval before TCP keepalive probes; `None` disables keepalive.
71    pub keepalive: Option<Duration>,
72    /// Requested kernel send buffer size, or the operating system default.
73    pub send_buffer_size: Option<usize>,
74    /// Requested kernel receive buffer size, or the operating system default.
75    pub recv_buffer_size: Option<usize>,
76    /// Whether reads and writes return immediately when they would block.
77    pub nonblocking: bool,
78}
79
80impl Default for TcpTransportOptions {
81    fn default() -> Self {
82        Self {
83            keepalive: Some(Duration::from_secs(60)),
84            send_buffer_size: None,
85            recv_buffer_size: None,
86            nonblocking: false,
87        }
88    }
89}
90
91/// A TCP byte stream with Nagle's algorithm disabled.
92#[derive(Debug)]
93pub struct TcpTransport(TcpStream);
94
95impl TcpTransport {
96    /// Connects to an endpoint with `TCP_NODELAY` enabled.
97    ///
98    /// # Errors
99    /// Returns an error if connection or socket configuration fails.
100    pub fn connect(address: impl ToSocketAddrs) -> io::Result<Self> {
101        Self::connect_with_options(address, TcpTransportOptions::default())
102    }
103
104    /// Wraps a stream and enables `TCP_NODELAY`.
105    ///
106    /// # Errors
107    /// Returns an error if applying the default socket settings fails.
108    pub fn from_std(stream: TcpStream) -> io::Result<Self> {
109        Self::from_std_with_options(stream, TcpTransportOptions::default())
110    }
111
112    /// Connects in blocking mode, then applies the requested socket settings.
113    ///
114    /// # Errors
115    /// Returns an error if connecting or applying any option fails.
116    pub fn connect_with_options(
117        address: impl ToSocketAddrs,
118        options: TcpTransportOptions,
119    ) -> io::Result<Self> {
120        let mut last = io::Error::new(io::ErrorKind::InvalidInput, "no socket addresses resolved");
121        for address in address.to_socket_addrs()? {
122            let result = (|| {
123                let socket = socket2::Socket::new(
124                    socket2::Domain::for_address(address),
125                    socket2::Type::STREAM,
126                    Some(socket2::Protocol::TCP),
127                )?;
128                proactor::connect(&socket, address.into())?;
129                Self::from_std_with_options(socket.into(), options)
130            })();
131            match result {
132                Ok(transport) => return Ok(transport),
133                Err(error) => last = error,
134            }
135        }
136        Err(last)
137    }
138
139    /// Applies socket settings, always enabling `TCP_NODELAY`.
140    ///
141    /// # Errors
142    /// Returns an error if applying any option fails.
143    pub fn from_std_with_options(
144        stream: TcpStream,
145        options: TcpTransportOptions,
146    ) -> io::Result<Self> {
147        for size in [options.send_buffer_size, options.recv_buffer_size]
148            .into_iter()
149            .flatten()
150        {
151            if size == 0 || size > i32::MAX as usize {
152                return Err(io::Error::new(
153                    io::ErrorKind::InvalidInput,
154                    "socket buffer size must fit a positive c_int",
155                ));
156            }
157        }
158        stream.set_nodelay(true)?;
159        let socket = socket2::SockRef::from(&stream);
160        socket.set_keepalive(options.keepalive.is_some())?;
161        if let Some(interval) = options.keepalive {
162            if interval.is_zero() {
163                return Err(io::Error::new(
164                    io::ErrorKind::InvalidInput,
165                    "keepalive interval must be positive",
166                ));
167            }
168            socket.set_tcp_keepalive(&socket2::TcpKeepalive::new().with_time(interval))?;
169        }
170        if let Some(size) = options.send_buffer_size {
171            socket.set_send_buffer_size(size)?;
172        }
173        if let Some(size) = options.recv_buffer_size {
174            socket.set_recv_buffer_size(size)?;
175        }
176        stream.set_nonblocking(options.nonblocking)?;
177        Ok(Self(stream))
178    }
179
180    fn endpoint(&self) -> io::Result<String> {
181        Ok(self.0.peer_addr()?.to_string())
182    }
183
184    /// Returns the connected peer address.
185    ///
186    /// # Errors
187    /// Returns an error if the socket address cannot be queried.
188    pub fn peer_addr(&self) -> io::Result<SocketAddr> {
189        self.0.peer_addr()
190    }
191
192    /// Returns the local socket address.
193    ///
194    /// # Errors
195    /// Returns an error if the socket address cannot be queried.
196    pub fn local_addr(&self) -> io::Result<SocketAddr> {
197        self.0.local_addr()
198    }
199}
200
201macro_rules! impl_stream_transport {
202    ($transport:ident, $stream:ty) => {
203        impl $transport {
204            /// Borrows the underlying stream.
205            pub fn get_ref(&self) -> &$stream {
206                &self.0
207            }
208
209            /// Returns ownership of the underlying stream.
210            pub fn into_inner(self) -> $stream {
211                self.0
212            }
213        }
214
215        impl io::Read for $transport {
216            fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
217                proactor::read(
218                    &self.0,
219                    buffer,
220                    self.0.read_timeout()?,
221                    proactor::is_nonblocking(&self.0)?,
222                )
223            }
224        }
225
226        impl io::Write for $transport {
227            fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
228                proactor::write(
229                    &self.0,
230                    buffer,
231                    self.0.write_timeout()?,
232                    proactor::is_nonblocking(&self.0)?,
233                )
234            }
235
236            fn flush(&mut self) -> io::Result<()> {
237                Ok(())
238            }
239        }
240
241        impl SyncTransport for $transport {
242            fn set_nonblocking(&mut self, nonblocking: bool) -> io::Result<()> {
243                self.0.set_nonblocking(nonblocking)
244            }
245
246            fn set_read_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
247                self.0.set_read_timeout(timeout)
248            }
249
250            fn set_write_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
251                self.0.set_write_timeout(timeout)
252            }
253
254            fn read_timeout(&self) -> io::Result<Option<Duration>> {
255                self.0.read_timeout()
256            }
257
258            fn write_timeout(&self) -> io::Result<Option<Duration>> {
259                self.0.write_timeout()
260            }
261
262            fn peer_endpoint(&self) -> io::Result<String> {
263                self.endpoint()
264            }
265        }
266    };
267}
268
269impl_stream_transport!(TcpTransport, TcpStream);
270
271/// A TCP listener whose accepted streams enable `TCP_NODELAY`.
272#[derive(Debug)]
273pub struct TcpTransportListener(TcpListener);
274
275impl TcpTransportListener {
276    /// Binds a TCP listener.
277    ///
278    /// # Errors
279    /// Returns an error if address resolution or binding fails.
280    pub fn bind(address: impl ToSocketAddrs) -> io::Result<Self> {
281        let mut last_error =
282            io::Error::new(io::ErrorKind::InvalidInput, "no socket addresses resolved");
283        for address in address.to_socket_addrs()? {
284            let result = (|| {
285                let socket = socket2::Socket::new(
286                    socket2::Domain::for_address(address),
287                    socket2::Type::STREAM,
288                    Some(socket2::Protocol::TCP),
289                )?;
290                socket.set_reuse_address(true)?;
291                socket.bind(&address.into())?;
292                socket.listen(128)?;
293                Ok(Self(socket.into()))
294            })();
295            match result {
296                Ok(listener) => return Ok(listener),
297                Err(error) => last_error = error,
298            }
299        }
300        Err(last_error)
301    }
302}
303
304/// A Unix domain byte stream.
305#[cfg(unix)]
306#[derive(Debug)]
307pub struct UnixTransport(UnixStream);
308
309#[cfg(unix)]
310impl UnixTransport {
311    /// Connects to a Unix domain socket path.
312    ///
313    /// # Errors
314    /// Returns an error if the connection fails.
315    pub fn connect(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
316        let address = socket2::SockAddr::unix(path)?;
317        let socket = socket2::Socket::new(socket2::Domain::UNIX, socket2::Type::STREAM, None)?;
318        proactor::connect(&socket, address)?;
319        Self::from_std(socket.into())
320    }
321
322    /// Wraps an existing Unix domain stream.
323    ///
324    /// # Errors
325    /// This operation currently always succeeds.
326    pub fn from_std(stream: UnixStream) -> io::Result<Self> {
327        Ok(Self(stream))
328    }
329
330    fn endpoint(&self) -> io::Result<String> {
331        let address = self.0.peer_addr()?;
332        Ok(match address.as_pathname() {
333            Some(path) => format!("unix:{}", path.display()),
334            None => format!("unix:{address:?}"),
335        })
336    }
337}
338
339#[cfg(unix)]
340impl_stream_transport!(UnixTransport, UnixStream);
341
342/// A Unix domain listener. The caller owns cleanup of its socket path.
343#[cfg(unix)]
344#[derive(Debug)]
345pub struct UnixTransportListener(UnixListener);
346
347#[cfg(unix)]
348impl UnixTransportListener {
349    /// Binds a Unix domain socket path.
350    ///
351    /// # Errors
352    /// Returns an error if the path cannot be bound.
353    pub fn bind(path: impl AsRef<std::path::Path>) -> io::Result<Self> {
354        Ok(Self(UnixListener::bind(path)?))
355    }
356}
357
358macro_rules! impl_listener {
359    ($wrapper:ident, $listener:ty, $stream:ty, $transport:ident, $address:ty) => {
360        impl $wrapper {
361            /// Wraps an existing listener.
362            pub fn from_std(listener: $listener) -> Self {
363                Self(listener)
364            }
365
366            /// Accepts and configures a connected transport.
367            ///
368            /// # Errors
369            /// Returns an error if accepting or configuring the stream fails.
370            pub fn accept(&self) -> io::Result<($transport, $address)> {
371                let fd = proactor::accept(&self.0)?;
372                let stream = <$stream>::from(fd);
373                let address = stream.peer_addr()?;
374                Ok(($transport::from_std(stream)?, address))
375            }
376
377            /// Returns the bound socket address.
378            ///
379            /// # Errors
380            /// Returns an error if the address cannot be queried.
381            pub fn local_addr(&self) -> io::Result<$address> {
382                self.0.local_addr()
383            }
384
385            /// Configures whether accepting returns immediately.
386            ///
387            /// # Errors
388            /// Returns an error if the operating system rejects the setting.
389            pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
390                self.0.set_nonblocking(nonblocking)
391            }
392        }
393    };
394}
395impl_listener!(
396    TcpTransportListener,
397    TcpListener,
398    TcpStream,
399    TcpTransport,
400    SocketAddr
401);
402#[cfg(unix)]
403impl_listener!(
404    UnixTransportListener,
405    UnixListener,
406    UnixStream,
407    UnixTransport,
408    UnixSocketAddr
409);
410
411#[cfg(test)]
412#[path = "../tests/unit/transport.rs"]
413mod tests;