From 3c08a1dece78416d179dc85945c39aae5332aa79 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Mon, 10 Feb 2025 15:10:53 -0800 Subject: [PATCH 1/2] feat(sys): reimplement `ngx_queue_t` macros Bindgen does not support functional macros or inline functions, so we have to maintain a copy of the implementation of any such item we need to use. There are no safe wrappers, because all the operations here are unsafe and unsound, unless used on the heap- or pool-allocated objects with certain ownership constraints. --- nginx-sys/src/lib.rs | 4 + nginx-sys/src/queue.rs | 257 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 nginx-sys/src/queue.rs diff --git a/nginx-sys/src/lib.rs b/nginx-sys/src/lib.rs index 9e2abd41..2455bcab 100644 --- a/nginx-sys/src/lib.rs +++ b/nginx-sys/src/lib.rs @@ -2,6 +2,8 @@ #![warn(missing_docs)] #![no_std] +mod queue; + use core::fmt; use core::mem::offset_of; use core::ptr::{self, copy_nonoverlapping}; @@ -22,6 +24,8 @@ mod bindings { #[doc(no_inline)] pub use bindings::*; +pub use queue::*; + /// The offset of the `main_conf` field in the `ngx_http_conf_ctx_t` struct. /// /// This is used to access the main configuration context for an HTTP module. diff --git a/nginx-sys/src/queue.rs b/nginx-sys/src/queue.rs new file mode 100644 index 00000000..be18a611 --- /dev/null +++ b/nginx-sys/src/queue.rs @@ -0,0 +1,257 @@ +use core::ptr; + +use crate::bindings::ngx_queue_t; + +/// Get a reference to the beginning of a queue node data structure, +/// considering the queue field offset in it. +/// +/// # Safety +/// +/// `$q` must be a valid pointer to the field `$link` in the struct `$type` +#[macro_export] +macro_rules! ngx_queue_data { + ($q:expr, $type:path, $link:ident) => { + $q.byte_sub(::core::mem::offset_of!($type, $link)).cast::<$type>() + }; +} + +/// Initializes the queue head before use. +/// +/// # Safety +/// +/// `q` must be a valid pointer to [ngx_queue_t]. +#[inline] +pub unsafe fn ngx_queue_init(q: *mut ngx_queue_t) { + (*q).prev = q; + (*q).next = q; +} + +/// Returns `true` if the queue contains no elements. +/// +/// # Safety +/// +/// `q` must be a valid pointer to [ngx_queue_t], initialized with [ngx_queue_init]. +#[inline] +pub unsafe fn ngx_queue_empty(q: *const ngx_queue_t) -> bool { + q == (*q).prev +} + +/// Inserts a new node after the current. +/// +/// # Safety +/// +/// Both `q` and `x` must be valid pointers to [ngx_queue_t] +#[inline] +pub unsafe fn ngx_queue_insert_after(q: *mut ngx_queue_t, x: *mut ngx_queue_t) { + (*x).next = (*q).next; + (*(*x).next).prev = x; + (*x).prev = q; + (*q).next = x; +} + +/// Inserts a new node before the current. +/// +/// # Safety +/// +/// Both `q` and `x` must be valid pointers to [ngx_queue_t]. +#[inline] +pub unsafe fn ngx_queue_insert_before(q: *mut ngx_queue_t, x: *mut ngx_queue_t) { + (*x).prev = (*q).prev; + (*(*x).prev).next = x; + (*x).next = q; + (*q).prev = x; +} + +/// Removes a node from the queue. +/// +/// # Safety +/// +/// `q` must be a valid pointer to an [ngx_queue_t] node. +#[inline] +pub unsafe fn ngx_queue_remove(q: *mut ngx_queue_t) { + (*(*q).next).prev = (*q).prev; + (*(*q).prev).next = (*q).next; + (*q).prev = ptr::null_mut(); + (*q).next = ptr::null_mut(); +} + +/// Splits a queue at a node, returning the queue tail in a separate queue. +/// +/// # Safety +/// +/// `h` must be a valid pointer to a head queue node. +/// `q` must be a node in the queue `h`. +/// `n` must be a valid pointer to [ngx_queue_t]. +#[inline] +pub unsafe fn ngx_queue_split(h: *mut ngx_queue_t, q: *mut ngx_queue_t, n: *mut ngx_queue_t) { + (*n).prev = (*h).prev; + (*(*n).prev).next = n; + (*n).next = q; + (*h).prev = (*q).prev; + (*(*h).prev).next = h; + (*q).prev = n; +} + +/// Adds a second queue to the first queue. +/// +/// # Safety +/// +/// Both `h` and `n` must be valid pointers to queue heads, initialized with [ngx_queue_init]. +/// `n` will be left in invalid state, pointing to the subrange of `h` without back references. +#[inline] +pub unsafe fn ngx_queue_add(h: *mut ngx_queue_t, n: *mut ngx_queue_t) { + (*(*h).prev).next = (*n).next; + (*(*n).next).prev = (*h).prev; + (*h).prev = (*n).prev; + (*(*h).prev).next = h; +} + +impl ngx_queue_t { + /// Returns `true` if the queue contains no elements. + pub fn is_empty(&self) -> bool { + unsafe { ngx_queue_empty(self) } + } +} + +impl Default for ngx_queue_t { + fn default() -> ngx_queue_t { + ngx_queue_t { + prev: ptr::null_mut(), + next: ptr::null_mut(), + } + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + use alloc::boxed::Box; + + use super::*; + + struct TestData { + value: usize, + queue: ngx_queue_t, + } + + impl TestData { + pub fn new(value: usize) -> *mut Self { + // We should be using `ngx_pool_t` here, but that is not possible without linking to + // the nginx + let mut x = Box::new(Self { + value, + queue: Default::default(), + }); + unsafe { ngx_queue_init(ptr::addr_of_mut!(x.queue)) }; + Box::into_raw(x) + } + + pub unsafe fn free(x: *mut Self) { + let _ = Box::from_raw(x); + } + } + + impl Drop for TestData { + fn drop(&mut self) { + if !self.queue.next.is_null() && !self.queue.is_empty() { + unsafe { ngx_queue_remove(ptr::addr_of_mut!(self.queue)) }; + } + } + } + + struct Iter { + h: *mut ngx_queue_t, + q: *mut ngx_queue_t, + next: fn(*mut ngx_queue_t) -> *mut ngx_queue_t, + } + + impl Iter { + pub fn new(h: *mut ngx_queue_t) -> Self { + let next = |x: *mut ngx_queue_t| unsafe { (*x).next }; + Self { h, q: next(h), next } + } + + pub fn new_reverse(h: *mut ngx_queue_t) -> Self { + let next = |x: *mut ngx_queue_t| unsafe { (*x).prev }; + Self { h, q: next(h), next } + } + } + + impl Iterator for Iter { + type Item = *mut ngx_queue_t; + + fn next(&mut self) -> Option { + if self.h == self.q { + return None; + } + + let item = self.q; + self.q = (self.next)(self.q); + Some(item) + } + } + + #[test] + fn test_queue() { + fn value(q: *mut ngx_queue_t) -> usize { + unsafe { (*ngx_queue_data!(q, TestData, queue)).value } + } + + // Check forward and reverse iteration + fn cmp(h: *mut ngx_queue_t, other: &[usize]) -> bool { + Iter::new(h).map(value).eq(other.iter().cloned()) + && Iter::new_reverse(h).map(value).eq(other.iter().rev().cloned()) + } + + // Note how this test does not use references or borrows to avoid triggering UBs + // detectable by Miri. This does not mean that the code is safe or sound. + unsafe { + // Initialize and fill the queue + + let mut h1 = ngx_queue_t::default(); + ngx_queue_init(ptr::addr_of_mut!(h1)); + + let mut h2 = ngx_queue_t::default(); + ngx_queue_init(ptr::addr_of_mut!(h2)); + + for i in 1..=5 { + let elem = TestData::new(i); + ngx_queue_insert_before(ptr::addr_of_mut!(h1), ptr::addr_of_mut!((*elem).queue)); + + let elem = TestData::new(i); + ngx_queue_insert_after(ptr::addr_of_mut!(h2), ptr::addr_of_mut!((*elem).queue)); + } + + // Iterate and test the values + + assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5])); + assert!(cmp(ptr::addr_of_mut!(h2), &[5, 4, 3, 2, 1])); + + // Move nodes from h2 to h1 + + // h2 still points to the subrange of h1 after this operation + ngx_queue_add(ptr::addr_of_mut!(h1), ptr::addr_of_mut!(h2)); + + assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5, 5, 4, 3, 2, 1])); + + ngx_queue_split(ptr::addr_of_mut!(h1), (*h2.next).next, ptr::addr_of_mut!(h2)); + + assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5, 5])); + assert!(cmp(ptr::addr_of_mut!(h2), &[4, 3, 2, 1])); + + // Cleanup + + for q in Iter::new(ptr::addr_of_mut!(h1)) { + let td = ngx_queue_data!(q, TestData, queue); + TestData::free(td); + } + assert!(h1.is_empty()); + + for q in Iter::new(ptr::addr_of_mut!(h2)) { + let td = ngx_queue_data!(q, TestData, queue); + TestData::free(td); + } + assert!(h2.is_empty()); + }; + } +} From 644eb093984ac8f9dfe85ef38e7d42785698a7a1 Mon Sep 17 00:00:00 2001 From: Aleksei Bavshin Date: Wed, 12 Feb 2025 14:19:41 -0800 Subject: [PATCH 2/2] feat(sys): reimplement `ngx_event_t` macros and inline methods Bindgen does not support functional macros or inline functions, so we have to maintain a copy of the implementation for any such item we need to use. --- examples/async.rs | 20 ++-------- nginx-sys/src/event.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ nginx-sys/src/lib.rs | 2 + 3 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 nginx-sys/src/event.rs diff --git a/examples/async.rs b/examples/async.rs index ae27fec6..dcce2bba 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -8,7 +8,7 @@ use ngx::core; use ngx::ffi::{ ngx_array_push, ngx_command_t, ngx_conf_t, ngx_cycle, ngx_event_t, ngx_http_core_module, ngx_http_core_run_phases, ngx_http_handler_pt, ngx_http_module_t, ngx_http_phases_NGX_HTTP_ACCESS_PHASE, ngx_http_request_t, ngx_int_t, - ngx_module_t, ngx_posted_events, ngx_queue_s, ngx_str_t, ngx_uint_t, NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, + ngx_module_t, ngx_post_event, ngx_posted_events, ngx_str_t, ngx_uint_t, NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET, NGX_HTTP_MODULE, }; use ngx::http::{self, HTTPModule, MergeConfigError}; @@ -112,7 +112,7 @@ unsafe extern "C" fn check_async_work_done(event: *mut ngx_event_t) { // this doesn't have have good performance but works as a simple thread-safe example and doesn't causes // segfault. The best method that provides both thread-safety and performance requires // an nginx patch. - post_event(event, addr_of_mut!(ngx_posted_events)); + ngx_post_event(event, addr_of_mut!(ngx_posted_events)); } } @@ -128,20 +128,6 @@ struct EventData { unsafe impl Send for EventData {} unsafe impl Sync for EventData {} -// same as ngx_post_event -// source: https://github.com/nginx/ngx-rust/pull/31/files#diff-132330bb775bed17fb9990ec2b56e6c52e6a9e56d62f2114fade95e4decdba08R80-R90 -unsafe fn post_event(event: *mut ngx_event_t, queue: *mut ngx_queue_s) { - let event = &mut (*event); - if event.posted() == 0 { - event.set_posted(1); - // translated from ngx_queue_insert_tail macro - event.queue.prev = (*queue).prev; - (*event.queue.prev).next = &event.queue as *const _ as *mut _; - event.queue.next = queue; - (*queue).prev = &event.queue as *const _ as *mut _; - } -} - http_request_handler!(async_access_handler, |request: &mut http::Request| { let co = unsafe { request.get_module_loc_conf::(&*addr_of!(ngx_http_async_module)) }; let co = co.expect("module config is none"); @@ -184,7 +170,7 @@ http_request_handler!(async_access_handler, |request: &mut http::Request| { event.data = Arc::into_raw(event_data.clone()) as _; event.log = (*ngx_cycle).log; - post_event(event, addr_of_mut!(ngx_posted_events)); + ngx_post_event(event, addr_of_mut!(ngx_posted_events)); } ngx_log_debug_http!(request, "async module enabled: {}", co.enable); diff --git a/nginx-sys/src/event.rs b/nginx-sys/src/event.rs new file mode 100644 index 00000000..3e59af93 --- /dev/null +++ b/nginx-sys/src/event.rs @@ -0,0 +1,83 @@ +use core::ptr; + +use crate::{ + ngx_current_msec, ngx_event_t, ngx_event_timer_rbtree, ngx_msec_t, ngx_queue_insert_before, ngx_queue_remove, + ngx_queue_t, ngx_rbtree_delete, ngx_rbtree_insert, NGX_TIMER_LAZY_DELAY, +}; + +/// Sets a timeout for an event. +/// +/// # Safety +/// +///`ev` must be a valid pointer to an `ngx_event_t`. +#[inline] +pub unsafe fn ngx_add_timer(ev: *mut ngx_event_t, timer: ngx_msec_t) { + let key: ngx_msec_t = ngx_current_msec.wrapping_add(timer); + + if (*ev).timer_set() != 0 { + /* + * Use a previous timer value if difference between it and a new + * value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows + * to minimize the rbtree operations for fast connections. + */ + if key.abs_diff((*ev).timer.key) < NGX_TIMER_LAZY_DELAY as _ { + return; + } + + ngx_del_timer(ev); + } + + (*ev).timer.key = key; + + ngx_rbtree_insert( + ptr::addr_of_mut!(ngx_event_timer_rbtree), + ptr::addr_of_mut!((*ev).timer), + ); + + (*ev).set_timer_set(1); +} + +/// Deletes a previously set timeout. +/// +/// # Safety +/// +/// `ev` must be a valid pointer to an `ngx_event_t`, previously armed with [ngx_add_timer]. +#[inline] +pub unsafe fn ngx_del_timer(ev: *mut ngx_event_t) { + ngx_rbtree_delete( + ptr::addr_of_mut!(ngx_event_timer_rbtree), + ptr::addr_of_mut!((*ev).timer), + ); + + (*ev).timer.left = ptr::null_mut(); + (*ev).timer.right = ptr::null_mut(); + (*ev).timer.parent = ptr::null_mut(); + + (*ev).set_timer_set(0); +} + +/// Post the event `ev` to the post queue `q`. +/// +/// # Safety +/// +/// `ev` must be a valid pointer to an `ngx_event_t`. +/// `q` is a valid pointer to a queue head. +#[inline] +pub unsafe fn ngx_post_event(ev: *mut ngx_event_t, q: *mut ngx_queue_t) { + if (*ev).posted() == 0 { + (*ev).set_posted(1); + ngx_queue_insert_before(q, ptr::addr_of_mut!((*ev).queue)); + } +} + +/// Deletes the event `ev` from the queue it's currently posted in. +/// +/// # Safety +/// +/// `ev` must be a valid pointer to an `ngx_event_t`. +/// `ev.queue` is initialized with `ngx_queue_init`. +#[inline] +pub unsafe fn ngx_delete_posted_event(ev: *mut ngx_event_t) { + (*ev).set_posted(0); + ngx_queue_remove(ptr::addr_of_mut!((*ev).queue)); +} diff --git a/nginx-sys/src/lib.rs b/nginx-sys/src/lib.rs index 2455bcab..f0389ae8 100644 --- a/nginx-sys/src/lib.rs +++ b/nginx-sys/src/lib.rs @@ -2,6 +2,7 @@ #![warn(missing_docs)] #![no_std] +mod event; mod queue; use core::fmt; @@ -24,6 +25,7 @@ mod bindings { #[doc(no_inline)] pub use bindings::*; +pub use event::*; pub use queue::*; /// The offset of the `main_conf` field in the `ngx_http_conf_ctx_t` struct.