@@ -5,7 +5,7 @@ use std::{
55 mem:: { self , ManuallyDrop } ,
66 ptr:: NonNull ,
77 sync:: {
8- Mutex ,
8+ Condvar , Mutex ,
99 atomic:: { AtomicBool , AtomicU32 , Ordering } ,
1010 } ,
1111} ;
@@ -47,45 +47,109 @@ pub struct FixedSizeAllocatorPool {
4747 allocators : Mutex < Vec < FixedSizeAllocator > > ,
4848 /// ID to assign to next `Allocator` that's created
4949 next_id : AtomicU32 ,
50+ /// Maximum number of allocators this pool will create.
51+ /// Once this limit is reached, [`get`](Self::get) will block until an allocator
52+ /// is returned to the pool via [`add`](Self::add).
53+ capacity_limit : usize ,
54+ /// Condition variable used to signal when an allocator is returned to the pool.
55+ /// Threads blocked in [`get`](Self::get) waiting for an allocator will be woken
56+ /// when [`add`](Self::add) returns one to the pool.
57+ available : Condvar ,
58+ /// `true` if allocator creation has failed.
59+ /// Once set, no further attempts to create allocators will be made, since they would
60+ /// also fail.
61+ allocation_failed : AtomicBool ,
5062}
5163
5264impl FixedSizeAllocatorPool {
5365 /// Create a new [`FixedSizeAllocatorPool`] for use across the specified number of threads.
66+ ///
67+ /// The pool eagerly creates one allocator during construction. In total, it will create
68+ /// at most `max(thread_count, 1)` allocators (including this initial allocator). If all
69+ /// allocators are in use when [`get`](Self::get) is called, it will block until one is
70+ /// returned to the pool via [`add`](Self::add).
71+ ///
72+ /// Passing `thread_count == 0` is equivalent to a pool that may use exactly one allocator
73+ /// in total (the one created during construction).
5474 pub fn new ( thread_count : usize ) -> FixedSizeAllocatorPool {
55- // Each allocator consumes a large block of memory, so create them on demand instead of upfront,
56- // in case not all threads end up being used (e.g. language server without `import` plugin)
57- let allocators = Vec :: with_capacity ( thread_count) ;
58- FixedSizeAllocatorPool { allocators : Mutex :: new ( allocators) , next_id : AtomicU32 :: new ( 0 ) }
75+ let max_allocator_count = thread_count. max ( 1 ) ;
76+ let mut allocators = Vec :: with_capacity ( max_allocator_count) ;
77+ let Ok ( allocator) = FixedSizeAllocator :: try_new ( 0 ) else {
78+ panic ! ( "Failed to create initial fixed-size allocator for the pool" ) ;
79+ } ;
80+ allocators. push ( allocator) ;
81+
82+ FixedSizeAllocatorPool {
83+ allocators : Mutex :: new ( allocators) ,
84+ next_id : AtomicU32 :: new ( 1 ) ,
85+ available : Condvar :: new ( ) ,
86+ capacity_limit : max_allocator_count,
87+ allocation_failed : AtomicBool :: new ( false ) ,
88+ }
5989 }
6090
6191 /// Retrieve an [`Allocator`] from the pool, or create a new one if the pool is empty.
6292 ///
6393 /// # Panics
64- /// Panics if the underlying mutex is poisoned.
94+ /// * Panics if the underlying mutex is poisoned.
6595 pub fn get ( & self ) -> Allocator {
66- let fixed_size_allocator = {
67- let mut allocators = self . allocators . lock ( ) . unwrap ( ) ;
68- allocators. pop ( )
69- } ;
96+ fn into_allocator ( allocator : FixedSizeAllocator ) -> Allocator {
97+ // SAFETY: `FixedSizeAllocator` is just a wrapper around `ManuallyDrop<Allocator>`,
98+ // and is `#[repr(transparent)]`, so the 2 are equivalent.
99+ let allocator =
100+ unsafe { mem:: transmute :: < FixedSizeAllocator , ManuallyDrop < Allocator > > ( allocator) } ;
101+ ManuallyDrop :: into_inner ( allocator)
102+ }
103+
104+ {
105+ let maybe_allocator = self . allocators . lock ( ) . unwrap ( ) . pop ( ) ;
106+ if let Some ( allocator) = maybe_allocator {
107+ return into_allocator ( allocator) ;
108+ }
109+ }
110+
111+ if let Some ( Ok ( allocator) ) = self . create_new_allocator ( ) {
112+ return into_allocator ( allocator) ;
113+ }
114+
115+ loop {
116+ let mut maybe_allocator = self . available . wait ( self . allocators . lock ( ) . unwrap ( ) ) . unwrap ( ) ;
117+ if let Some ( allocator) = maybe_allocator. pop ( ) {
118+ return into_allocator ( allocator) ;
119+ }
120+ }
121+ }
122+
123+ fn create_new_allocator ( & self ) -> Option < Result < FixedSizeAllocator , AllocError > > {
124+ // If a previous allocation attempt failed, don't try again - it will also fail.
125+ if self . allocation_failed . load ( Ordering :: Relaxed ) {
126+ return None ;
127+ }
128+
129+ loop {
130+ let id = self . next_id . load ( Ordering :: Relaxed ) ;
131+
132+ if id as usize >= self . capacity_limit {
133+ return None ;
134+ }
70135
71- let fixed_size_allocator = fixed_size_allocator. unwrap_or_else ( || {
72- // Each allocator needs to have a unique ID, but the order those IDs are assigned in
73- // doesn't matter, so `Ordering::Relaxed` is fine
74- let id = self . next_id . fetch_add ( 1 , Ordering :: Relaxed ) ;
75136 // Protect against IDs wrapping around.
76137 // TODO: Does this work? Do we need it anyway?
77138 assert ! ( id < u32 :: MAX , "Created too many allocators" ) ;
78- FixedSizeAllocator :: try_new ( id) . unwrap ( )
79- } ) ;
80-
81- // Unwrap `FixedSizeAllocator`.
82- // `add` method will wrap it again, before returning it to pool, ensuring it gets dropped properly.
83- // SAFETY: `FixedSizeAllocator` is just a wrapper around `ManuallyDrop<Allocator>`,
84- // and is `#[repr(transparent)]`, so the 2 are equivalent.
85- let allocator = unsafe {
86- mem:: transmute :: < FixedSizeAllocator , ManuallyDrop < Allocator > > ( fixed_size_allocator)
87- } ;
88- ManuallyDrop :: into_inner ( allocator)
139+
140+ // Try to claim this ID. If another thread got there first, retry with new ID.
141+ if self
142+ . next_id
143+ . compare_exchange_weak ( id, id + 1 , Ordering :: Relaxed , Ordering :: Relaxed )
144+ . is_ok ( )
145+ {
146+ let result = FixedSizeAllocator :: try_new ( id) ;
147+ if result. is_err ( ) {
148+ self . allocation_failed . store ( true , Ordering :: Relaxed ) ;
149+ }
150+ return Some ( result) ;
151+ }
152+ }
89153 }
90154
91155 /// Add an [`Allocator`] to the pool.
@@ -104,6 +168,7 @@ impl FixedSizeAllocatorPool {
104168
105169 let mut allocators = self . allocators . lock ( ) . unwrap ( ) ;
106170 allocators. push ( fixed_size_allocator) ;
171+ self . available . notify_one ( ) ;
107172 }
108173}
109174
0 commit comments