Skip to content

Conversation

@ensh63
Copy link
Contributor

@ensh63 ensh63 commented Jan 9, 2026

Draft implementations of the following items are added to SDK:

  • asynchronous phase handler;
  • asynchronous subrequest;
  • type-safe request context;
  • typed storage for Pool.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces asynchronous capabilities to the Rust SDK for nginx modules, including async phase handlers, subrequests, and type-safe request context management.

Key Changes:

  • Introduces AsyncHandler trait and supporting infrastructure for asynchronous HTTP request handling
  • Adds RequestContext trait for type-safe request-specific context data management
  • Implements TypeStorage trait for storing typed values in nginx memory pools
  • Refactors existing examples to use new HttpRequestHandler trait and add_phase_handler function

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/log.rs Extracts connection to intermediate variable in logging macro
src/http/status.rs Adds From<HTTPStatus> implementation for ngx_int_t
src/http/request_context.rs Implements RequestContext trait for managing request-specific context
src/http/request.rs Adds HttpHandlerReturn and HttpRequestHandler traits, new request methods
src/http/mod.rs Adds async_request module exports (feature-gated)
src/http/conf.rs Adds HttpPhase enum and add_phase_handler function
src/http/async_request.rs Implements async handler infrastructure and subrequest builder
src/core/type_storage.rs Implements TypeStorage trait for pool-based type storage
src/core/pool.rs Adds remove method for cleaning up pool-allocated values
src/core/mod.rs Exports new type_storage module
examples/*.rs Refactors examples to use new handler registration pattern
examples/config Adds async_request module configuration
examples/Cargo.toml Adds async-related dependencies
Cargo.toml Adds futures dependency
.github/workflows/nginx.yaml Updates CI to load async_request module

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

(*cln).data = value as *mut c_void;
// `data` points to the memory allocated for the value by `ngx_pool_cleanup_add()`
ptr::write((*cln).data as *mut T, value);
NonNull::new((*cln).data as *mut T)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If T is ZST
cln.data will be NULL
ptr::write((*cln).data as *mut T, value) - is fine as null is explicitly allowed when value is ZST.
but NonNull::new() will return None.

as a result allocate<T>() will return ptr::null_mut().

I do not know should we improve it or not, but probably we at least should document it.

///
/// Returns a typed pointer to the allocated memory if successful, or `None` if
/// allocation or cleanup handler addition fails.
pub fn allocate_unique<T: Sized>(&mut self, value: T) -> Option<&mut T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will return None if the T is already allocated.

/// This function is marked as unsafe because it involves raw pointer manipulation.
unsafe fn allocate_with_cleanup_unique<T: Sized>(&self, value: T) -> Option<NonNull<T>> {
if self.cleanup_lookup::<T>(None).is_some() {
return None;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit confusing, if this happen it looks like a logic error in code (not runtime error)
can we just panic here?

use core::pin::Pin;
use core::task::{Context, Poll};

use crate::core::type_storage::*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeStorage is absent, the commit is broken.

if ctx.launcher.is_none() {
Err(AsyncHandlerError::NoAsyncLauncher)
} else if ctx.launcher.as_ref().unwrap().is_finished() {
let rc = futures::executor::block_on(ctx.launcher.take().unwrap());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems, we use futures just for this one line.
Can we write something like fast_poll_ready() manually and remove the dependency?

Satifying fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> is not easy, but still doable. Using RawWakerVTable and RawWaker::new() with stub methods.

I wander is it something worth doing and what is the trade-off.

/// The return type of the asynchronous worker function.
type ReturnType: HttpHandlerReturn;
/// The asynchronous worker function to be implemented.
fn worker(request: &mut Request) -> impl Future<Output = Self::ReturnType>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what prevents us from using async fn worker() here? Given that is should be available since 1.75.

NGX_TEST_FILES: examples/t
NGX_TEST_GLOBALS_DYNAMIC: >-
load_module ${{ github.workspace }}/nginx/objs/ngx_http_async_module.so;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we have native async support now, we should remove half-working ngx_http_async_module example and remove tokio dependency.

EOF

$t->write_file('index.html', '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file is not needed here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, more tests are needed, including negative ones.

}

impl<'sr> core::future::Future for AsyncSubRequest<'sr> {
type Output = (ngx_int_t, Option<&'sr Request>);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not to make a Result<> value here too?

diff --git a/examples/async_request.rs b/examples/async_request.rs
index a616b7d3..507268a3 100644
--- a/examples/async_request.rs
+++ b/examples/async_request.rs
@@ -63,20 +63,12 @@ impl AsyncHandler for SampleAsyncHandler {
         let request_ptr: *mut ngx_http_request_t = request.as_mut();
 
         let fut = AsyncSubRequestBuilder::new("/proxy")
-            //.args("arg1=val1&arg2=val2")
+            .args("arg1=val1&arg2=val2")
             .in_memory()
             .waited()
             .build(request)?;
 
-        let subrc = fut.await;
-
-        ngx_log_error!(nginx_sys::NGX_LOG_INFO, log, "Subrequest rc {}", subrc.0);
-
-        if subrc.0 != nginx_sys::NGX_OK as _ || subrc.1.is_none() {
-            return Err(SampleAsyncHandlerError::from(subrc.0));
-        }
-
-        let sr = subrc.1.unwrap();
+        let sr = fut.await?;
 
         ngx_log_error!(
             nginx_sys::NGX_LOG_INFO,
diff --git a/nginx-src/nginx b/nginx-src/nginx
index 95078974..481d28cb 160000
--- a/nginx-src/nginx
+++ b/nginx-src/nginx
@@ -1 +1 @@
-Subproject commit 95078974cfe998ec4095ac2c504c5c92bbc9a326
+Subproject commit 481d28cb4e04c8096b9b6134856891dc52ecc68f
diff --git a/src/http/async_request.rs b/src/http/async_request.rs
index 83ac81b4..82355e87 100644
--- a/src/http/async_request.rs
+++ b/src/http/async_request.rs
@@ -317,7 +317,7 @@ impl<'sr> AsyncSubRequest<'sr> {
 }
 
 impl<'sr> core::future::Future for AsyncSubRequest<'sr> {
-    type Output = (ngx_int_t, Option<&'sr Request>);
+    type Output = Result<&'sr Request, ngx_int_t>;
 
     fn poll(
         self: Pin<&mut Self>,
@@ -332,7 +332,7 @@ impl<'sr> core::future::Future for AsyncSubRequest<'sr> {
                 ngx_cycle_log().as_ptr(),
                 "Subrequest not created"
             );
-            return core::task::Poll::Ready((nginx_sys::NGX_ERROR as _, None));
+            return core::task::Poll::Ready(Err(nginx_sys::NGX_ERROR as _));
         }
 
         if this.rc.is_none() {
@@ -341,8 +341,8 @@ impl<'sr> core::future::Future for AsyncSubRequest<'sr> {
         }
 
         // let request: &Request = unsafe { Request::from_ngx_http_request(this.sr.take().unwrap()) };
-        let rc = this.rc.unwrap();
+        // let rc = this.rc.unwrap();
         // ngx_log_debug_http!(request, "subrequest poll: ready({rc})");
-        core::task::Poll::Ready((rc, Some(this.sr.take().unwrap())))
+        core::task::Poll::Ready(Ok(this.sr.take().unwrap()))
     }
 }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants