Skip to main content

base_pool/
multiplex.rs

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