Skip to main content

grpc_core/transport/
memory.rs

1//! Exact-capacity SPSC byte lanes with completion-driven peer notification.
2
3use std::{
4    cell::UnsafeCell,
5    io::{self, Read, Write},
6    net::Shutdown,
7    os::unix::net::UnixStream,
8    sync::{
9        Arc,
10        atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering},
11    },
12    time::{Duration, Instant},
13};
14
15use super::{SyncTransport, proactor};
16
17#[repr(align(64))]
18struct Cursor(AtomicUsize);
19
20struct MemoryLane {
21    bytes: Box<[UnsafeCell<u8>]>,
22    head: Cursor,
23    tail: Cursor,
24    reader_alive: AtomicBool,
25    writer_alive: AtomicBool,
26    error: AtomicI32,
27    #[cfg(test)]
28    waiting: Arc<AtomicBool>,
29}
30
31// SAFETY: Each lane has exactly one producer and one consumer, accessed through
32// separate endpoints requiring &mut self. Release publication of tail/head and
33// acquire observation transfer disjoint byte ranges between those two owners.
34// No byte references escape; neither side accesses the other's in-flight range.
35unsafe impl Sync for MemoryLane {}
36
37impl MemoryLane {
38    fn new(capacity: usize) -> io::Result<Self> {
39        let mut bytes = Vec::new();
40        bytes
41            .try_reserve_exact(capacity)
42            .map_err(io::Error::other)?;
43        bytes.resize_with(capacity, || UnsafeCell::new(0));
44        Ok(Self {
45            bytes: bytes.into_boxed_slice(),
46            head: Cursor(AtomicUsize::new(0)),
47            tail: Cursor(AtomicUsize::new(0)),
48            reader_alive: AtomicBool::new(true),
49            writer_alive: AtomicBool::new(true),
50            error: AtomicI32::new(0),
51            #[cfg(test)]
52            waiting: Arc::new(AtomicBool::new(false)),
53        })
54    }
55
56    fn distance(&self, head: usize, tail: usize) -> usize {
57        if tail >= head {
58            tail - head
59        } else {
60            self.bytes.len() * 2 - head + tail
61        }
62    }
63
64    fn advance(&self, cursor: usize, count: usize) -> usize {
65        let until_wrap = self.bytes.len() * 2 - cursor;
66        if count >= until_wrap {
67            count - until_wrap
68        } else {
69            cursor + count
70        }
71    }
72
73    fn read(&self, destination: &mut [u8]) -> usize {
74        let head = self.head.0.load(Ordering::Relaxed);
75        let tail = self.tail.0.load(Ordering::Acquire);
76        let count = destination.len().min(self.distance(head, tail));
77        if count == 0 {
78            return 0;
79        }
80        let start = head % self.bytes.len();
81        let first = count.min(self.bytes.len() - start);
82        // SAFETY: Acquired tail publishes these initialized bytes exclusively to
83        // this consumer. The producer cannot reuse them before the head release.
84        // Both wrapped ranges lie in the allocation and destination is external.
85        unsafe {
86            std::ptr::copy_nonoverlapping(self.bytes[start].get(), destination.as_mut_ptr(), first);
87            std::ptr::copy_nonoverlapping(
88                self.bytes[0].get(),
89                destination.as_mut_ptr().add(first),
90                count - first,
91            );
92        }
93        self.head
94            .0
95            .store(self.advance(head, count), Ordering::Release);
96        count
97    }
98
99    fn write(&self, source: &[u8]) -> usize {
100        let tail = self.tail.0.load(Ordering::Relaxed);
101        let head = self.head.0.load(Ordering::Acquire);
102        let count = source
103            .len()
104            .min(self.bytes.len() - self.distance(head, tail));
105        if count == 0 {
106            return 0;
107        }
108        let start = tail % self.bytes.len();
109        let first = count.min(self.bytes.len() - start);
110        // SAFETY: Acquired head transfers these free bytes exclusively to this
111        // producer. No consumer can access them before the tail release; both
112        // wrapped ranges are in the allocation and source is external.
113        unsafe {
114            std::ptr::copy_nonoverlapping(source.as_ptr(), self.bytes[start].get(), first);
115            std::ptr::copy_nonoverlapping(
116                source.as_ptr().add(first),
117                self.bytes[0].get(),
118                count - first,
119            );
120        }
121        self.tail
122            .0
123            .store(self.advance(tail, count), Ordering::Release);
124        count
125    }
126
127    fn check_error(&self) -> io::Result<()> {
128        match self.error.load(Ordering::Acquire) {
129            0 => Ok(()),
130            errno => Err(io::Error::from_raw_os_error(errno)),
131        }
132    }
133}
134
135/// Bounded full-duplex SPSC byte stream with exact per-direction capacity.
136/// Reads drain published bytes before reporting EOF after the peer is dropped.
137/// Endpoints may move between threads after each completed operation.
138pub struct MemoryPipe {
139    incoming: Arc<MemoryLane>,
140    outgoing: Arc<MemoryLane>,
141    wake: UnixStream,
142    nonblocking: bool,
143    read_timeout: Option<Duration>,
144    write_timeout: Option<Duration>,
145}
146
147impl MemoryPipe {
148    /// Creates a pair with 64 KiB of preallocated capacity in each direction.
149    ///
150    /// # Panics
151    /// Panics if buffers or notification sockets cannot be created.
152    pub fn pair() -> (Self, Self) {
153        Self::with_capacity(64 * 1024).expect("cannot create default memory pipe")
154    }
155
156    /// Creates connected endpoints with an exact byte capacity in each direction.
157    ///
158    /// # Errors
159    /// Returns invalid capacity, allocation, or notification socket errors.
160    pub fn with_capacity(capacity: usize) -> io::Result<(Self, Self)> {
161        if capacity == 0 || capacity > isize::MAX as usize {
162            return Err(io::Error::new(
163                io::ErrorKind::InvalidInput,
164                "memory capacity must be positive and fit an allocation",
165            ));
166        }
167        let incoming = Arc::new(MemoryLane::new(capacity)?);
168        let outgoing = Arc::new(MemoryLane::new(capacity)?);
169        let (first, second) = UnixStream::pair()?;
170        let endpoint = |incoming, outgoing, wake| Self {
171            incoming,
172            outgoing,
173            wake,
174            nonblocking: false,
175            read_timeout: None,
176            write_timeout: None,
177        };
178        Ok((
179            endpoint(Arc::clone(&incoming), Arc::clone(&outgoing), first),
180            endpoint(outgoing, incoming, second),
181        ))
182    }
183
184    fn validate_timeout(timeout: Option<Duration>) -> io::Result<()> {
185        if timeout.is_some_and(|duration| {
186            duration.is_zero() || Instant::now().checked_add(duration).is_none()
187        }) {
188            return Err(io::Error::new(
189                io::ErrorKind::InvalidInput,
190                "timeout must be nonzero and fit the monotonic clock",
191            ));
192        }
193        Ok(())
194    }
195
196    fn notify(&self) {
197        loop {
198            match proactor::write(&self.wake, &[1], None, true) {
199                Ok(1) => return,
200                // A full socket already carries a wakeup for the peer.
201                Err(error) if error.kind() == io::ErrorKind::WouldBlock => return,
202                Err(error) if error.kind() == io::ErrorKind::Interrupted => continue,
203                Err(error)
204                    if matches!(
205                        error.kind(),
206                        io::ErrorKind::BrokenPipe | io::ErrorKind::ConnectionReset
207                    ) && !self.outgoing.reader_alive.load(Ordering::Acquire) =>
208                {
209                    return;
210                }
211                result => {
212                    // Bytes already committed cannot be retracted or reported as
213                    // unaccepted. Publish a terminal error for the next operation
214                    // and wake a blocked peer through notification-channel EOF.
215                    let errno = match result {
216                        Err(error) => error.raw_os_error().unwrap_or(libc::EIO),
217                        Ok(_) => libc::EIO,
218                    };
219                    self.incoming.error.store(errno, Ordering::Release);
220                    self.outgoing.error.store(errno, Ordering::Release);
221                    if let Err(error) = self.wake.shutdown(Shutdown::Both) {
222                        self.incoming
223                            .error
224                            .store(error.raw_os_error().unwrap_or(libc::EIO), Ordering::Release);
225                        self.outgoing
226                            .error
227                            .store(error.raw_os_error().unwrap_or(libc::EIO), Ordering::Release);
228                    }
229                    return;
230                }
231            }
232        }
233    }
234
235    fn drain_notification(&self) -> io::Result<()> {
236        match proactor::read(&self.wake, &mut [0; 256], None, true) {
237            Ok(_) => Ok(()),
238            Err(error)
239                if matches!(
240                    error.kind(),
241                    io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted
242                ) =>
243            {
244                Ok(())
245            }
246            Err(error)
247                if matches!(
248                    error.kind(),
249                    io::ErrorKind::ConnectionReset | io::ErrorKind::BrokenPipe
250                ) && !self.incoming.writer_alive.load(Ordering::Acquire) =>
251            {
252                Ok(())
253            }
254            Err(error) => Err(error),
255        }
256    }
257
258    fn wait(&self, lane: &MemoryLane, deadline: Option<Instant>) -> io::Result<()> {
259        let remaining = deadline
260            .map(|deadline| {
261                deadline
262                    .checked_duration_since(Instant::now())
263                    .ok_or_else(|| io::Error::from(io::ErrorKind::TimedOut))
264            })
265            .transpose()?;
266        #[cfg(test)]
267        lane.waiting.store(true, Ordering::Release);
268        #[cfg(not(test))]
269        lane.check_error()?;
270        proactor::readable(&self.wake, remaining)
271    }
272
273    #[cfg(test)]
274    pub(super) fn wait_flag(&self, writing: bool) -> Arc<AtomicBool> {
275        Arc::clone(
276            &if writing {
277                &self.outgoing
278            } else {
279                &self.incoming
280            }
281            .waiting,
282        )
283    }
284}
285
286impl Read for MemoryPipe {
287    fn read(&mut self, destination: &mut [u8]) -> io::Result<usize> {
288        if destination.is_empty() {
289            return Ok(0);
290        }
291        let deadline = self
292            .read_timeout
293            .and_then(|timeout| Instant::now().checked_add(timeout));
294        let mut drained = false;
295        loop {
296            let count = self.incoming.read(destination);
297            if count > 0 {
298                self.notify();
299                return Ok(count);
300            }
301            if !self.incoming.writer_alive.load(Ordering::Acquire) {
302                // Recheck after the release/acquire edge from peer Drop so the
303                // final published bytes are drained even if the first tail was stale.
304                let count = self.incoming.read(destination);
305                if count > 0 {
306                    self.notify();
307                }
308                return Ok(count);
309            }
310            self.incoming.check_error()?;
311            if self.nonblocking {
312                return Err(io::ErrorKind::WouldBlock.into());
313            }
314            if drained {
315                self.wait(&self.incoming, deadline)?;
316            }
317            self.drain_notification()?;
318            // Always recheck shared state after draining, before registering a wait.
319            drained = true;
320        }
321    }
322}
323
324impl Write for MemoryPipe {
325    fn write(&mut self, source: &[u8]) -> io::Result<usize> {
326        if source.is_empty() {
327            return Ok(0);
328        }
329        let deadline = self
330            .write_timeout
331            .and_then(|timeout| Instant::now().checked_add(timeout));
332        let mut drained = false;
333        loop {
334            if !self.outgoing.reader_alive.load(Ordering::Acquire) {
335                return Err(io::ErrorKind::BrokenPipe.into());
336            }
337            self.outgoing.check_error()?;
338            let count = self.outgoing.write(source);
339            if count > 0 {
340                self.notify();
341                return Ok(count);
342            }
343            if self.nonblocking {
344                return Err(io::ErrorKind::WouldBlock.into());
345            }
346            if drained {
347                self.wait(&self.outgoing, deadline)?;
348            }
349            self.drain_notification()?;
350            drained = true;
351        }
352    }
353
354    fn flush(&mut self) -> io::Result<()> {
355        Ok(())
356    }
357}
358
359impl SyncTransport for MemoryPipe {
360    fn set_nonblocking(&mut self, nonblocking: bool) -> io::Result<()> {
361        self.nonblocking = nonblocking;
362        Ok(())
363    }
364    fn set_read_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
365        Self::validate_timeout(timeout)?;
366        self.read_timeout = timeout;
367        Ok(())
368    }
369    fn set_write_timeout(&mut self, timeout: Option<Duration>) -> io::Result<()> {
370        Self::validate_timeout(timeout)?;
371        self.write_timeout = timeout;
372        Ok(())
373    }
374    fn read_timeout(&self) -> io::Result<Option<Duration>> {
375        Ok(self.read_timeout)
376    }
377    fn write_timeout(&self) -> io::Result<Option<Duration>> {
378        Ok(self.write_timeout)
379    }
380    fn peer_endpoint(&self) -> io::Result<String> {
381        Ok("memory://peer".into())
382    }
383}
384
385impl Drop for MemoryPipe {
386    fn drop(&mut self) {
387        self.incoming.reader_alive.store(false, Ordering::Release);
388        self.outgoing.writer_alive.store(false, Ordering::Release);
389        // Closing the notification endpoint wakes peer waits even if notification
390        // submission is unavailable during unwinding or thread-local destruction.
391    }
392}