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 9e2abd41..f0389ae8 100644 --- a/nginx-sys/src/lib.rs +++ b/nginx-sys/src/lib.rs @@ -2,6 +2,9 @@ #![warn(missing_docs)] #![no_std] +mod event; +mod queue; + use core::fmt; use core::mem::offset_of; use core::ptr::{self, copy_nonoverlapping}; @@ -22,6 +25,9 @@ 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. /// /// 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()); + }; + } +}