fix(core): linearize ABI connect cancellation
Verify Data Plane / gateway (push) Successful in 4m48s

This commit is contained in:
sechmachine
2026-08-12 17:29:30 +07:00
parent 5cc2d120e7
commit 7947ebcc75
+182 -40
View File
@@ -4,7 +4,7 @@ use std::ffi::c_void;
use std::mem::size_of; use std::mem::size_of;
use std::panic::{catch_unwind, AssertUnwindSafe}; use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr::{self, NonNull}; use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicU64, AtomicU8, AtomicUsize, Ordering};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, SyncSender, TrySendError}; use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, SyncSender, TrySendError};
use std::sync::{Arc, Mutex, MutexGuard, OnceLock, TryLockError}; use std::sync::{Arc, Mutex, MutexGuard, OnceLock, TryLockError};
use std::thread::{self, JoinHandle}; use std::thread::{self, JoinHandle};
@@ -44,6 +44,10 @@ const MAX_CONNECT_BYTES: usize = 1024 * 1024;
const CALLBACK_QUEUE_CAPACITY: usize = 64; const CALLBACK_QUEUE_CAPACITY: usize = 64;
const CONTROL_QUEUE_CAPACITY: usize = 64; const CONTROL_QUEUE_CAPACITY: usize = 64;
const TRANSITION_OPEN: u8 = 0;
const TRANSITION_CONNECTED: u8 = 1;
const TRANSITION_CANCELLED: u8 = 2;
static NEXT_ID: AtomicUsize = AtomicUsize::new(1); static NEXT_ID: AtomicUsize = AtomicUsize::new(1);
static HANDLES: OnceLock<Mutex<HashMap<usize, Arc<CoreInner>>>> = OnceLock::new(); static HANDLES: OnceLock<Mutex<HashMap<usize, Arc<CoreInner>>>> = OnceLock::new();
@@ -53,6 +57,12 @@ static CANCEL_ADMISSION_HOOK: OnceLock<Mutex<Option<CancelAdmissionHook>>> = Onc
#[cfg(test)] #[cfg(test)]
static FINAL_CONNECT_HOOK: OnceLock<Mutex<Option<FinalConnectHook>>> = OnceLock::new(); static FINAL_CONNECT_HOOK: OnceLock<Mutex<Option<FinalConnectHook>>> = OnceLock::new();
#[cfg(test)]
static INITIAL_CONNECT_HOOK: OnceLock<Mutex<Option<ConnectTransitionHook>>> = OnceLock::new();
#[cfg(test)]
static FINAL_PUBLISH_HOOK: OnceLock<Mutex<Option<ConnectTransitionHook>>> = OnceLock::new();
#[cfg(test)] #[cfg(test)]
struct CancelAdmissionHook { struct CancelAdmissionHook {
core: usize, core: usize,
@@ -67,6 +77,13 @@ struct FinalConnectHook {
resume: Receiver<()>, resume: Receiver<()>,
} }
#[cfg(test)]
struct ConnectTransitionHook {
core: usize,
reached: Sender<()>,
resume: Receiver<()>,
}
thread_local! { thread_local! {
static CALLBACK_MODE: Cell<CallbackMode> = const { Cell::new(CallbackMode::None) }; static CALLBACK_MODE: Cell<CallbackMode> = const { Cell::new(CallbackMode::None) };
} }
@@ -228,7 +245,7 @@ struct CoreInner {
worker_done: Mutex<Receiver<()>>, worker_done: Mutex<Receiver<()>>,
worker: Mutex<Option<JoinHandle<()>>>, worker: Mutex<Option<JoinHandle<()>>>,
in_flight: AtomicUsize, in_flight: AtomicUsize,
cancelled: AtomicBool, transition: AtomicU8,
cancellation_emitted: AtomicBool, cancellation_emitted: AtomicBool,
freeing: AtomicBool, freeing: AtomicBool,
destroying: AtomicBool, destroying: AtomicBool,
@@ -309,6 +326,26 @@ fn pause_before_connected(core: *mut CoreHandle) {
} }
} }
#[cfg(test)]
fn pause_connect_transition(
hooks: &OnceLock<Mutex<Option<ConnectTransitionHook>>>,
core: *mut CoreHandle,
) {
let hook = hooks.get_or_init(|| Mutex::new(None));
let selected = {
let mut hook = lock(hook);
if hook.as_ref().is_some_and(|hook| hook.core == core as usize) {
hook.take()
} else {
None
}
};
if let Some(hook) = selected {
hook.reached.send(()).expect("announce connect transition");
hook.resume.recv().expect("resume connect transition");
}
}
fn status(error: CoreError) -> u32 { fn status(error: CoreError) -> u32 {
match error { match error {
CoreError::QueueFull => QUEUE_FULL, CoreError::QueueFull => QUEUE_FULL,
@@ -450,8 +487,32 @@ fn apply_cancellation(inner: &CoreInner, state: &mut SessionState) {
} }
} }
fn cancellation_admitted(inner: &CoreInner) -> bool {
inner.transition.load(Ordering::Acquire) == TRANSITION_CANCELLED
}
fn commit_cancellation(inner: &CoreInner) -> u32 {
let mut state = lock(&inner.state);
apply_cancellation(inner, &mut state);
CANCELLED
}
fn claim_connected(inner: &CoreInner) -> bool {
inner
.transition
.compare_exchange(
TRANSITION_OPEN,
TRANSITION_CONNECTED,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
}
fn cancel_inner(inner: &CoreInner) { fn cancel_inner(inner: &CoreInner) {
inner.cancelled.store(true, Ordering::Release); inner
.transition
.store(TRANSITION_CANCELLED, Ordering::Release);
match inner.state.try_lock() { match inner.state.try_lock() {
Ok(mut state) => apply_cancellation(inner, &mut state), Ok(mut state) => apply_cancellation(inner, &mut state),
Err(TryLockError::Poisoned(error)) => { Err(TryLockError::Poisoned(error)) => {
@@ -622,7 +683,7 @@ unsafe extern "C" fn verse_core_create_v1(
worker_done: Mutex::new(done_rx), worker_done: Mutex::new(done_rx),
worker: Mutex::new(Some(worker)), worker: Mutex::new(Some(worker)),
in_flight: AtomicUsize::new(0), in_flight: AtomicUsize::new(0),
cancelled: AtomicBool::new(false), transition: AtomicU8::new(TRANSITION_OPEN),
cancellation_emitted: AtomicBool::new(false), cancellation_emitted: AtomicBool::new(false),
freeing: AtomicBool::new(false), freeing: AtomicBool::new(false),
destroying: AtomicBool::new(false), destroying: AtomicBool::new(false),
@@ -676,10 +737,12 @@ unsafe extern "C" fn verse_core_connect_v1(
} }
{ {
let mut state = lock(&handle.state); let mut state = lock(&handle.state);
if handle.cancelled.load(Ordering::Acquire) { if cancellation_admitted(&handle) {
apply_cancellation(&handle, &mut state); apply_cancellation(&handle, &mut state);
return CANCELLED; return CANCELLED;
} }
#[cfg(test)]
pause_connect_transition(&INITIAL_CONNECT_HOOK, core);
if state.lifecycle != Lifecycle::Created { if state.lifecycle != Lifecycle::Created {
return INVALID_STATE; return INVALID_STATE;
} }
@@ -696,8 +759,8 @@ unsafe extern "C" fn verse_core_connect_v1(
cancel_inner(&handle); cancel_inner(&handle);
return admission; return admission;
} }
if handle.cancelled.load(Ordering::Acquire) { if cancellation_admitted(&handle) {
return CANCELLED; return commit_cancellation(&handle);
} }
let tls = call_signer( let tls = call_signer(
SignerPurpose::Tls, SignerPurpose::Tls,
@@ -711,30 +774,31 @@ unsafe extern "C" fn verse_core_connect_v1(
} }
{ {
let mut state = lock(&handle.state); let mut state = lock(&handle.state);
if handle.cancelled.load(Ordering::Acquire) || state.lifecycle == Lifecycle::Destroying if cancellation_admitted(&handle) || state.lifecycle == Lifecycle::Destroying {
{
apply_cancellation(&handle, &mut state); apply_cancellation(&handle, &mut state);
return CANCELLED; return CANCELLED;
} }
#[cfg(test)] #[cfg(test)]
pause_before_connected(core); pause_before_connected(core);
if handle.cancelled.load(Ordering::Acquire) { if cancellation_admitted(&handle) {
apply_cancellation(&handle, &mut state);
return CANCELLED;
}
#[cfg(test)]
pause_connect_transition(&FINAL_PUBLISH_HOOK, core);
if !claim_connected(&handle) {
apply_cancellation(&handle, &mut state); apply_cancellation(&handle, &mut state);
return CANCELLED; return CANCELLED;
} }
state.lifecycle = Lifecycle::Connected; state.lifecycle = Lifecycle::Connected;
enqueue_callback_under_state_lock(&handle, CallbackEvent::State(STATE_CONNECTED)); enqueue_callback_under_state_lock(&handle, CallbackEvent::State(STATE_CONNECTED));
if handle.cancelled.load(Ordering::Acquire) { if cancellation_admitted(&handle) {
apply_cancellation(&handle, &mut state); apply_cancellation(&handle, &mut state);
return CANCELLED; return CANCELLED;
} }
} }
if handle.cancelled.load(Ordering::Acquire) if cancellation_admitted(&handle) && !handle.cancellation_emitted.load(Ordering::Acquire) {
&& !handle.cancellation_emitted.load(Ordering::Acquire) return commit_cancellation(&handle);
{
let mut state = lock(&handle.state);
apply_cancellation(&handle, &mut state);
return CANCELLED;
} }
OK OK
}) })
@@ -760,7 +824,7 @@ unsafe extern "C" fn verse_core_send_input_v1(
Err(error) => return error, Err(error) => return error,
}; };
let mut state = lock(&handle.state); let mut state = lock(&handle.state);
if handle.cancelled.load(Ordering::Acquire) { if cancellation_admitted(&handle) {
apply_cancellation(&handle, &mut state); apply_cancellation(&handle, &mut state);
return CANCELLED; return CANCELLED;
} }
@@ -795,7 +859,7 @@ unsafe extern "C" fn verse_core_request_idr_v1(core: *mut CoreHandle) -> u32 {
Err(error) => return error, Err(error) => return error,
}; };
let mut state = lock(&handle.state); let mut state = lock(&handle.state);
if handle.cancelled.load(Ordering::Acquire) { if cancellation_admitted(&handle) {
apply_cancellation(&handle, &mut state); apply_cancellation(&handle, &mut state);
return CANCELLED; return CANCELLED;
} }
@@ -921,7 +985,9 @@ unsafe extern "C" fn verse_core_destroy_v1(core: *mut CoreHandle, timeout_ms: u3
return BUSY; return BUSY;
} }
inner.callbacks_closed.store(true, Ordering::Release); inner.callbacks_closed.store(true, Ordering::Release);
inner.cancelled.store(true, Ordering::Release); inner
.transition
.store(TRANSITION_CANCELLED, Ordering::Release);
{ {
if Instant::now() > deadline { if Instant::now() > deadline {
return destroy_busy(&inner); return destroy_busy(&inner);
@@ -983,6 +1049,7 @@ mod tests {
use std::ffi::c_void; use std::ffi::c_void;
use std::mem::size_of; use std::mem::size_of;
use std::ptr; use std::ptr;
use std::sync::atomic::Ordering;
use std::sync::mpsc; use std::sync::mpsc;
use std::sync::{Arc, Condvar, Mutex}; use std::sync::{Arc, Condvar, Mutex};
use std::thread; use std::thread;
@@ -990,9 +1057,9 @@ mod tests {
use super::{ use super::{
ffi_boundary, handles, lock, BytesView, CancelAdmissionHook, Config, ConnectRequest, ffi_boundary, handles, lock, BytesView, CancelAdmissionHook, Config, ConnectRequest,
CoreHandle, FinalConnectHook, SignFn, StateEvent, StateFn, BUSY, CANCELLED, ConnectTransitionHook, CoreHandle, FinalConnectHook, Lifecycle, SignFn, StateEvent,
CANCEL_ADMISSION_HOOK, FINAL_CONNECT_HOOK, INTERNAL, OK, STATE_CANCELLED, STATE_CONNECTED, StateFn, BUSY, CANCELLED, CANCEL_ADMISSION_HOOK, FINAL_CONNECT_HOOK, FINAL_PUBLISH_HOOK,
STATE_CONNECTING, INITIAL_CONNECT_HOOK, INTERNAL, OK, STATE_CANCELLED, STATE_CONNECTED, STATE_CONNECTING,
}; };
const MANIFEST: &[u8] = br#"{ const MANIFEST: &[u8] = br#"{
@@ -1071,6 +1138,26 @@ mod tests {
} }
} }
fn spawn_connect(core: *mut CoreHandle) -> thread::JoinHandle<u32> {
let address = core as usize;
thread::spawn(move || {
let request = ConnectRequest {
struct_size: u32::try_from(size_of::<ConnectRequest>()).expect("request size"),
abi_version: 1,
manifest_json: BytesView {
data: MANIFEST.as_ptr(),
length: MANIFEST.len(),
},
tunnel_credential_json: BytesView {
data: CREDENTIAL.as_ptr(),
length: CREDENTIAL.len(),
},
};
// Test invariant: the request and static byte views remain valid for the call.
unsafe { super::verse_core_connect_v1(address as *mut CoreHandle, &raw const request) }
})
}
fn inner(core: *mut CoreHandle) -> Arc<super::CoreInner> { fn inner(core: *mut CoreHandle) -> Arc<super::CoreInner> {
lock(handles()) lock(handles())
.get(&(core as usize)) .get(&(core as usize))
@@ -1204,23 +1291,7 @@ mod tests {
resume: resume_rx, resume: resume_rx,
}); });
let address = core as usize; let connect = spawn_connect(core);
let connect = thread::spawn(move || {
let request = ConnectRequest {
struct_size: u32::try_from(size_of::<ConnectRequest>()).expect("request size"),
abi_version: 1,
manifest_json: BytesView {
data: MANIFEST.as_ptr(),
length: MANIFEST.len(),
},
tunnel_credential_json: BytesView {
data: CREDENTIAL.as_ptr(),
length: CREDENTIAL.len(),
},
};
// Test invariant: the request and static byte views remain valid for the call.
unsafe { super::verse_core_connect_v1(address as *mut CoreHandle, &raw const request) }
});
reached_rx.recv().expect("final connect reached"); reached_rx.recv().expect("final connect reached");
assert_eq!(unsafe { super::verse_core_cancel_v1(core) }, OK); assert_eq!(unsafe { super::verse_core_cancel_v1(core) }, OK);
@@ -1238,4 +1309,75 @@ mod tests {
); );
assert_eq!(connect_status, CANCELLED); assert_eq!(connect_status, CANCELLED);
} }
#[test]
fn cancellation_during_initial_connect_is_committed_and_emitted() {
let context = Box::new(CallbackContext::default());
let context_pointer = ptr::from_ref(&*context).cast_mut().cast::<c_void>();
let core = create_core_with_callback(context_pointer, Some(record_state));
let inner = inner(core);
let (reached_tx, reached_rx) = mpsc::channel();
let (resume_tx, resume_rx) = mpsc::channel();
*lock(INITIAL_CONNECT_HOOK.get_or_init(|| Mutex::new(None))) =
Some(ConnectTransitionHook {
core: core as usize,
reached: reached_tx,
resume: resume_rx,
});
let connect = spawn_connect(core);
reached_rx.recv().expect("initial connect reached");
assert_eq!(unsafe { super::verse_core_cancel_v1(core) }, OK);
resume_tx.send(()).expect("resume initial connect");
let connect_status = connect.join().expect("connect caller");
let cancellation_committed = lock(&inner.state).lifecycle == Lifecycle::Cancelled;
let cancellation_emitted = inner.cancellation_emitted.load(Ordering::Acquire);
if cancellation_emitted {
wait_for_state(&context, STATE_CANCELLED);
} else {
assert_eq!(unsafe { super::verse_core_request_idr_v1(core) }, CANCELLED);
wait_for_state(&context, STATE_CANCELLED);
}
assert_eq!(unsafe { super::verse_core_destroy_v1(core, 2_000) }, OK);
assert_eq!(connect_status, CANCELLED);
assert!(
cancellation_committed,
"connect returned without committing cancellation"
);
assert!(
cancellation_emitted,
"connect returned without emitting CANCELLED"
);
assert_eq!(
context.states.lock().expect("states lock").as_slice(),
[STATE_CONNECTING, STATE_CANCELLED]
);
}
#[test]
fn cancellation_wins_the_final_check_to_publish_window() {
let context = Box::new(CallbackContext::default());
let context_pointer = ptr::from_ref(&*context).cast_mut().cast::<c_void>();
let core = create_core_with_callback(context_pointer, Some(record_state));
let (reached_tx, reached_rx) = mpsc::channel();
let (resume_tx, resume_rx) = mpsc::channel();
*lock(FINAL_PUBLISH_HOOK.get_or_init(|| Mutex::new(None))) = Some(ConnectTransitionHook {
core: core as usize,
reached: reached_tx,
resume: resume_rx,
});
let connect = spawn_connect(core);
reached_rx.recv().expect("final publish reached");
assert_eq!(unsafe { super::verse_core_cancel_v1(core) }, OK);
resume_tx.send(()).expect("resume final publish");
assert_eq!(connect.join().expect("connect caller"), CANCELLED);
wait_for_state(&context, STATE_CANCELLED);
assert_eq!(unsafe { super::verse_core_destroy_v1(core, 2_000) }, OK);
assert_eq!(
context.states.lock().expect("states lock").as_slice(),
[STATE_CONNECTING, STATE_CANCELLED]
);
}
} }