Skip to main content

netstack3_base/socket/
sndbuf.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Types and traits providing send buffer management for netstack sockets.
6
7use derivative::Derivative;
8use netstack3_sync::Mutex;
9use netstack3_sync::atomic::{AtomicIsize, Ordering};
10
11use crate::BindingsSyncTypes;
12use crate::num::PositiveIsize;
13
14/// Tracks the available send buffer space for a socket.
15#[derive(Derivative)]
16#[derivative(Debug(bound = "L: core::fmt::Debug"))]
17pub struct SendBufferTracking<L, BT: BindingsSyncTypes> {
18    /// Keeps track of how many bytes are available in the send buffer.
19    ///
20    /// Due to overcommit, this is allowed to become negative. Hence, whenever
21    /// `available_bytes changes`, the only thing to be observed in terms of
22    /// signaling the listener is whether the zero is crossed (from negative to
23    /// positive and vice-versa) as part of adding or subtracting in flight
24    /// bytes.
25    available_bytes: AtomicIsize,
26    inner: Mutex<Inner<L>, BT::Mutex>,
27}
28
29impl<L, BT: BindingsSyncTypes> SendBufferTracking<L, BT> {
30    /// Creates a new `SendBufferTracking` with the initial available send
31    /// buffer space `capacity` and a writable listener `listener`.
32    pub fn new(capacity: PositiveIsize, listener: L) -> Self {
33        Self {
34            available_bytes: AtomicIsize::new(capacity.into()),
35            inner: Mutex::new(Inner {
36                listener,
37                // Listeners must assume the socket is writable from creation.
38                // See SocketWritableListener.
39                notified_state: true,
40                capacity,
41            }),
42        }
43    }
44
45    /// Calls the callback `f` with a mutable reference to the listener.
46    #[cfg(any(test, feature = "testutils"))]
47    pub fn with_listener<R, F: FnOnce(&mut L) -> R>(&self, f: F) -> R {
48        f(&mut self.inner.lock().listener)
49    }
50}
51
52impl<L: SocketWritableListener, BT: BindingsSyncTypes> SendBufferTracking<L, BT> {
53    /// Acquires `bytes` for send with this `SendBufferTracking` instance.
54    pub fn acquire(&self, bytes: PositiveIsize) -> Result<SendBufferSpace, SendBufferFullError> {
55        let bytes_isize = bytes.into();
56        // Ordering: Paired with Release in return_and_notify.
57        let prev = self.available_bytes.fetch_sub(bytes_isize, Ordering::Acquire);
58        // If we had enough bytes available allow the write.
59        if prev > bytes_isize {
60            return Ok(SendBufferSpace(bytes));
61        }
62        // Turns out we didn't have enough space. Place the bytes back in and
63        // potentially notify, but return an error.
64        //
65        // The regular return flow here is necessary because we could be racing
66        // with other agents acquiring and returning space, and this return
67        // could be the one that flips the socket back into writable.
68        if prev <= 0 {
69            self.return_and_notify(bytes_isize);
70            return Err(SendBufferFullError);
71        }
72
73        // prev is in the interval (0, bytes] meaning this allocation crossed a
74        // threshold. Notify the listener before returning.
75        self.notify();
76        Ok(SendBufferSpace(bytes))
77    }
78
79    /// Releases `space` back to this `SendBufferTracking` instance.
80    pub fn release(&self, space: SendBufferSpace) {
81        let SendBufferSpace(delta) = &space;
82        self.return_and_notify((*delta).into());
83        // Prevent drop panic for send buffer space.
84        core::mem::forget(space);
85    }
86
87    fn return_and_notify(&self, delta: isize) {
88        // Ordering: Paired with Acquire in acquire.
89        let prev = self.available_bytes.fetch_add(delta, Ordering::Release);
90        if prev <= 0 && prev + delta > 0 {
91            self.notify();
92        }
93    }
94
95    fn notify(&self) {
96        let Self { available_bytes, inner } = self;
97        let mut inner = inner.lock();
98        Self::notify_locked(available_bytes, &mut inner);
99    }
100
101    fn notify_locked(available_bytes: &AtomicIsize, inner: &mut Inner<L>) {
102        let Inner { listener, notified_state, capacity: _ } = inner;
103        // Read the available buffer space under lock and change the
104        // notification state accordingly. Relaxed ordering is okay here because
105        // the lock is guaranteeing the ordering.
106        let new_writable = available_bytes.load(Ordering::Relaxed) > 0;
107        if core::mem::replace(notified_state, new_writable) != new_writable {
108            listener.on_writable_changed(new_writable);
109        }
110    }
111
112    /// Returns the tracker's capacity.
113    pub fn capacity(&self) -> PositiveIsize {
114        self.inner.lock().capacity
115    }
116
117    /// Returns the currently available buffer space.
118    pub fn available(&self) -> Option<PositiveIsize> {
119        PositiveIsize::new(self.available_bytes.load(Ordering::Relaxed))
120    }
121
122    /// Updates the tracker's capacity to `new_capacity`.
123    ///
124    /// Note that upon changing the capacity the socket's writable state may
125    /// change.
126    pub fn set_capacity(&self, new_capacity: PositiveIsize) {
127        let Self { available_bytes, inner } = self;
128        let mut inner = inner.lock();
129        let Inner { listener: _, notified_state: _, capacity } = &mut *inner;
130        let old = core::mem::replace(capacity, new_capacity);
131        let delta = new_capacity.get() - old.get();
132        // Ordering: We're already under lock here and we want to ensure we're
133        // not reordering with the relaxed load in notify_locked.
134        let _: isize = available_bytes.fetch_add(delta, Ordering::AcqRel);
135        // We already have the lock, check if we need to notify regardless of
136        // whether we crossed zero or not.
137        Self::notify_locked(available_bytes, &mut inner);
138    }
139}
140
141#[derive(Debug)]
142struct Inner<L> {
143    listener: L,
144    /// Keeps track of the expected "writable" state seen by `listener`.
145    ///
146    /// This is necessary because of the zero-crossing optimization implemented
147    /// by [`SendBufferTracking`]. Multiple threads can be acquiring and
148    /// releasing bytes from the buffer at the same time and the zero crossing
149    /// itself doesn't fully guarantee that we'd never notify the listener twice
150    /// with the same value. This boolean provides that guarantee.
151    ///
152    /// Example race:
153    ///
154    /// - T1 acquires N bytes observes zero-crossing and tries to acquire the
155    ///   lock; gets descheduled.
156    /// - T2 releases N bytes, observes zero crossing and tries to acquire the
157    ///   lock; gets descheduled.
158    /// - T3 acquires N bytes, observes zero crossing and tries to acquire the
159    ///   lock; gets descheduled.
160    ///
161    /// Whenever the 3 threads become runnable again they'll acquire the lock
162    /// one at a time, read the number of available bytes in the buffer and
163    /// decide on a writable state, which would be attempted to be notified 3
164    /// times in a row.
165    notified_state: bool,
166    /// The maximum capacity allowed in the [`SendBufferTracking`] instance.
167    ///
168    /// Note that, due to overcommit, more than `capacity` bytes can be
169    /// in-flight at once. A single [`SendBufferSpace`] may be emitted that
170    /// crosses the capacity threshold.
171    capacity: PositiveIsize,
172}
173
174/// A type stating that some amount of space was reserved within a
175/// [`SendBufferTracking`] instance.
176///
177/// This type is returned from [`SendBufferTracking::acquire`] and *must* be
178/// returned to [`SendBufferTracking::release`] when the annotated send buffer
179/// space is freed. Otherwise, [`SendBufferSpace::acknowledge_drop`] must be
180/// called. This type panics on drop otherwise.
181#[derive(Debug, Eq, PartialEq)]
182pub struct SendBufferSpace(PositiveIsize);
183
184impl Drop for SendBufferSpace {
185    fn drop(&mut self) {
186        panic!("dropped send buffer space with {:?} bytes", self)
187    }
188}
189
190impl SendBufferSpace {
191    /// Acknowledges an unused and from this point on untracked buffer space.
192    ///
193    /// This may be used to drop previously allocated buffer space tracking that
194    /// has outlived the owning socket.
195    pub fn acknowledge_drop(self) {
196        core::mem::forget(self)
197    }
198}
199
200/// An error indicating that the send buffer is full.
201#[derive(Debug, Eq, PartialEq)]
202pub struct SendBufferFullError;
203
204/// A type capable of handling socket writable changes.
205///
206/// Upon creation, listeners must always assume the socket is writable.
207pub trait SocketWritableListener {
208    /// Notifies the listener the writable state has changed to `writable`.
209    ///
210    /// Callers must only call this when the writable state has actually
211    /// changed. Implementers may panic if they see the current state being
212    /// notified as changed.
213    fn on_writable_changed(&mut self, writable: bool);
214}
215
216#[cfg(any(test, feature = "testutils"))]
217pub(crate) mod testutil {
218    use super::*;
219
220    /// A fake [`SocketWritableListener`] implementation.
221    #[derive(Debug)]
222    pub struct FakeSocketWritableListener {
223        writable: bool,
224    }
225
226    impl FakeSocketWritableListener {
227        /// Returns whether the listener has observed a writable state.
228        pub fn is_writable(&self) -> bool {
229            self.writable
230        }
231    }
232
233    impl Default for FakeSocketWritableListener {
234        fn default() -> Self {
235            Self { writable: true }
236        }
237    }
238
239    impl SocketWritableListener for FakeSocketWritableListener {
240        fn on_writable_changed(&mut self, writable: bool) {
241            assert_ne!(core::mem::replace(&mut self.writable, writable), writable);
242        }
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use crate::testutil::FakeBindingsCtx;
250    use testutil::FakeSocketWritableListener;
251
252    use alloc::vec::Vec;
253
254    type SendBufferTracking<L> = super::SendBufferTracking<L, FakeBindingsCtx<(), (), (), ()>>;
255
256    const SNDBUF: PositiveIsize = PositiveIsize::new(4).unwrap();
257    const HALF_SNDBUF: PositiveIsize = PositiveIsize::new(SNDBUF.get() / 2).unwrap();
258    const TWO_SNDBUF: PositiveIsize = PositiveIsize::new(SNDBUF.get() * 2).unwrap();
259    const ONE: PositiveIsize = PositiveIsize::new(1).unwrap();
260
261    impl SendBufferTracking<FakeSocketWritableListener> {
262        fn listener_writable(&self) -> bool {
263            self.inner.lock().listener.is_writable()
264        }
265    }
266
267    #[test]
268    fn acquire_all_buffer() {
269        let tracking = SendBufferTracking::new(SNDBUF, FakeSocketWritableListener::default());
270        let acquired = tracking.acquire(SNDBUF).expect("acquire");
271        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), 0);
272        assert_eq!(tracking.listener_writable(), false);
273        assert_eq!(tracking.acquire(ONE), Err(SendBufferFullError));
274        tracking.release(acquired);
275        assert_eq!(tracking.listener_writable(), true);
276        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), SNDBUF.get());
277    }
278
279    #[test]
280    fn acquire_half_buffer() {
281        let tracking = SendBufferTracking::new(SNDBUF, FakeSocketWritableListener::default());
282        let acquired = tracking.acquire(HALF_SNDBUF).expect("acquire");
283        assert_eq!(
284            tracking.available_bytes.load(Ordering::SeqCst),
285            SNDBUF.get() - HALF_SNDBUF.get()
286        );
287        assert_eq!(tracking.listener_writable(), true);
288        tracking.release(acquired);
289        assert_eq!(tracking.listener_writable(), true);
290        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), SNDBUF.get());
291    }
292
293    #[test]
294    fn acquire_multiple() {
295        let tracking = SendBufferTracking::new(SNDBUF, FakeSocketWritableListener::default());
296        let tokens = (0..SNDBUF.get())
297            .map(|_| {
298                assert_eq!(tracking.listener_writable(), true);
299                tracking.acquire(ONE).expect("acquire")
300            })
301            .collect::<Vec<_>>();
302        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), 0);
303        assert_eq!(tracking.listener_writable(), false);
304
305        assert_eq!(tracking.acquire(ONE), Err(SendBufferFullError));
306        for t in tokens {
307            tracking.release(t);
308            assert_eq!(tracking.listener_writable(), true);
309        }
310
311        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), SNDBUF.get());
312        assert_eq!(tracking.listener_writable(), true);
313    }
314
315    #[test]
316    fn overcommit_single_buffer() {
317        let tracking = SendBufferTracking::new(SNDBUF, FakeSocketWritableListener::default());
318        let acquired = tracking.acquire(TWO_SNDBUF).expect("acquire");
319        assert_eq!(tracking.listener_writable(), false);
320        assert_eq!(
321            tracking.available_bytes.load(Ordering::SeqCst),
322            SNDBUF.get() - TWO_SNDBUF.get()
323        );
324        assert_eq!(tracking.acquire(ONE), Err(SendBufferFullError));
325
326        tracking.release(acquired);
327        assert_eq!(tracking.listener_writable(), true);
328        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), SNDBUF.get());
329    }
330
331    #[test]
332    fn overcommit_two_buffers() {
333        let tracking = SendBufferTracking::new(SNDBUF, FakeSocketWritableListener::default());
334        let acquired1 = tracking.acquire(ONE).expect("acquire");
335        assert_eq!(tracking.listener_writable(), true);
336        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), SNDBUF.get() - 1);
337        let acquired2 = tracking.acquire(SNDBUF).expect("acquire");
338        assert_eq!(tracking.listener_writable(), false);
339        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), -1);
340        assert_eq!(tracking.acquire(ONE), Err(SendBufferFullError));
341
342        tracking.release(acquired1);
343        // Still not writable.
344        assert_eq!(tracking.listener_writable(), false);
345        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), 0);
346
347        tracking.release(acquired2);
348        assert_eq!(tracking.listener_writable(), true);
349        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), SNDBUF.get());
350    }
351
352    #[test]
353    fn capacity_increase_makes_writable() {
354        let tracking = SendBufferTracking::new(SNDBUF, FakeSocketWritableListener::default());
355        assert_eq!(tracking.capacity(), SNDBUF);
356        let acquired = tracking.acquire(SNDBUF).expect("acquire");
357        assert_eq!(tracking.listener_writable(), false);
358
359        tracking.set_capacity(TWO_SNDBUF);
360        assert_eq!(tracking.listener_writable(), true);
361        assert_eq!(
362            tracking.available_bytes.load(Ordering::SeqCst),
363            TWO_SNDBUF.get() - SNDBUF.get()
364        );
365        assert_eq!(tracking.capacity(), TWO_SNDBUF);
366        tracking.release(acquired);
367    }
368
369    #[test]
370    fn capacity_decrease_makes_non_writable() {
371        let tracking = SendBufferTracking::new(SNDBUF, FakeSocketWritableListener::default());
372        assert_eq!(tracking.capacity(), SNDBUF);
373        let acquired = tracking.acquire(HALF_SNDBUF).expect("acquire");
374        assert_eq!(tracking.listener_writable(), true);
375
376        tracking.set_capacity(HALF_SNDBUF);
377        assert_eq!(tracking.listener_writable(), false);
378        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), 0);
379        assert_eq!(tracking.capacity(), HALF_SNDBUF);
380        tracking.release(acquired);
381        assert_eq!(tracking.listener_writable(), true);
382        assert_eq!(tracking.available_bytes.load(Ordering::SeqCst), HALF_SNDBUF.get());
383    }
384}