Skip to content

Commit 362c93e

Browse files
committed
feat: typed storage in pool
1 parent 34ee650 commit 362c93e

1 file changed

Lines changed: 181 additions & 19 deletions

File tree

src/core/pool.rs

Lines changed: 181 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use core::ptr::{self, NonNull};
55

66
use 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

1111
use 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+
131136
impl 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.
333493
unsafe 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

Comments
 (0)