Skip to main content

pgsync_core/
channel.rs

1//! Bounded, byte-accounted channel between the reader and the applier.
2//!
3//! Two limits apply: a count of in-flight items (`capacity`) and a byte budget
4//! (`max_bytes`). A producer must acquire both before an item is enqueued, so memory
5//! held by undelivered events is bounded by `max_bytes + one oversized item`.
6
7use std::sync::Arc;
8use std::sync::atomic::{AtomicU64, Ordering};
9use tokio::sync::{Semaphore, mpsc};
10
11pub struct Envelope<T> {
12    pub item: T,
13    _bytes: Option<tokio::sync::OwnedSemaphorePermit>,
14}
15
16impl<T> Envelope<T> {
17    pub fn into_inner(self) -> T {
18        self.item
19    }
20}
21
22#[derive(Clone)]
23pub struct Sender<T> {
24    tx: mpsc::Sender<Envelope<T>>,
25    bytes: Arc<Semaphore>,
26    max_bytes: usize,
27    inflight: Arc<AtomicU64>,
28}
29
30pub struct Receiver<T> {
31    rx: mpsc::Receiver<Envelope<T>>,
32    inflight: Arc<AtomicU64>,
33}
34
35pub fn bounded<T>(capacity: usize, max_bytes: usize) -> (Sender<T>, Receiver<T>) {
36    let (tx, rx) = mpsc::channel(capacity);
37    let inflight = Arc::new(AtomicU64::new(0));
38    (
39        Sender {
40            tx,
41            bytes: Arc::new(Semaphore::new(max_bytes)),
42            max_bytes,
43            inflight: inflight.clone(),
44        },
45        Receiver { rx, inflight },
46    )
47}
48
49#[derive(Debug, thiserror::Error)]
50#[error("channel closed")]
51pub struct Closed;
52
53impl<T> Sender<T> {
54    /// Reserve a slot for an item of `bytes` bytes. Waits for both limits.
55    /// Items larger than the whole budget are admitted alone (the budget is clamped) so a
56    /// single oversized row cannot deadlock the pipeline.
57    pub async fn reserve(&self, bytes: usize) -> Result<Permit<'_, T>, Closed> {
58        let want = bytes.min(self.max_bytes).max(1) as u32;
59        let slot = self.tx.reserve().await.map_err(|_| Closed)?;
60        let permit = self
61            .bytes
62            .clone()
63            .acquire_many_owned(want)
64            .await
65            .map_err(|_| Closed)?;
66        self.inflight.fetch_add(1, Ordering::Relaxed);
67        Ok(Permit {
68            slot: Some(slot),
69            permit: Some(permit),
70            inflight: &self.inflight,
71        })
72    }
73
74    pub fn inflight_items(&self) -> u64 {
75        self.inflight.load(Ordering::Relaxed)
76    }
77
78    pub fn available_bytes(&self) -> usize {
79        self.bytes.available_permits()
80    }
81}
82
83pub struct Permit<'a, T> {
84    slot: Option<mpsc::Permit<'a, Envelope<T>>>,
85    permit: Option<tokio::sync::OwnedSemaphorePermit>,
86    inflight: &'a AtomicU64,
87}
88
89impl<T> Permit<'_, T> {
90    pub fn send(mut self, item: T) {
91        let bytes = self.permit.take();
92        if let Some(slot) = self.slot.take() {
93            slot.send(Envelope {
94                item,
95                _bytes: bytes,
96            });
97        }
98    }
99}
100
101impl<T> Drop for Permit<'_, T> {
102    fn drop(&mut self) {
103        if self.permit.is_some() {
104            // Reserved but never sent.
105            self.inflight.fetch_sub(1, Ordering::Relaxed);
106        }
107    }
108}
109
110impl<T> Receiver<T> {
111    /// Returns `None` once all senders are dropped and the queue is drained.
112    pub async fn recv(&mut self) -> Option<Envelope<T>> {
113        let e = self.rx.recv().await?;
114        self.inflight.fetch_sub(1, Ordering::Relaxed);
115        Some(e)
116    }
117
118    pub fn close(&mut self) {
119        self.rx.close();
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use std::time::Duration;
127
128    #[tokio::test]
129    async fn blocks_when_bytes_exhausted_until_consumer_drains() {
130        let (tx, mut rx) = bounded::<Vec<u8>>(100, 1000);
131        tx.reserve(600).await.unwrap().send(vec![0; 600]);
132        tx.reserve(300).await.unwrap().send(vec![0; 300]);
133        assert_eq!(tx.available_bytes(), 100);
134        assert_eq!(tx.inflight_items(), 2);
135
136        let blocked = tokio::time::timeout(Duration::from_millis(50), tx.reserve(200)).await;
137        assert!(blocked.is_err(), "third item should block on byte budget");
138
139        let first = rx.recv().await.unwrap();
140        assert_eq!(first.item.len(), 600);
141        drop(first);
142        let p = tokio::time::timeout(Duration::from_millis(500), tx.reserve(200)).await;
143        assert!(p.is_ok(), "should unblock after consumer releases bytes");
144    }
145
146    #[tokio::test]
147    async fn blocks_on_item_capacity() {
148        let (tx, mut rx) = bounded::<u8>(2, 1 << 20);
149        tx.reserve(1).await.unwrap().send(1);
150        tx.reserve(1).await.unwrap().send(2);
151        let blocked = tokio::time::timeout(Duration::from_millis(50), tx.reserve(1)).await;
152        assert!(blocked.is_err());
153        assert_eq!(rx.recv().await.unwrap().into_inner(), 1);
154        assert!(
155            tokio::time::timeout(Duration::from_millis(500), tx.reserve(1))
156                .await
157                .is_ok()
158        );
159    }
160
161    #[tokio::test]
162    async fn oversized_item_is_admitted_alone() {
163        let (tx, mut rx) = bounded::<Vec<u8>>(10, 100);
164        let p = tokio::time::timeout(Duration::from_millis(200), tx.reserve(10_000)).await;
165        assert!(p.is_ok(), "oversized item must not deadlock");
166        p.unwrap().unwrap().send(vec![1]);
167        assert_eq!(tx.available_bytes(), 0);
168        drop(rx.recv().await.unwrap());
169        assert_eq!(tx.available_bytes(), 100);
170    }
171
172    #[tokio::test]
173    async fn dropped_permit_releases_everything() {
174        let (tx, _rx) = bounded::<u8>(1, 10);
175        {
176            let _p = tx.reserve(10).await.unwrap();
177            assert_eq!(tx.inflight_items(), 1);
178        }
179        assert_eq!(tx.inflight_items(), 0);
180        assert_eq!(tx.available_bytes(), 10);
181        assert!(
182            tokio::time::timeout(Duration::from_millis(100), tx.reserve(10))
183                .await
184                .is_ok()
185        );
186    }
187
188    #[tokio::test]
189    async fn recv_returns_none_after_senders_drop() {
190        let (tx, mut rx) = bounded::<u8>(1, 10);
191        tx.reserve(1).await.unwrap().send(5);
192        drop(tx);
193        assert_eq!(rx.recv().await.unwrap().into_inner(), 5);
194        assert!(rx.recv().await.is_none());
195    }
196}