@@ -5,7 +5,7 @@ use core::ptr::{self, NonNull};
55
66use nginx_sys:: {
77 NGX_ALIGNMENT , ngx_buf_t, ngx_create_temp_buf, ngx_palloc, ngx_pcalloc, ngx_pfree,
8- ngx_pmemalign, ngx_pnalloc, ngx_pool_cleanup_add, ngx_pool_t,
8+ ngx_pmemalign, ngx_pnalloc, ngx_pool_cleanup_add, ngx_pool_cleanup_t , ngx_pool_t,
99} ;
1010
1111use crate :: allocator:: { AllocError , Allocator , dangling_for_layout} ;
@@ -128,6 +128,11 @@ impl AsMut<ngx_pool_t> for Pool {
128128 }
129129}
130130
131+ // Wrapper to create an unique value type
132+ struct Item < T : Sized > {
133+ value : T ,
134+ }
135+
131136impl Pool {
132137 /// Creates a new `Pool` from an `ngx_pool_t` pointer.
133138 ///
@@ -200,25 +205,98 @@ impl Pool {
200205 Some ( MemoryBuffer :: from_ngx_buf ( buf) )
201206 }
202207
203- /// Adds a cleanup handler for a value in the memory pool.
208+ /// Allocates memory for a value and adds a cleanup handler to the memory pool.
204209 ///
205- /// Returns `Ok(())` if the cleanup handler is successfully added, or `Err(())` if the cleanup
206- /// handler cannot be added.
210+ /// The value is created by calling the provided closure `f`. If allocation fails,
211+ /// the closure is not called.
212+ ///
213+ /// Returns `Some(NonNull<T>)` if the allocation and cleanup handler addition are successful,
214+ /// or `None` if allocation fails.
207215 ///
208216 /// # Safety
209217 /// This function is marked as unsafe because it involves raw pointer manipulation.
210- unsafe fn add_cleanup_for_value < T > ( & self , value : * mut T ) -> Result < ( ) , ( ) > {
211- let cln = unsafe { ngx_pool_cleanup_add ( self . 0 . as_ptr ( ) , 0 ) } ;
218+ /// The returned pointer must not outlive the pool, must not be freed manually
219+ /// (as it has a cleanup handler), and must not be accessed after the pool is destroyed.
220+ pub unsafe fn allocate_with_cleanup < T : Sized , F : FnOnce ( ) -> T > (
221+ & self ,
222+ f : F ,
223+ ) -> Option < NonNull < T > > {
224+ let cln = unsafe { ngx_pool_cleanup_add ( self . 0 . as_ptr ( ) , mem:: size_of :: < T > ( ) ) } ;
212225 if cln. is_null ( ) {
213- return Err ( ( ) ) ;
226+ return None ;
214227 }
215-
216228 unsafe {
229+ // 'data' may be NULL only if `T` is zero-sized. In that case, no real value is stored,
230+ // so we can just use the cleanup structure itself as a placeholder.
231+ // Note that zero-sized `T` may implement `Drop`, and this implementation will be
232+ // called at cleanup time.
233+ if ( * cln) . data . is_null ( ) {
234+ ( * cln) . data = cln as _ ;
235+ } ;
217236 ( * cln) . handler = Some ( cleanup_type :: < T > ) ;
218- ( * cln) . data = value as * mut c_void ;
237+ // `data` points to the memory allocated for the value by `ngx_pool_cleanup_add()`
238+ ptr:: write ( ( * cln) . data as * mut T , f ( ) ) ;
239+
240+ NonNull :: new ( ( * cln) . data as * mut T )
241+ }
242+ }
243+
244+ /// Runs the cleanup handler for a value and removes it from the cleanup chain.
245+ ///
246+ /// If `value` is `Some`, removes the specific value's cleanup handler.
247+ /// If `value` is `None`, removes the first cleanup handler found for type `T`.
248+ ///
249+ /// Returns `Some(())` if a cleanup handler was found and executed,
250+ /// or `None` if no matching cleanup handler was found.
251+ ///
252+ /// # Safety
253+ /// The caller must ensure that if `value` is `Some`, it points to a valid value
254+ /// that has an associated cleanup handler in the pool.
255+ unsafe fn remove_cleanup < T : Sized > ( & self , value : Option < * const T > ) -> Option < ( ) > {
256+ unsafe {
257+ self . cleanup_lookup :: < T > ( value) . map ( |mut cln| {
258+ let cln = cln. as_mut ( ) ;
259+ cln. handler . take ( ) . inspect ( |handler| {
260+ handler ( cln. data ) ;
261+ } ) ;
262+ cln. data = core:: ptr:: null_mut ( ) ;
263+ } )
219264 }
265+ }
220266
221- Ok ( ( ) )
267+ /// Searches for a cleanup handler in the pool's cleanup chain.
268+ ///
269+ /// If `value` is `Some`, searches for the cleanup handler associated with that specific value.
270+ /// If `value` is `None`, returns the first cleanup handler found for type `T`.
271+ ///
272+ /// Returns `Some(NonNull<ngx_pool_cleanup_t>)` if a matching cleanup handler is found,
273+ /// or `None` if no matching handler is found.
274+ ///
275+ /// # Safety
276+ /// This function is marked as unsafe because it involves raw pointer manipulation
277+ /// and traverses the nginx cleanup chain structure.
278+ unsafe fn cleanup_lookup < T : Sized > (
279+ & self ,
280+ value : Option < * const T > ,
281+ ) -> Option < NonNull < ngx_pool_cleanup_t > > {
282+ let mut cln = ( unsafe { * self . 0 . as_ptr ( ) } ) . cleanup ;
283+
284+ while !cln. is_null ( ) {
285+ // SAFETY: comparing function pointers is generally unreliable, but in this specific
286+ // case we can assume that the same function pointer was used when adding the cleanup
287+ // handler.
288+ unsafe {
289+ #[ allow( unpredictable_function_pointer_comparisons) ]
290+ if ( * cln) . handler == Some ( cleanup_type :: < T > )
291+ && ( value. is_none ( ) || ( * cln) . data == value. unwrap ( ) as * mut c_void )
292+ {
293+ return NonNull :: new ( cln) ;
294+ }
295+ cln = ( * cln) . next ;
296+ }
297+ }
298+
299+ None
222300 }
223301
224302 /// Allocates memory from the pool of the specified size.
@@ -272,18 +350,87 @@ impl Pool {
272350 ///
273351 /// Returns a typed pointer to the allocated memory if successful, or a null pointer if
274352 /// allocation or cleanup handler addition fails.
275- pub fn allocate < T > ( & self , value : T ) -> * mut T {
353+ pub fn allocate < T : Sized > ( & self , value : T ) -> * mut T {
276354 unsafe {
277- let p = self . alloc ( mem:: size_of :: < T > ( ) ) as * mut T ;
278- ptr:: write ( p, value) ;
279- if self . add_cleanup_for_value ( p) . is_err ( ) {
280- ptr:: drop_in_place ( p) ;
281- return ptr:: null_mut ( ) ;
282- } ;
283- p
355+ match self . allocate_with_cleanup ( || value) {
356+ None => ptr:: null_mut ( ) ,
357+ Some ( mut ptr) => ptr. as_mut ( ) ,
358+ }
284359 }
285360 }
286361
362+ /// Gets the unique value of type `T` from the memory pool, or allocates and initializes it
363+ /// using the provided function if it does not exist.
364+ ///
365+ /// This ensures only one value of type `T` exists in the pool. If a value already exists,
366+ /// it is returned and the function `f` is not called. If no value exists, a new one is
367+ /// created using `f` and stored with a cleanup handler. If allocation fails, `f` is not called.
368+ ///
369+ /// Returns a mutable reference to the value if successful, or `None` if allocation fails.
370+ pub fn get_or_add_unique < T : Sized , F : FnOnce ( ) -> T > ( & mut self , f : F ) -> Option < & mut T > {
371+ unsafe {
372+ self . cleanup_lookup :: < Item < T > > ( None )
373+ . map ( |cln| {
374+ let item = cln. as_ref ( ) . data as * mut Item < T > ;
375+ & mut ( * item) . value
376+ } )
377+ . or_else ( || {
378+ self . allocate_with_cleanup ( || Item { value : f ( ) } )
379+ . map ( |mut ptr| & mut ptr. as_mut ( ) . value )
380+ } )
381+ }
382+ }
383+
384+ /// Gets the unique value of type `T` from the memory pool.
385+ ///
386+ /// This value must have been previously allocated with [`Pool::get_or_add_unique`].
387+ ///
388+ /// Returns a reference to the value if found, or `None` if not found.
389+ pub fn get_unique < T : Sized > ( & self ) -> Option < & T > {
390+ unsafe {
391+ self . cleanup_lookup :: < Item < T > > ( None ) . map ( |cln| {
392+ let item = cln. as_ref ( ) . data as * const Item < T > ;
393+ & ( * item) . value
394+ } )
395+ }
396+ }
397+
398+ /// Gets a mutable reference to the unique value of type `T` from the memory pool.
399+ ///
400+ /// This value must have been previously allocated with [`Pool::get_or_add_unique`].
401+ ///
402+ /// Returns a mutable reference to the value if found, or `None` if not found.
403+ pub fn get_unique_mut < T : Sized > ( & mut self ) -> Option < & mut T > {
404+ unsafe {
405+ self . cleanup_lookup :: < Item < T > > ( None ) . map ( |cln| {
406+ let item = cln. as_ref ( ) . data as * mut Item < T > ;
407+ & mut ( * item) . value
408+ } )
409+ }
410+ }
411+
412+ /// Runs the cleanup handler for a value and removes it.
413+ ///
414+ /// Returns `Some(())` if the value was successfully removed,
415+ /// or `None` if the value was not found.
416+ ///
417+ /// # Safety
418+ /// The caller must ensure that `value` is a valid pointer to a value that has an
419+ /// associated cleanup handler in the pool.
420+ pub unsafe fn remove < T : Sized > ( & self , value : * const T ) -> Option < ( ) > {
421+ unsafe { self . remove_cleanup ( Some ( value) ) }
422+ }
423+
424+ /// Runs the cleanup handler for a unique value and removes it.
425+ ///
426+ /// This value must have been previously allocated with [`Pool::get_or_add_unique`].
427+ ///
428+ /// Returns `Some(())` if the value was successfully removed,
429+ /// or `None` if the value was not found.
430+ pub fn remove_unique < T : Sized > ( & self ) -> Option < ( ) > {
431+ unsafe { self . remove_cleanup :: < Item < T > > ( None ) }
432+ }
433+
287434 /// Resizes a memory allocation in place if possible.
288435 ///
289436 /// If resizing is requested for the last allocation in the pool, it may be
@@ -332,6 +479,8 @@ impl Pool {
332479/// * `data` - A raw pointer to the value of type `T` to be cleaned up.
333480unsafe extern "C" fn cleanup_type < T > ( data : * mut c_void ) {
334481 unsafe {
335- ptr:: drop_in_place ( data as * mut T ) ;
482+ if !data. is_null ( ) {
483+ ptr:: drop_in_place ( data as * mut T ) ;
484+ }
336485 }
337486}
0 commit comments