-
Notifications
You must be signed in to change notification settings - Fork 86
Draft: asynchronous phase handler #237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
4a7588b to
315d8e3
Compare
There was a problem hiding this 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
AsyncHandlertrait and supporting infrastructure for asynchronous HTTP request handling - Adds
RequestContexttrait for type-safe request-specific context data management - Implements
TypeStoragetrait for storing typed values in nginx memory pools - Refactors existing examples to use new
HttpRequestHandlertrait andadd_phase_handlerfunction
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.
315d8e3 to
78a97e6
Compare
78a97e6 to
27338b6
Compare
| (*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) |
There was a problem hiding this comment.
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> { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
src/http/async_request.rs
Outdated
| use core::pin::Pin; | ||
| use core::task::{Context, Poll}; | ||
|
|
||
| use crate::core::type_storage::*; |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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>; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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', ''); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>); |
There was a problem hiding this comment.
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()))
}
}
Draft implementations of the following items are added to SDK: