Skip to main content

base_pool/
multiplex.rs

1// Released under the MIT License.
2// Copyright, 2019-2026, by Samuel Williams.
3
4use crate::{
5    Acquisition, Error, Options, Pool, ResourceId, Retirement, Stats,
6    core::{Acquired, Core, Slot, cancellable},
7};
8use base_scheduler::Cancellation;
9use std::{
10    collections::BTreeMap,
11    ops::Deref,
12    sync::{Arc, Weak},
13};
14
15/// Concurrent-use bound and optional consumable opening credit. Completing a
16/// use restores concurrent capacity, but never manufactures opening credit.
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct Admission {
19    pub max_concurrent: usize,
20    pub opening_credit: Option<usize>,
21}
22impl Admission {
23    pub fn concurrent(max_concurrent: usize) -> Self {
24        Self {
25            max_concurrent,
26            opening_credit: None,
27        }
28    }
29}
30
31/// A use identifier within one resource registration, never recycled there.
32#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
33pub struct UseId(u64);
34
35#[derive(Clone, Copy, Eq, PartialEq)]
36enum Stage {
37    Reserved,
38    Active,
39    Abandoned,
40    Cleaning,
41}
42
43struct MultiSlot<R> {
44    resource: Arc<R>,
45    admission: Admission,
46    uses: BTreeMap<UseId, Stage>,
47    reserved: usize,
48    active: usize,
49    next_use: u64,
50    draining: bool,
51}
52impl<R: Send + Sync> Slot for MultiSlot<R> {
53    type Resource = Arc<R>;
54    type Acquired = (Arc<R>, UseId);
55    fn available(&self) -> bool {
56        !self.draining
57            && self.uses.len() < self.admission.max_concurrent
58            && self
59                .admission
60                .opening_credit
61                .is_none_or(|credit| self.reserved < credit)
62    }
63    fn acquire(&mut self) -> Self::Acquired {
64        let id = UseId(self.next_use);
65        self.next_use = self
66            .next_use
67            .checked_add(1)
68            .expect("multiplex use identifier space exhausted");
69        self.uses.insert(id, Stage::Reserved);
70        self.reserved += 1;
71        (self.resource.clone(), id)
72    }
73    fn uses(&self) -> usize {
74        self.uses.len()
75    }
76    fn draining(&self) -> bool {
77        self.draining
78    }
79    fn drain(&mut self) {
80        self.draining = true;
81    }
82    fn into_resource(self) -> Arc<R> {
83        self.resource
84    }
85}
86
87/// Shared resources with non-cloneable use reservations. The protocol owner
88/// validates and activates reservations while committing the actual operation.
89pub struct MultiplexPool<R: Send + Sync> {
90    core: Arc<Core<MultiSlot<R>>>,
91}
92impl<R: Send + Sync> Clone for MultiplexPool<R> {
93    fn clone(&self) -> Self {
94        Self {
95            core: self.core.clone(),
96        }
97    }
98}
99impl<R: Send + Sync> MultiplexPool<R> {
100    pub fn new(options: Options) -> Result<Self, Error> {
101        Ok(Self {
102            core: Core::new(options)?,
103        })
104    }
105    pub async fn acquire_or_reserve(
106        &self,
107        cancel: &Cancellation,
108    ) -> Result<Acquisition<MultiplexReservation<R>, MultiplexBuildPermit<R>>, Error> {
109        Ok(match self.core.acquire(cancel).await? {
110            Acquired::Existing(id, (resource, use_id)) => {
111                Acquisition::Existing(MultiplexReservation {
112                    core: self.core.clone(),
113                    id,
114                    use_id,
115                    resource,
116                    pending: true,
117                })
118            }
119            Acquired::Build(id) => Acquisition::Build(MultiplexBuildPermit {
120                core: self.core.clone(),
121                id,
122                pending: true,
123            }),
124        })
125    }
126    pub fn begin_close(&self) {
127        self.core.begin_close();
128    }
129    pub fn stats(&self) -> Stats {
130        self.core.stats()
131    }
132    pub fn prune(&self, retain: usize) -> usize {
133        self.core.prune(retain)
134    }
135    pub fn try_retirement(&self) -> Option<Retirement<Arc<R>>> {
136        self.core.retirements.pop()
137    }
138    pub async fn next_retirement(
139        &self,
140        cancel: &Cancellation,
141    ) -> Result<Option<Retirement<Arc<R>>>, Error> {
142        self.core.next_retirement(cancel).await
143    }
144    pub async fn wait_closed(&self, cancel: &Cancellation) -> Result<(), Error> {
145        self.core.wait_closed(cancel).await
146    }
147}
148impl<R: Send + Sync> Pool for MultiplexPool<R> {
149    type Permit = MultiplexReservation<R>;
150    type BuildPermit = MultiplexBuildPermit<R>;
151    async fn acquire_or_reserve(
152        &self,
153        cancel: &Cancellation,
154    ) -> Result<Acquisition<Self::Permit, Self::BuildPermit>, Error> {
155        self.acquire_or_reserve(cancel).await
156    }
157    fn begin_close(&self) {
158        self.begin_close();
159    }
160    fn stats(&self) -> Stats {
161        self.stats()
162    }
163}
164
165/// Reserves resource construction, independent of per-resource stream capacity.
166/// Commit registers the shared handle; acquire again for a use reservation.
167pub struct MultiplexBuildPermit<R: Send + Sync> {
168    core: Arc<Core<MultiSlot<R>>>,
169    id: ResourceId,
170    pending: bool,
171}
172impl<R: Send + Sync> MultiplexBuildPermit<R> {
173    pub fn id(&self) -> ResourceId {
174        self.id
175    }
176    /// Register only after installing the resource's progress owner. Zero stream
177    /// capacity is valid and can be increased later through the returned handle.
178    /// A late commit after shutdown enters retirement and returns `Closed`.
179    pub fn commit(
180        mut self,
181        resource: R,
182        admission: Admission,
183    ) -> Result<MultiplexResource<R>, Error> {
184        let slot = MultiSlot {
185            resource: Arc::new(resource),
186            admission,
187            uses: BTreeMap::new(),
188            reserved: 0,
189            active: 0,
190            next_use: 0,
191            draining: false,
192        };
193        self.core.commit(self.id, slot)?;
194        self.pending = false;
195        Ok(MultiplexResource {
196            core: Arc::downgrade(&self.core),
197            id: self.id,
198        })
199    }
200}
201impl<R: Send + Sync> Drop for MultiplexBuildPermit<R> {
202    fn drop(&mut self) {
203        if self.pending {
204            self.core.abort_build(self.id);
205        }
206    }
207}
208
209/// Weak driver-side control of one registration. Capacity updates and retirement
210/// wake acquirers even when no use has completed. No shared mutable resource
211/// reference is exposed by the pool.
212pub struct MultiplexResource<R: Send + Sync> {
213    core: Weak<Core<MultiSlot<R>>>,
214    id: ResourceId,
215}
216impl<R: Send + Sync> Clone for MultiplexResource<R> {
217    fn clone(&self) -> Self {
218        Self {
219            core: self.core.clone(),
220            id: self.id,
221        }
222    }
223}
224impl<R: Send + Sync> MultiplexResource<R> {
225    pub fn id(&self) -> ResourceId {
226        self.id
227    }
228    pub fn retire(&self) -> bool {
229        self.core.upgrade().is_some_and(|core| core.retire(self.id))
230    }
231    /// Publish current protocol/local admission. Decreases do not terminate uses
232    /// already active; outstanding reservations are revalidated at activation.
233    pub fn update(&self, admission: Admission) -> bool {
234        let Some(core) = self.core.upgrade() else {
235            return false;
236        };
237        {
238            let mut state = core.lock();
239            let Some(slot) = state.slots.get_mut(&self.id) else {
240                return false;
241            };
242            if slot.draining {
243                return false;
244            }
245            slot.admission = admission;
246        }
247        core.accounting.notify();
248        true
249    }
250    pub fn try_abandoned(&self) -> Option<MultiplexAbandonedUse<R>> {
251        let core = self.core.upgrade()?;
252        let (use_id, resource) = {
253            let mut state = core.lock();
254            let slot = state.slots.get_mut(&self.id)?;
255            let (&use_id, stage) = slot
256                .uses
257                .iter_mut()
258                .find(|(_, stage)| **stage == Stage::Abandoned)?;
259            *stage = Stage::Cleaning;
260            (use_id, slot.resource.clone())
261        };
262        Some(MultiplexAbandonedUse {
263            core,
264            id: self.id,
265            use_id,
266            resource,
267            pending: true,
268        })
269    }
270    /// The driver receives abandoned uses, performs protocol cleanup/reset, then
271    /// acknowledges completion. `None` means the registration has retired.
272    pub async fn next_abandoned(
273        &self,
274        cancel: &Cancellation,
275    ) -> Result<Option<MultiplexAbandonedUse<R>>, Error> {
276        loop {
277            cancel.check()?;
278            let Some(core) = self.core.upgrade() else {
279                return Ok(None);
280            };
281            event_listener::listener!(core.accounting.changed => listener);
282            if let Some(abandoned) = self.try_abandoned() {
283                return Ok(Some(abandoned));
284            }
285            if !core.lock().slots.contains_key(&self.id) {
286                return Ok(None);
287            }
288            cancellable(listener, cancel).await?;
289        }
290    }
291}
292
293/// Permission to open a use, with shared read-only access to its resource handle.
294/// Dropping before activation returns admission immediately: no use was opened.
295pub struct MultiplexReservation<R: Send + Sync> {
296    core: Arc<Core<MultiSlot<R>>>,
297    id: ResourceId,
298    use_id: UseId,
299    resource: Arc<R>,
300    pending: bool,
301}
302impl<R: Send + Sync> MultiplexReservation<R> {
303    pub fn id(&self) -> UseId {
304        self.use_id
305    }
306    pub fn resource_id(&self) -> ResourceId {
307        self.id
308    }
309    /// Revalidate pool admission at the protocol owner's opening boundary.
310    /// This accounts for an active use; it does not send frames or establish
311    /// peer acceptance. The protocol must still validate its own opening state.
312    pub fn activate(mut self) -> Result<MultiplexUse<R>, Error> {
313        let (accepted, retired) = {
314            let mut state = self.core.lock();
315            let closed = state.closed;
316            let slot = state.slots.get_mut(&self.id).unwrap();
317            assert!(slot.uses.remove(&self.use_id) == Some(Stage::Reserved));
318            slot.reserved -= 1;
319            let accepted = !closed
320                && !slot.draining
321                && slot.active < slot.admission.max_concurrent
322                && slot.admission.opening_credit != Some(0);
323            if accepted {
324                slot.uses.insert(self.use_id, Stage::Active);
325                slot.active += 1;
326                if let Some(credit) = &mut slot.admission.opening_credit {
327                    *credit -= 1;
328                }
329            }
330            (accepted, self.core.remove_if_drained(&mut state, self.id))
331        };
332        self.pending = false;
333        self.core.enqueue(self.id, retired);
334        if !accepted {
335            return Err(Error::AdmissionChanged);
336        }
337        Ok(MultiplexUse {
338            core: self.core.clone(),
339            id: self.id,
340            use_id: self.use_id,
341            resource: self.resource.clone(),
342            pending: true,
343        })
344    }
345}
346impl<R: Send + Sync> Deref for MultiplexReservation<R> {
347    type Target = R;
348    fn deref(&self) -> &R {
349        &self.resource
350    }
351}
352impl<R: Send + Sync> Drop for MultiplexReservation<R> {
353    fn drop(&mut self) {
354        if !self.pending {
355            return;
356        }
357        let retired = {
358            let mut state = self.core.lock();
359            let slot = state.slots.get_mut(&self.id).unwrap();
360            assert!(slot.uses.remove(&self.use_id) == Some(Stage::Reserved));
361            slot.reserved -= 1;
362            self.core.remove_if_drained(&mut state, self.id)
363        };
364        self.core.enqueue(self.id, retired);
365    }
366}
367
368fn finish<R: Send + Sync>(core: &Core<MultiSlot<R>>, id: ResourceId, use_id: UseId) {
369    let retired = {
370        let mut state = core.lock();
371        let slot = state.slots.get_mut(&id).unwrap();
372        let stage = slot.uses.remove(&use_id).unwrap();
373        assert!(stage == Stage::Active || stage == Stage::Cleaning);
374        slot.active -= 1;
375        core.remove_if_drained(&mut state, id)
376    };
377    core.enqueue(id, retired);
378}
379fn abandon<R: Send + Sync>(core: &Core<MultiSlot<R>>, id: ResourceId, use_id: UseId) {
380    {
381        let mut state = core.lock();
382        let stage = state
383            .slots
384            .get_mut(&id)
385            .unwrap()
386            .uses
387            .get_mut(&use_id)
388            .unwrap();
389        assert!(*stage == Stage::Active || *stage == Stage::Cleaning);
390        *stage = Stage::Abandoned;
391    }
392    core.accounting.notify();
393}
394
395/// The non-cloneable completion obligation for an opened use. Transfer it to
396/// the exchange owner and complete only after both directions/protocol cleanup.
397/// Drop reports abandonment to the resource driver and retains the charge.
398pub struct MultiplexUse<R: Send + Sync> {
399    core: Arc<Core<MultiSlot<R>>>,
400    id: ResourceId,
401    use_id: UseId,
402    resource: Arc<R>,
403    pending: bool,
404}
405impl<R: Send + Sync> MultiplexUse<R> {
406    pub fn id(&self) -> UseId {
407        self.use_id
408    }
409    pub fn resource_id(&self) -> ResourceId {
410        self.id
411    }
412    pub fn complete(mut self) {
413        finish(&self.core, self.id, self.use_id);
414        self.pending = false;
415    }
416}
417impl<R: Send + Sync> Deref for MultiplexUse<R> {
418    type Target = R;
419    fn deref(&self) -> &R {
420        &self.resource
421    }
422}
423impl<R: Send + Sync> Drop for MultiplexUse<R> {
424    fn drop(&mut self) {
425        if self.pending {
426            abandon(&self.core, self.id, self.use_id);
427        }
428    }
429}
430
431/// Driver-owned cleanup of one abandoned use. Complete after protocol cleanup;
432/// dropping this handle reports it again without releasing its charge.
433pub struct MultiplexAbandonedUse<R: Send + Sync> {
434    core: Arc<Core<MultiSlot<R>>>,
435    id: ResourceId,
436    use_id: UseId,
437    resource: Arc<R>,
438    pending: bool,
439}
440impl<R: Send + Sync> MultiplexAbandonedUse<R> {
441    pub fn id(&self) -> UseId {
442        self.use_id
443    }
444    pub fn resource_id(&self) -> ResourceId {
445        self.id
446    }
447    pub fn complete(mut self) {
448        finish(&self.core, self.id, self.use_id);
449        self.pending = false;
450    }
451}
452impl<R: Send + Sync> Deref for MultiplexAbandonedUse<R> {
453    type Target = R;
454    fn deref(&self) -> &R {
455        &self.resource
456    }
457}
458impl<R: Send + Sync> Drop for MultiplexAbandonedUse<R> {
459    fn drop(&mut self) {
460        if self.pending {
461            abandon(&self.core, self.id, self.use_id);
462        }
463    }
464}