@@ -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,62 @@ 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 )
219241 }
242+ }
220243
221- Ok ( ( ) )
244+ /// Searches for a cleanup handler in the pool's cleanup chain.
245+ ///
246+ /// Returns `Some(NonNull<ngx_pool_cleanup_t>)` if a cleanup handler is found for type `T`,
247+ /// or `None` if no matching handler is found.
248+ ///
249+ /// # Safety
250+ /// This function is marked as unsafe because it involves raw pointer manipulation
251+ /// and traverses the nginx cleanup chain structure.
252+ unsafe fn cleanup_lookup < T : Sized > ( & self ) -> Option < NonNull < ngx_pool_cleanup_t > > {
253+ let mut cln = ( unsafe { * self . 0 . as_ptr ( ) } ) . cleanup ;
254+ unsafe {
255+ // SAFETY: comparing function pointers is generally unreliable, but in this specific
256+ // case we can assume that the same function pointer was used when adding the cleanup
257+ // handler.
258+ #[ allow( unpredictable_function_pointer_comparisons) ]
259+ while !cln. is_null ( ) && ( * cln) . handler != Some ( cleanup_type :: < T > ) {
260+ cln = ( * cln) . next ;
261+ }
262+ }
263+ NonNull :: new ( cln)
222264 }
223265
224266 /// Allocates memory from the pool of the specified size.
@@ -272,18 +314,136 @@ impl Pool {
272314 ///
273315 /// Returns a typed pointer to the allocated memory if successful, or a null pointer if
274316 /// allocation or cleanup handler addition fails.
275- pub fn allocate < T > ( & self , value : T ) -> * mut T {
317+ pub fn allocate < T : Sized > ( & self , value : T ) -> * mut T {
276318 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
319+ match self . allocate_with_cleanup ( || value) {
320+ None => ptr:: null_mut ( ) ,
321+ Some ( mut ptr) => ptr. as_mut ( ) ,
322+ }
323+ }
324+ }
325+
326+ /// Gets the unique value of type `T` from the memory pool, or allocates and initializes it
327+ /// using the provided function if it does not exist.
328+ ///
329+ /// This ensures only one value of type `T` exists in the pool. If a value already exists,
330+ /// it is returned and the function `f` is not called. If no value exists, a new one is
331+ /// created using `f` and stored with a cleanup handler. If allocation fails, `f` is not called.
332+ ///
333+ /// Returns a mutable reference to the value if successful, or `None` if allocation fails.
334+ pub fn get_or_add_unique < T : Sized , F : FnOnce ( ) -> T > ( & mut self , f : F ) -> Option < & mut T > {
335+ unsafe {
336+ self . cleanup_lookup :: < Item < T > > ( )
337+ . map ( |cln| {
338+ let item = cln. as_ref ( ) . data as * mut Item < T > ;
339+ & mut ( * item) . value
340+ } )
341+ . or_else ( || {
342+ self . allocate_with_cleanup ( || Item { value : f ( ) } )
343+ . map ( |mut ptr| & mut ptr. as_mut ( ) . value )
344+ } )
284345 }
285346 }
286347
348+ /// Gets the unique value of type `T` from the memory pool.
349+ ///
350+ /// This value must have been previously allocated with [`Pool::get_or_add_unique`].
351+ ///
352+ /// Returns a reference to the value if found, or `None` if not found.
353+ pub fn get_unique < T : Sized > ( & self ) -> Option < & T > {
354+ unsafe {
355+ self . cleanup_lookup :: < Item < T > > ( ) . map ( |cln| {
356+ let item = cln. as_ref ( ) . data as * const Item < T > ;
357+ & ( * item) . value
358+ } )
359+ }
360+ }
361+
362+ /// Gets a mutable reference to the unique value of type `T` from the memory pool.
363+ ///
364+ /// This value must have been previously allocated with [`Pool::get_or_add_unique`].
365+ ///
366+ /// Returns a mutable reference to the value if found, or `None` if not found.
367+ pub fn get_unique_mut < T : Sized > ( & mut self ) -> Option < & mut T > {
368+ unsafe {
369+ self . cleanup_lookup :: < Item < T > > ( ) . map ( |cln| {
370+ let item = cln. as_ref ( ) . data as * mut Item < T > ;
371+ & mut ( * item) . value
372+ } )
373+ }
374+ }
375+
376+ /// Runs the cleanup handler for a value and removes it.
377+ ///
378+ /// Returns `Some(())` if the value was successfully removed,
379+ /// or `None` if the value was not found.
380+ ///
381+ /// # Safety
382+ /// The caller must ensure that `value` is a valid pointer to a value that has an
383+ /// associated cleanup handler in the pool.
384+ pub unsafe fn remove < T : Sized > ( & mut self , value : * const T ) -> Option < ( ) > {
385+ // SAFETY: comparing function pointers is generally unreliable, but in this specific
386+ // case we can assume that the same function pointer was used when adding the cleanup
387+ // handler.
388+ #[ allow( unpredictable_function_pointer_comparisons) ]
389+ self . remove_cleanup_handler ( |cln| {
390+ cln. handler == Some ( cleanup_type :: < T > ) && core:: ptr:: eq ( cln. data , value as _ )
391+ } )
392+ . map ( |cln| {
393+ unsafe { cln. handler . unwrap ( ) ( cln. data ) } ;
394+ } )
395+ }
396+
397+ /// Runs the cleanup handler for a unique value and removes it.
398+ ///
399+ /// This value must have been previously allocated with [`Pool::get_or_add_unique`].
400+ ///
401+ /// Returns `Some(())` if the value was successfully removed,
402+ /// or `None` if the value was not found.
403+ pub fn remove_unique < T : Sized > ( & mut self ) -> Option < ( ) > {
404+ // SAFETY: comparing function pointers is generally unreliable, but in this specific
405+ // case we can assume that the same function pointer was used when adding the cleanup
406+ // handler.
407+ #[ allow( unpredictable_function_pointer_comparisons) ]
408+ self . remove_cleanup_handler ( |cln| cln. handler == Some ( cleanup_type :: < T > ) ) . map ( |cln| {
409+ unsafe { cln. handler . unwrap ( ) ( cln. data ) } ;
410+ } )
411+ }
412+
413+ /// Internal method to find and remove a cleanup handler matching a predicate.
414+ ///
415+ /// Returns `Some(&ngx_pool_cleanup_t)` if a matching handler is found and removed,
416+ /// or `None` if not found.
417+ fn remove_cleanup_handler (
418+ & mut self ,
419+ predicate : impl Fn ( & ngx_pool_cleanup_t ) -> bool ,
420+ ) -> Option < & ngx_pool_cleanup_t > {
421+ let mut top = ngx_pool_cleanup_t {
422+ handler : None ,
423+ data : core:: ptr:: null_mut ( ) ,
424+ next : unsafe { self . 0 . as_mut ( ) . cleanup } ,
425+ } ;
426+
427+ let mut prev = & mut top;
428+ let top_ptr = prev as * const _ ;
429+
430+ while let Some ( cln) = unsafe { prev. next . as_mut ( ) } {
431+ if predicate ( cln) {
432+ if core:: ptr:: eq ( prev, top_ptr) {
433+ // If the removed cleanup was the first in the chain, update the pool's
434+ // cleanup pointer
435+ unsafe { self . 0 . as_mut ( ) . cleanup = cln. next } ;
436+ } else {
437+ prev. next = cln. next ;
438+ }
439+ return Some ( cln) ;
440+ }
441+ prev = cln;
442+ }
443+
444+ None
445+ }
446+
287447 /// Resizes a memory allocation in place if possible.
288448 ///
289449 /// If resizing is requested for the last allocation in the pool, it may be
@@ -332,6 +492,8 @@ impl Pool {
332492/// * `data` - A raw pointer to the value of type `T` to be cleaned up.
333493unsafe extern "C" fn cleanup_type < T > ( data : * mut c_void ) {
334494 unsafe {
335- ptr:: drop_in_place ( data as * mut T ) ;
495+ if !data. is_null ( ) {
496+ ptr:: drop_in_place ( data as * mut T ) ;
497+ }
336498 }
337499}
0 commit comments