-
Notifications
You must be signed in to change notification settings - Fork 618
Fix Rust connection callback context cleanup and add regression test #5618
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?
Changes from 4 commits
0696c2e
d0e2e7b
67b9254
35e659a
537c699
3c88a5d
58d833d
aff92eb
cd2ee06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -697,15 +697,15 @@ macro_rules! define_quic_handle_ctx_fn { | |
| // This is used by Connection and Stream, but not Listener. | ||
| #[allow(dead_code)] | ||
| fn consume_callback_ctx(&self) { | ||
| let res = unsafe { self.get_callback_ctx() }; | ||
| if res.is_some() { | ||
| unsafe { self.set_context(std::ptr::null_mut()) }; | ||
| if let Some(ctx) = unsafe { self.take_callback_ctx() } { | ||
| std::mem::drop(ctx); | ||
| } | ||
| } | ||
|
|
||
| /// # Safety | ||
| /// Caller is responsible for clearing the context if needed. | ||
| /// This does not clear the ctx. | ||
| #[allow(dead_code)] | ||
| unsafe fn get_callback_ctx(&self) -> Option<Box<Box<$callback_type>>> { | ||
| let ctx = self.get_context(); | ||
| if !ctx.is_null() { | ||
|
|
@@ -714,6 +714,18 @@ macro_rules! define_quic_handle_ctx_fn { | |
| None | ||
| } | ||
| } | ||
|
|
||
| /// # Safety | ||
| /// Removes the callback context from the handle and returns it. | ||
LindonAliu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| unsafe fn take_callback_ctx(&self) -> Option<Box<Box<$callback_type>>> { | ||
| let ctx = self.get_context(); | ||
| if ctx.is_null() { | ||
| None | ||
| } else { | ||
| unsafe { self.set_context(std::ptr::null_mut()) }; | ||
| Some(unsafe { Box::from_raw(ctx as *mut Box<$callback_type>) }) | ||
| } | ||
| } | ||
| } | ||
| }; | ||
| } | ||
|
|
@@ -861,17 +873,29 @@ extern "C" fn raw_conn_callback( | |
| context: *mut c_void, | ||
| event: *mut ffi::QUIC_CONNECTION_EVENT, | ||
| ) -> QUIC_STATUS { | ||
| let conn = unsafe { ConnectionRef::from_raw(connection) }; | ||
| let f = unsafe { | ||
| (context as *mut Box<ConnectionCallback>) | ||
| .as_mut() // allow mutation | ||
| .expect("cannot get ConnectionCallback from ctx") | ||
| let event_ref = unsafe { event.as_ref().expect("cannot get connection event") }; | ||
| let cleanup_ctx = | ||
| event_ref.Type == ffi::QUIC_CONNECTION_EVENT_TYPE_QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE; | ||
|
|
||
| let status = match unsafe { (context as *mut Box<ConnectionCallback>).as_mut() } { | ||
| Some(f) => { | ||
| let event = ConnectionEvent::from(event_ref); | ||
| let conn = unsafe { ConnectionRef::from_raw(connection) }; | ||
| match f(conn, event) { | ||
| Ok(_) => StatusCode::QUIC_STATUS_SUCCESS.into(), | ||
| Err(e) => e.0, | ||
| } | ||
| } | ||
| // Context already cleaned (e.g. after ShutdownComplete). Nothing to do. | ||
| None => StatusCode::QUIC_STATUS_SUCCESS.into(), | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this scenario is possible assuming the logic is sound. ShutdownComplete is the last event delivered to an application by MsQuic (see QUIC_CONNECTION_EVENT.md), and the rust wrapper own the context so it should never be freed before ShutdownComplete. We can
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the review! I found a case where the context can be
This is demonstrated in the test where we
I lean towards option 2, dropping the context after |
||
| }; | ||
| let event = ConnectionEvent::from(unsafe { event.as_ref().unwrap() }); | ||
| match f(conn, event) { | ||
| Ok(_) => StatusCode::QUIC_STATUS_SUCCESS.into(), | ||
| Err(e) => e.0, | ||
|
|
||
| if cleanup_ctx { | ||
LindonAliu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let conn = unsafe { ConnectionRef::from_raw(connection) }; | ||
| conn.consume_callback_ctx(); | ||
| } | ||
|
|
||
| status | ||
| } | ||
|
|
||
| impl Connection { | ||
|
|
@@ -920,7 +944,7 @@ impl Connection { | |
| fn close_inner(&self) { | ||
| if !self.handle.is_null() { | ||
| // get the context and drop it after handle close. | ||
| let ctx = unsafe { self.get_callback_ctx() }; | ||
| let ctx = unsafe { self.take_callback_ctx() }; | ||
| unsafe { | ||
| Api::ffi_ref().ConnectionClose.unwrap()(self.handle); | ||
| } | ||
|
|
@@ -1102,7 +1126,7 @@ impl Listener { | |
| fn close_inner(&self) { | ||
| if !self.handle.is_null() { | ||
| // consume the context and drop it after handle close. | ||
| let ctx = unsafe { self.get_callback_ctx() }; | ||
| let ctx = unsafe { self.take_callback_ctx() }; | ||
| unsafe { | ||
| Api::ffi_ref().ListenerClose.unwrap()(self.handle); | ||
| } | ||
|
|
@@ -1179,7 +1203,7 @@ impl Stream { | |
| pub fn close_inner(&self) { | ||
| if !self.handle.is_null() { | ||
| // consume the context and drop it after handle close. | ||
| let ctx = unsafe { self.get_callback_ctx() }; | ||
| let ctx = unsafe { self.take_callback_ctx() }; | ||
| unsafe { | ||
| Api::ffi_ref().StreamClose.unwrap()(self.handle); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.