Skip to content

Commit 1115932

Browse files
committed
feat: typed storage in pool
1 parent e90cb5f commit 1115932

1 file changed

Lines changed: 168 additions & 19 deletions

File tree

src/core/pool.rs

Lines changed: 168 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,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.
333480
unsafe 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

Comments
 (0)