From 52286f51115c4621d4cad6bb1e80ae3048ab253d Mon Sep 17 00:00:00 2001
From: Pat Hickey
Date: Mon, 18 Aug 2025 12:04:57 -0700
Subject: [PATCH 1/3] refactor(examples): rename async example to tokio
To reduce some confusion. Since this example was introduced, we
introduced a smaller nginx-native async executor directly to ngx
---
.github/workflows/nginx.yaml | 2 +-
examples/Cargo.toml | 4 +--
examples/config | 4 +--
examples/t/{async.t => tokio.t} | 4 +--
examples/{async.conf => tokio.conf} | 4 +--
examples/{async.rs => tokio.rs} | 44 ++++++++++++++---------------
6 files changed, 31 insertions(+), 31 deletions(-)
rename examples/t/{async.t => tokio.t} (91%)
rename examples/{async.conf => tokio.conf} (87%)
rename examples/{async.rs => tokio.rs} (86%)
diff --git a/.github/workflows/nginx.yaml b/.github/workflows/nginx.yaml
index 02fedf1a..5f62f875 100644
--- a/.github/workflows/nginx.yaml
+++ b/.github/workflows/nginx.yaml
@@ -49,7 +49,7 @@ env:
NGX_TEST_FILES: examples/t
NGX_TEST_GLOBALS_DYNAMIC: >-
- load_module ${{ github.workspace }}/nginx/objs/ngx_http_async_module.so;
+ load_module ${{ github.workspace }}/nginx/objs/ngx_http_tokio_module.so;
load_module ${{ github.workspace }}/nginx/objs/ngx_http_awssigv4_module.so;
load_module ${{ github.workspace }}/nginx/objs/ngx_http_curl_module.so;
load_module ${{ github.workspace }}/nginx/objs/ngx_http_shared_dict_module.so;
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index 9006aba7..2c796c39 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -47,8 +47,8 @@ path = "upstream.rs"
crate-type = ["cdylib"]
[[example]]
-name = "async"
-path = "async.rs"
+name = "tokio"
+path = "tokio.rs"
crate-type = ["cdylib"]
[[example]]
diff --git a/examples/config b/examples/config
index 6b763652..2dc7995c 100644
--- a/examples/config
+++ b/examples/config
@@ -16,9 +16,9 @@ if [ $HTTP = YES ]; then
ngx_rust_target_features=
if :; then
- ngx_module_name=ngx_http_async_module
+ ngx_module_name=ngx_http_tokio_module
ngx_module_libs="-lm"
- ngx_rust_target_name=async
+ ngx_rust_target_name=tokio
ngx_rust_module
fi
diff --git a/examples/t/async.t b/examples/t/tokio.t
similarity index 91%
rename from examples/t/async.t
rename to examples/t/tokio.t
index 98505fdb..a8371831 100644
--- a/examples/t/async.t
+++ b/examples/t/tokio.t
@@ -39,7 +39,7 @@ http {
server_name localhost;
location / {
- async on;
+ tokio on;
}
}
}
@@ -51,6 +51,6 @@ $t->run();
###############################################################################
-like(http_get('/index.html'), qr/X-Async-Time:/, 'async handler');
+like(http_get('/index.html'), qr/X-Tokio-Time:/, 'tokio handler');
###############################################################################
diff --git a/examples/async.conf b/examples/tokio.conf
similarity index 87%
rename from examples/async.conf
rename to examples/tokio.conf
index d96876e0..aea1e4a3 100644
--- a/examples/async.conf
+++ b/examples/tokio.conf
@@ -2,7 +2,7 @@ daemon off;
master_process off;
# worker_processes 1;
-load_module modules/libasync.so;
+load_module modules/libtokio.so;
error_log error.log debug;
events { }
@@ -14,7 +14,7 @@ http {
location / {
root html;
index index.html index.htm;
- async on;
+ tokio on;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
diff --git a/examples/async.rs b/examples/tokio.rs
similarity index 86%
rename from examples/async.rs
rename to examples/tokio.rs
index 47f5de8e..85d9f34a 100644
--- a/examples/async.rs
+++ b/examples/tokio.rs
@@ -20,7 +20,7 @@ struct Module;
impl http::HttpModule for Module {
fn module() -> &'static ngx_module_t {
- unsafe { &*::core::ptr::addr_of!(ngx_http_async_module) }
+ unsafe { &*::core::ptr::addr_of!(ngx_http_tokio_module) }
}
unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t {
@@ -35,7 +35,7 @@ impl http::HttpModule for Module {
return core::Status::NGX_ERROR.into();
}
// set an Access phase handler
- *h = Some(async_access_handler);
+ *h = Some(tokio_access_handler);
core::Status::NGX_OK.into()
}
}
@@ -49,11 +49,11 @@ unsafe impl HttpModuleLocationConf for Module {
type LocationConf = ModuleConfig;
}
-static mut NGX_HTTP_ASYNC_COMMANDS: [ngx_command_t; 2] = [
+static mut NGX_HTTP_TOKIO_COMMANDS: [ngx_command_t; 2] = [
ngx_command_t {
- name: ngx_string!("async"),
+ name: ngx_string!("tokio"),
type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
- set: Some(ngx_http_async_commands_set_enable),
+ set: Some(ngx_http_tokio_commands_set_enable),
conf: NGX_HTTP_LOC_CONF_OFFSET,
offset: 0,
post: std::ptr::null_mut(),
@@ -61,7 +61,7 @@ static mut NGX_HTTP_ASYNC_COMMANDS: [ngx_command_t; 2] = [
ngx_command_t::empty(),
];
-static NGX_HTTP_ASYNC_MODULE_CTX: ngx_http_module_t = ngx_http_module_t {
+static NGX_HTTP_TOKIO_MODULE_CTX: ngx_http_module_t = ngx_http_module_t {
preconfiguration: Some(Module::preconfiguration),
postconfiguration: Some(Module::postconfiguration),
create_main_conf: None,
@@ -75,14 +75,14 @@ static NGX_HTTP_ASYNC_MODULE_CTX: ngx_http_module_t = ngx_http_module_t {
// Generate the `ngx_modules` table with exported modules.
// This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem.
#[cfg(feature = "export-modules")]
-ngx::ngx_modules!(ngx_http_async_module);
+ngx::ngx_modules!(ngx_http_tokio_module);
#[used]
#[allow(non_upper_case_globals)]
#[cfg_attr(not(feature = "export-modules"), no_mangle)]
-pub static mut ngx_http_async_module: ngx_module_t = ngx_module_t {
- ctx: std::ptr::addr_of!(NGX_HTTP_ASYNC_MODULE_CTX) as _,
- commands: unsafe { &NGX_HTTP_ASYNC_COMMANDS[0] as *const _ as *mut _ },
+pub static mut ngx_http_tokio_module: ngx_module_t = ngx_module_t {
+ ctx: std::ptr::addr_of!(NGX_HTTP_TOKIO_MODULE_CTX) as _,
+ commands: unsafe { &NGX_HTTP_TOKIO_COMMANDS[0] as *const _ as *mut _ },
type_: NGX_HTTP_MODULE as _,
..ngx_module_t::default()
};
@@ -96,12 +96,12 @@ impl http::Merge for ModuleConfig {
}
}
-unsafe extern "C" fn check_async_work_done(event: *mut ngx_event_t) {
+unsafe extern "C" fn check_tokio_work_done(event: *mut ngx_event_t) {
let ctx = ngx::ngx_container_of!(event, RequestCTX, event);
let c: *mut ngx_connection_t = (*event).data.cast();
if (*ctx).done.load(Ordering::Relaxed) {
- // Triggering async_access_handler again
+ // Triggering tokio_access_handler again
ngx_post_event((*c).write, addr_of_mut!(ngx_posted_events));
} else {
// this doesn't have have good performance but works as a simple thread-safe example and
@@ -139,17 +139,17 @@ impl Drop for RequestCTX {
}
}
-http_request_handler!(async_access_handler, |request: &mut http::Request| {
+http_request_handler!(tokio_access_handler, |request: &mut http::Request| {
let co = Module::location_conf(request).expect("module config is none");
- ngx_log_debug_http!(request, "async module enabled: {}", co.enable);
+ ngx_log_debug_http!(request, "tokio module enabled: {}", co.enable);
if !co.enable {
return core::Status::NGX_DECLINED;
}
if let Some(ctx) =
- unsafe { request.get_module_ctx::(&*addr_of!(ngx_http_async_module)) }
+ unsafe { request.get_module_ctx::(&*addr_of!(ngx_http_tokio_module)) }
{
if !ctx.done.load(Ordering::Relaxed) {
return core::Status::NGX_AGAIN;
@@ -162,10 +162,10 @@ http_request_handler!(async_access_handler, |request: &mut http::Request| {
if ctx.is_null() {
return core::Status::NGX_ERROR;
}
- request.set_module_ctx(ctx.cast(), unsafe { &*addr_of!(ngx_http_async_module) });
+ request.set_module_ctx(ctx.cast(), unsafe { &*addr_of!(ngx_http_tokio_module) });
let ctx = unsafe { &mut *ctx };
- ctx.event.handler = Some(check_async_work_done);
+ ctx.event.handler = Some(check_tokio_work_done);
ctx.event.data = request.connection().cast();
ctx.event.log = unsafe { (*request.connection()).log };
unsafe { ngx_post_event(&mut ctx.event, addr_of_mut!(ngx_posted_next_events)) };
@@ -174,7 +174,7 @@ http_request_handler!(async_access_handler, |request: &mut http::Request| {
let req = AtomicPtr::new(request.into());
let done_flag = ctx.done.clone();
- let rt = ngx_http_async_runtime();
+ let rt = ngx_http_tokio_runtime();
ctx.task = Some(rt.spawn(async move {
let start = Instant::now();
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
@@ -183,7 +183,7 @@ http_request_handler!(async_access_handler, |request: &mut http::Request| {
// but this is just an example. proper way would be storing these headers in the request ctx
// and apply them when we get back to the nginx thread.
req.add_header_out(
- "X-Async-Time",
+ "X-Tokio-Time",
start.elapsed().as_millis().to_string().as_str(),
);
@@ -197,7 +197,7 @@ http_request_handler!(async_access_handler, |request: &mut http::Request| {
core::Status::NGX_AGAIN
});
-extern "C" fn ngx_http_async_commands_set_enable(
+extern "C" fn ngx_http_tokio_commands_set_enable(
cf: *mut ngx_conf_t,
_cmd: *mut ngx_command_t,
conf: *mut c_void,
@@ -208,7 +208,7 @@ extern "C" fn ngx_http_async_commands_set_enable(
let val = match args[1].to_str() {
Ok(s) => s,
Err(_) => {
- ngx_conf_log_error!(NGX_LOG_EMERG, cf, "`async` argument is not utf-8 encoded");
+ ngx_conf_log_error!(NGX_LOG_EMERG, cf, "`tokio` argument is not utf-8 encoded");
return ngx::core::NGX_CONF_ERROR;
}
};
@@ -226,7 +226,7 @@ extern "C" fn ngx_http_async_commands_set_enable(
ngx::core::NGX_CONF_OK
}
-fn ngx_http_async_runtime() -> &'static Runtime {
+fn ngx_http_tokio_runtime() -> &'static Runtime {
// Should not be called from the master process
assert_ne!(
unsafe { ngx::ffi::ngx_process },
From 4043c1fb73464ef2fee57d4fdb18215c59449e5a Mon Sep 17 00:00:00 2001
From: Pat Hickey
Date: Mon, 18 Aug 2025 16:38:18 -0700
Subject: [PATCH 2/3] fix(CI): windows CI can't cache nginx/objs
because the windows nmake program doesn't support .PHONY, and therefore
it may reuse stale items from the cache instead of building them
---
.github/workflows/nginx.yaml | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/nginx.yaml b/.github/workflows/nginx.yaml
index 5f62f875..dff9b49a 100644
--- a/.github/workflows/nginx.yaml
+++ b/.github/workflows/nginx.yaml
@@ -185,9 +185,8 @@ jobs:
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
- nginx/objs/**/CACHEDIR.TAG
- nginx/objs/**/ngx-debug
- nginx/objs/**/ngx-release
+ # Windows nmake implementation doesn't support .PHONY. Don't cache
+ # anything make creates because it might not rebuild correctly
key: ${{ runner.os }}-nginx-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-nginx-
From 0eb092f77101593902521c78668b6bf2b90354a6 Mon Sep 17 00:00:00 2001
From: Pat Hickey
Date: Mon, 18 Aug 2025 16:41:23 -0700
Subject: [PATCH 3/3] feat(examples): add example using ngx::async_
---
.github/workflows/nginx.yaml | 1 +
examples/Cargo.toml | 8 ++
examples/async.conf | 24 ++++
examples/async.rs | 248 +++++++++++++++++++++++++++++++++++
examples/config | 9 ++
examples/t/async.t | 56 ++++++++
6 files changed, 346 insertions(+)
create mode 100644 examples/async.conf
create mode 100644 examples/async.rs
create mode 100644 examples/t/async.t
diff --git a/.github/workflows/nginx.yaml b/.github/workflows/nginx.yaml
index dff9b49a..1f10490f 100644
--- a/.github/workflows/nginx.yaml
+++ b/.github/workflows/nginx.yaml
@@ -49,6 +49,7 @@ env:
NGX_TEST_FILES: examples/t
NGX_TEST_GLOBALS_DYNAMIC: >-
+ load_module ${{ github.workspace }}/nginx/objs/ngx_http_async_module.so;
load_module ${{ github.workspace }}/nginx/objs/ngx_http_tokio_module.so;
load_module ${{ github.workspace }}/nginx/objs/ngx_http_awssigv4_module.so;
load_module ${{ github.workspace }}/nginx/objs/ngx_http_curl_module.so;
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index 2c796c39..c87586c2 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -25,6 +25,13 @@ idna_adapter = "=1.1.0"
libc = "0.2.140"
tokio = { version = "1.33.0", features = ["full"] }
+
+[[example]]
+name = "async"
+path = "async.rs"
+crate-type = ["cdylib"]
+required-features = [ "async" ]
+
[[example]]
name = "curl"
path = "curl.rs"
@@ -65,3 +72,4 @@ default = ["export-modules", "ngx/vendored"]
# See https://github.com/rust-lang/rust/issues/20267
export-modules = []
linux = []
+async = [ "ngx/async" ]
diff --git a/examples/async.conf b/examples/async.conf
new file mode 100644
index 00000000..d96876e0
--- /dev/null
+++ b/examples/async.conf
@@ -0,0 +1,24 @@
+daemon off;
+master_process off;
+# worker_processes 1;
+
+load_module modules/libasync.so;
+error_log error.log debug;
+
+events { }
+
+http {
+ server {
+ listen *:8000;
+ server_name localhost;
+ location / {
+ root html;
+ index index.html index.htm;
+ async on;
+ }
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root html;
+ }
+ }
+}
diff --git a/examples/async.rs b/examples/async.rs
new file mode 100644
index 00000000..72d93ef3
--- /dev/null
+++ b/examples/async.rs
@@ -0,0 +1,248 @@
+use std::ffi::{c_char, c_void};
+use std::time::Instant;
+
+use ngx::async_::{sleep, spawn, Task};
+use ngx::core;
+use ngx::ffi::{
+ ngx_array_push, ngx_buf_t, ngx_chain_t, ngx_command_t, ngx_conf_t, ngx_http_finalize_request,
+ ngx_http_handler_pt, ngx_http_module_t, ngx_http_phases_NGX_HTTP_ACCESS_PHASE,
+ ngx_http_read_client_request_body, ngx_http_request_t, ngx_int_t, ngx_module_t, ngx_str_t,
+ ngx_uint_t, NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET, NGX_HTTP_MODULE,
+ NGX_HTTP_SPECIAL_RESPONSE, NGX_LOG_EMERG,
+};
+use ngx::http::{self, HttpModule, MergeConfigError};
+use ngx::http::{HttpModuleLocationConf, HttpModuleMainConf, NgxHttpCoreModule};
+use ngx::{http_request_handler, ngx_conf_log_error, ngx_log_debug_http, ngx_string};
+
+struct Module;
+
+impl http::HttpModule for Module {
+ fn module() -> &'static ngx_module_t {
+ unsafe { &*std::ptr::addr_of!(ngx_http_async_module) }
+ }
+
+ unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t {
+ // SAFETY: this function is called with non-NULL cf always
+ let cf = &mut *cf;
+ let cmcf = NgxHttpCoreModule::main_conf_mut(cf).expect("http core main conf");
+
+ let h = ngx_array_push(
+ &mut cmcf.phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers,
+ ) as *mut ngx_http_handler_pt;
+ if h.is_null() {
+ return core::Status::NGX_ERROR.into();
+ }
+ // set an Access phase handler
+ *h = Some(async_access_handler);
+ core::Status::NGX_OK.into()
+ }
+}
+
+#[derive(Debug, Default)]
+struct ModuleConfig {
+ enable: bool,
+}
+
+unsafe impl HttpModuleLocationConf for Module {
+ type LocationConf = ModuleConfig;
+}
+
+static mut NGX_HTTP_ASYNC_COMMANDS: [ngx_command_t; 2] = [
+ ngx_command_t {
+ name: ngx_string!("async"),
+ type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t,
+ set: Some(ngx_http_async_commands_set_enable),
+ conf: NGX_HTTP_LOC_CONF_OFFSET,
+ offset: 0,
+ post: std::ptr::null_mut(),
+ },
+ ngx_command_t::empty(),
+];
+
+static NGX_HTTP_ASYNC_MODULE_CTX: ngx_http_module_t = ngx_http_module_t {
+ preconfiguration: Some(Module::preconfiguration),
+ postconfiguration: Some(Module::postconfiguration),
+ create_main_conf: None,
+ init_main_conf: None,
+ create_srv_conf: None,
+ merge_srv_conf: None,
+ create_loc_conf: Some(Module::create_loc_conf),
+ merge_loc_conf: Some(Module::merge_loc_conf),
+};
+
+// Generate the `ngx_modules` table with exported modules.
+// This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem.
+#[cfg(feature = "export-modules")]
+ngx::ngx_modules!(ngx_http_async_module);
+
+#[used]
+#[allow(non_upper_case_globals)]
+#[cfg_attr(not(feature = "export-modules"), no_mangle)]
+pub static mut ngx_http_async_module: ngx_module_t = ngx_module_t {
+ ctx: std::ptr::addr_of!(NGX_HTTP_ASYNC_MODULE_CTX) as _,
+ commands: unsafe { &NGX_HTTP_ASYNC_COMMANDS[0] as *const _ as *mut _ },
+ type_: NGX_HTTP_MODULE as _,
+ ..ngx_module_t::default()
+};
+
+impl http::Merge for ModuleConfig {
+ fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> {
+ if prev.enable {
+ self.enable = true;
+ };
+ Ok(())
+ }
+}
+
+extern "C" fn ngx_http_async_commands_set_enable(
+ cf: *mut ngx_conf_t,
+ _cmd: *mut ngx_command_t,
+ conf: *mut c_void,
+) -> *mut c_char {
+ unsafe {
+ let conf = &mut *(conf as *mut ModuleConfig);
+ let args: &[ngx_str_t] = (*(*cf).args).as_slice();
+ let val = match args[1].to_str() {
+ Ok(s) => s,
+ Err(_) => {
+ ngx_conf_log_error!(NGX_LOG_EMERG, cf, "`async` argument is not utf-8 encoded");
+ return ngx::core::NGX_CONF_ERROR;
+ }
+ };
+
+ // set default value optionally
+ conf.enable = false;
+
+ if val.eq_ignore_ascii_case("on") {
+ conf.enable = true;
+ } else if val.eq_ignore_ascii_case("off") {
+ conf.enable = false;
+ }
+ };
+
+ ngx::core::NGX_CONF_OK
+}
+
+http_request_handler!(async_access_handler, |request: &mut http::Request| {
+ let co = Module::location_conf(request).expect("module config is none");
+
+ ngx_log_debug_http!(request, "async module enabled: {}", co.enable);
+
+ if !co.enable {
+ return core::Status::NGX_DECLINED;
+ }
+
+ if request
+ .get_module_ctx::>(unsafe { &*std::ptr::addr_of!(ngx_http_async_module) })
+ .is_some()
+ {
+ return core::Status::NGX_DONE;
+ }
+
+ let rc =
+ unsafe { ngx_http_read_client_request_body(request.into(), Some(content_event_handler)) };
+ if rc as u32 >= NGX_HTTP_SPECIAL_RESPONSE {
+ return core::Status(rc);
+ }
+
+ core::Status::NGX_DONE
+});
+
+extern "C" fn content_event_handler(request: *mut ngx_http_request_t) {
+ let task = spawn(async move {
+ let start = Instant::now();
+ sleep(std::time::Duration::from_secs(2)).await;
+
+ let req = unsafe { http::Request::from_ngx_http_request(request) };
+ req.add_header_out(
+ "X-Async-Time",
+ start.elapsed().as_millis().to_string().as_str(),
+ );
+ req.set_status(http::HTTPStatus::OK);
+ req.send_header();
+ let buf = req.pool().calloc(std::mem::size_of::()) as *mut ngx_buf_t;
+ unsafe {
+ (*buf).set_last_buf(if req.is_main() { 1 } else { 0 });
+ (*buf).set_last_in_chain(1);
+ }
+ req.output_filter(&mut ngx_chain_t {
+ buf,
+ next: std::ptr::null_mut(),
+ });
+
+ unsafe {
+ ngx::ffi::ngx_post_event(
+ (*(*request).connection).write,
+ std::ptr::addr_of_mut!(ngx::ffi::ngx_posted_events),
+ );
+ }
+ });
+
+ let req = unsafe { http::Request::from_ngx_http_request(request) };
+
+ let ctx = req.pool().allocate::>(task);
+ if ctx.is_null() {
+ unsafe { ngx_http_finalize_request(request, core::Status::NGX_ERROR.into()) };
+ return;
+ }
+ req.set_module_ctx(ctx.cast(), unsafe {
+ &*std::ptr::addr_of!(ngx_http_async_module)
+ });
+ unsafe { (*request).write_event_handler = Some(write_event_handler) };
+}
+
+extern "C" fn write_event_handler(request: *mut ngx_http_request_t) {
+ let req = unsafe { http::Request::from_ngx_http_request(request) };
+ if let Some(task) =
+ req.get_module_ctx::>(unsafe { &*std::ptr::addr_of!(ngx_http_async_module) })
+ {
+ if task.is_finished() {
+ unsafe { ngx_http_finalize_request(request, core::Status::NGX_OK.into()) };
+ return;
+ }
+ }
+
+ let write_event =
+ unsafe { (*(*request).connection).write.as_ref() }.expect("write event is not null");
+ if write_event.timedout() != 0 {
+ unsafe {
+ ngx::ffi::ngx_connection_error(
+ (*request).connection,
+ ngx::ffi::NGX_ETIMEDOUT as i32,
+ c"client timed out".as_ptr() as *mut _,
+ )
+ };
+ return;
+ }
+
+ if unsafe { ngx::ffi::ngx_http_output_filter(request, std::ptr::null_mut()) }
+ == ngx::ffi::NGX_ERROR as isize
+ {
+ // Client error
+ return;
+ }
+ let clcf =
+ NgxHttpCoreModule::location_conf(unsafe { request.as_ref().expect("request not null") })
+ .expect("http core server conf");
+
+ if unsafe {
+ ngx::ffi::ngx_handle_write_event(std::ptr::from_ref(write_event) as *mut _, clcf.send_lowat)
+ } != ngx::ffi::NGX_OK as isize
+ {
+ // Client error
+ return;
+ }
+
+ if write_event.delayed() == 0 {
+ if (write_event.active() != 0) && (write_event.ready() == 0) {
+ unsafe {
+ ngx::ffi::ngx_add_timer(
+ std::ptr::from_ref(write_event) as *mut _,
+ clcf.send_timeout,
+ )
+ }
+ } else if write_event.timer_set() != 0 {
+ unsafe { ngx::ffi::ngx_del_timer(std::ptr::from_ref(write_event) as *mut _) }
+ }
+ }
+}
diff --git a/examples/config b/examples/config
index 2dc7995c..45aa9a24 100644
--- a/examples/config
+++ b/examples/config
@@ -15,6 +15,15 @@ if [ $HTTP = YES ]; then
ngx_rust_target_type=EXAMPLE
ngx_rust_target_features=
+ if :; then
+ ngx_module_name=ngx_http_async_module
+ ngx_module_libs="-lm"
+ ngx_rust_target_name=async
+ ngx_rust_target_features=async
+
+ ngx_rust_module
+ fi
+
if :; then
ngx_module_name=ngx_http_tokio_module
ngx_module_libs="-lm"
diff --git a/examples/t/async.t b/examples/t/async.t
new file mode 100644
index 00000000..98505fdb
--- /dev/null
+++ b/examples/t/async.t
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+
+# (C) Nginx, Inc
+
+# Tests for ngx-rust example modules.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http/)->plan(1)
+ ->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+ %%TEST_GLOBALS_HTTP%%
+
+ server {
+ listen 127.0.0.1:8080;
+ server_name localhost;
+
+ location / {
+ async on;
+ }
+ }
+}
+
+EOF
+
+$t->write_file('index.html', '');
+$t->run();
+
+###############################################################################
+
+like(http_get('/index.html'), qr/X-Async-Time:/, 'async handler');
+
+###############################################################################