mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-19 05:50:41 +08:00
* feat(isolation): add RFC 0012 backend contract Signed-off-by: Drew Newberry <385+drew@users.noreply.github.com> * refactor(isolation): name the interface crate explicitly Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): expose trusted host gateway Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(agents): inventory the MXC driver Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): add mediated DNS transport Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): tighten interface error and digest contracts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(isolation): remove unrelated driver inventory Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): define capability-free launch contract Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): seal confirmed boundary state Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): validate confirmation for external backend implementations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): clarify mediated DNS identity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): generalize loopback connector Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): unify typed network mediation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): bind launches to sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(mxc): initialize extended sandbox status Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): add boundary protocol and Linux primitives Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): harden signals and separate process status from transport Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): validate remote confirmation through public contract Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): validate wire state and propagate snapshot failures Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(isolation): import owned agent specification explicitly Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(isolation): describe mediated DNS channel Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): bound mediation attach without nested retries Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): generalize loopback protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): add transport-neutral session authentication Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): separate sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): harden runtime boundary controls Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): add terminal boundary operation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): split supervisor and sandbox runtimes Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): harden boundary isolation and lifecycle ownership Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): reject private root redirects and adopt typed errors Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): preserve accept thread ownership on musl Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(sandbox): isolate credential probes from filtered threads Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): return retained exec exit status to independent waiters Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): bound network mediation and preserve socket authorization Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): bound control admission and retire stale mediation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * ci(e2e): select migrated drivers per stack layer Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(sandbox): implement loopback connector Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(isolation): authenticate the Sandbox Protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(supervisor): rotate launch-scoped authentication Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(sandbox): consume dedicated backend crate Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(sandbox): align topology session fixture Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): align projected bootstrap bundle Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): validate refreshed credentials before rotation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): fail closed across supervisor disconnects Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): repair rebased sandbox CI Signed-off-by: Drew Newberry <anewberry@nvidia.com> * build(runtime): publish separate sandbox and supervisor images Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(config): configure the sandbox runtime image Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): validate sandbox binary linkage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(isolation): use backend and runtime terminology Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(sandbox): use a scratch runtime image Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): refresh schema and dependency policy Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(sandbox): bind reconnects to supervisor process Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs: align runtime split operational guidance Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(security): document Kubernetes runtime RBAC Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): enforce runtime lifecycle invariants Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(compute): identify sandbox start generations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(server): restore sandbox launch sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): support authenticated runtime replacement Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): bind sandbox session successors Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): retry pending sandbox successors Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(vm): run the supervisor outside the guest workload Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): use sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): repair rebase integration Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): use unified build toolchain Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): use sandbox runtime terminology Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): own guest network bootstrap Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): expose guest init version Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): select native supervisor artifacts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): guard guest init Linux symbols Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): scope Linux test imports Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): avoid guest interface casts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): reconcile admitted sandbox identity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): share resolved sandbox identity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): surface host supervisor failures Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): include guest logs on supervisor exit Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): rotate and clean runtime generations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): make sandbox starts generation-aware Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): rotate restored sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): keep shared paths in the base layer Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): bind sandbox session lineage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(docker): isolate workloads behind the host supervisor Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(docker): rotate launch-scoped authentication Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): use sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): use host networking for supervisor Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): preserve host gateway alias resolution Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(docker): use separate sandbox and supervisor images Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): restore startup validation after rebase Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): name the sandbox runtime directly Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): narrow supervisor CA runtime storage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): close companion isolation gaps Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(docker): align mediated network expectations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(docker): exercise mediated network paths Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): attach supervisor to managed network Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): defer supervisor recovery until gateway is ready Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): make sandbox starts generation-aware Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): rotate restored sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): preserve workloads during session rotation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(docker): remove unrelated configuration RFC changes Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): bind sandbox session lineage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(kubernetes): add proxy-pod isolation topology Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(kubernetes): use sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): use stable sandbox service authority Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(kubernetes): split sandbox and supervisor images Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): adapt proxy pods to current runtime APIs Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(kubernetes): describe the single runtime placement Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(kubernetes): simplify sandbox orchestration Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): validate deployment prerequisites Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): update Trivy Helm profile inventory Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(kubernetes): update Trivy scan inventory count Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): reuse preloaded runtime images in e2e Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): type and clean runtime resources Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): make sandbox restarts recoverable Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): rotate restored sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): preserve supervisor egress Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): bind sandbox session lineage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(podman): adopt isolated sandbox and supervisor containers Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): stage bootstrap archives at named volume destinations Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(podman): rotate launch-scoped authentication Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(podman): use sandbox backend protocol Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(podman): use host networking for supervisor Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(podman): split sandbox and supervisor images Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): repair rebase integration Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(podman): name the sandbox runtime directly Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): provision supervisor CA runtime storage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): address isolation review findings Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): inspect Debian supervisor provenance Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): use libpod-compatible tmpfs options Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): bind verified sandbox runtime binary Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): provide external driver data directory Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): start sandbox before joining user namespace Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): separate supervisor user namespace Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): make sandbox starts generation-aware Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): rotate restored sandbox sessions Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): bind sandbox session lineage Signed-off-by: Drew Newberry <anewberry@nvidia.com> * perf(isolation): add TCP and DNS benchmark harnesses Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(perf): align benchmark timing and supported protocols Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(perf): report TCP benchmark metrics accurately Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(perf): cancel failed worker startup Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(docker): build matching local supervisor image Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(podman): make local sandbox smoke test runnable Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): wire local sandbox runtime image Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(kubernetes): narrow sandbox service RBAC Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(ci): validate split runtime artifacts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): harden runtime session handling Signed-off-by: Drew Newberry <anewberry@nvidia.com> * feat(supervisor): add standalone network proxy role Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(rfc): remove implementation companion notes Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(vm): standardize runtime release name Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(vm): pin renamed runtime artifacts Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): persist sandbox runtime identity Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(runtime): restore branch validation Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(isolation): reconcile main after rebase Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(network): close unframed HTTP 1.0 responses Signed-off-by: Drew Newberry <anewberry@nvidia.com> * chore(isolation): preserve upstream OCSF updates Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(security): close credential and TLS replay paths Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(auth): make sandbox refresh retries idempotent Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <385+drew@users.noreply.github.com> Signed-off-by: Drew Newberry <anewberry@nvidia.com>
298 lines
11 KiB
Rust
298 lines
11 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//! Cancellation for broker-owned blocking accepts without changing workload OFDs.
|
|
//!
|
|
//! SIGUSR2 is reserved by the sandbox binary. Its process-global disposition is
|
|
//! necessarily kernel state, not a global application context. All registration,
|
|
//! cancellation and thread ownership state belongs to one broker instance.
|
|
|
|
#![allow(unsafe_code)]
|
|
|
|
use std::collections::HashMap;
|
|
use std::io;
|
|
use std::marker::PhantomData;
|
|
use std::rc::Rc;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::{Arc, Condvar, Mutex};
|
|
use std::time::Duration;
|
|
|
|
const INTERRUPT_SIGNAL: libc::c_int = libc::SIGUSR2;
|
|
const INTERRUPT_INTERVAL: Duration = Duration::from_millis(10);
|
|
|
|
extern "C" fn interrupt_accept(_: libc::c_int) {}
|
|
|
|
fn reserve_signal() -> io::Result<()> {
|
|
// SAFETY: both actions are initialized storage. The no-op handler is
|
|
// async-signal-safe and deliberately omits SA_RESTART so accept returns EINTR.
|
|
unsafe {
|
|
let mut previous: libc::sigaction = std::mem::zeroed();
|
|
if libc::sigaction(INTERRUPT_SIGNAL, std::ptr::null(), &raw mut previous) < 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
if previous.sa_sigaction != libc::SIG_DFL
|
|
&& previous.sa_sigaction != interrupt_accept as *const () as usize
|
|
{
|
|
return Err(io::Error::other(
|
|
"sandbox SIGUSR2 is already reserved by another handler",
|
|
));
|
|
}
|
|
let mut action: libc::sigaction = std::mem::zeroed();
|
|
action.sa_sigaction = interrupt_accept as *const () as usize;
|
|
libc::sigemptyset(&raw mut action.sa_mask);
|
|
if libc::sigaction(INTERRUPT_SIGNAL, &raw const action, std::ptr::null_mut()) < 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct State {
|
|
workers: Mutex<HashMap<u64, RegisteredThread>>,
|
|
changed: Condvar,
|
|
stopped: AtomicBool,
|
|
}
|
|
|
|
// musl represents pthread_t as an opaque pointer, unlike glibc's integer. It
|
|
// is only passed back to pthread_kill, never dereferenced by this module.
|
|
struct RegisteredThread(libc::pthread_t);
|
|
|
|
// SAFETY: POSIX permits signaling a live pthread from another thread. The
|
|
// handle is accessed only under State::workers, and the owning worker removes
|
|
// its registration under that same mutex before returning. AcceptRegistration
|
|
// cannot move to another thread, so its Drop cannot outlive the owning worker.
|
|
unsafe impl Send for RegisteredThread {}
|
|
|
|
pub struct AcceptMonitor {
|
|
state: Arc<State>,
|
|
thread: Option<std::thread::JoinHandle<()>>,
|
|
}
|
|
|
|
impl AcceptMonitor {
|
|
pub(crate) fn start(valid: impl Fn(u64) -> bool + Send + 'static) -> io::Result<Self> {
|
|
reserve_signal()?;
|
|
let state = Arc::new(State::default());
|
|
let worker_state = state.clone();
|
|
let thread = std::thread::Builder::new()
|
|
.name("openshell-accept-cancellation".into())
|
|
.spawn(move || monitor(&worker_state, valid))?;
|
|
Ok(Self {
|
|
state,
|
|
thread: Some(thread),
|
|
})
|
|
}
|
|
|
|
pub(crate) fn registrar(&self) -> AcceptRegistrar {
|
|
AcceptRegistrar(self.state.clone())
|
|
}
|
|
}
|
|
|
|
impl Drop for AcceptMonitor {
|
|
fn drop(&mut self) {
|
|
let workers = lock(&self.state.workers);
|
|
self.state.stopped.store(true, Ordering::Release);
|
|
self.state.changed.notify_all();
|
|
drop(workers);
|
|
// The monitor keeps interrupting registered workers during shutdown.
|
|
// Registrations are removed before their threads can exit/reuse IDs.
|
|
if let Some(thread) = self.thread.take() {
|
|
let _ = thread.join();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct AcceptRegistrar(Arc<State>);
|
|
|
|
impl AcceptRegistrar {
|
|
pub(crate) fn register(&self, notification_id: u64) -> io::Result<AcceptRegistration> {
|
|
// SAFETY: this changes only the current broker worker's signal mask.
|
|
// Workload launchers do not inherit this mask; exec resets the handler.
|
|
let thread = unsafe {
|
|
let mut mask: libc::sigset_t = std::mem::zeroed();
|
|
libc::sigemptyset(&raw mut mask);
|
|
libc::sigaddset(&raw mut mask, INTERRUPT_SIGNAL);
|
|
let error =
|
|
libc::pthread_sigmask(libc::SIG_UNBLOCK, &raw const mask, std::ptr::null_mut());
|
|
if error != 0 {
|
|
return Err(io::Error::from_raw_os_error(error));
|
|
}
|
|
libc::pthread_self()
|
|
};
|
|
let mut workers = lock(&self.0.workers);
|
|
if self.0.stopped.load(Ordering::Acquire) {
|
|
return Err(io::Error::from_raw_os_error(libc::ECANCELED));
|
|
}
|
|
if workers.contains_key(¬ification_id) {
|
|
return Err(io::Error::other(
|
|
"duplicate accept notification registration",
|
|
));
|
|
}
|
|
workers.insert(notification_id, RegisteredThread(thread));
|
|
self.0.changed.notify_one();
|
|
Ok(AcceptRegistration {
|
|
state: self.0.clone(),
|
|
notification_id,
|
|
owning_thread: PhantomData,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct AcceptRegistration {
|
|
state: Arc<State>,
|
|
notification_id: u64,
|
|
// Drop must run on the registering thread before its pthread_t can expire.
|
|
// No Rc is allocated; this marker makes the guard neither Send nor Sync.
|
|
owning_thread: PhantomData<Rc<()>>,
|
|
}
|
|
|
|
impl AcceptRegistration {
|
|
pub(crate) fn ensure_running(&self) -> io::Result<()> {
|
|
if self.state.stopped.load(Ordering::Acquire) {
|
|
Err(io::Error::from_raw_os_error(libc::ECANCELED))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Drop for AcceptRegistration {
|
|
fn drop(&mut self) {
|
|
lock(&self.state.workers).remove(&self.notification_id);
|
|
self.state.changed.notify_one();
|
|
}
|
|
}
|
|
|
|
fn monitor(state: &State, valid: impl Fn(u64) -> bool) {
|
|
let mut workers = lock(&state.workers);
|
|
loop {
|
|
let stopped = state.stopped.load(Ordering::Acquire);
|
|
if stopped && workers.is_empty() {
|
|
return;
|
|
}
|
|
for (¬ification_id, thread) in &*workers {
|
|
if stopped || !valid(notification_id) {
|
|
// SAFETY: the registration lock pins this live pthread_t.
|
|
// Repeated interrupts close the check-to-accept race: a signal
|
|
// received before accept cannot leave a later accept stranded.
|
|
let _ = unsafe { libc::pthread_kill(thread.0, INTERRUPT_SIGNAL) };
|
|
}
|
|
}
|
|
workers = if workers.is_empty() {
|
|
state
|
|
.changed
|
|
.wait(workers)
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
} else {
|
|
state
|
|
.changed
|
|
.wait_timeout(workers, INTERRUPT_INTERVAL)
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
.0
|
|
};
|
|
}
|
|
}
|
|
|
|
fn lock<T>(mutex: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
|
|
mutex
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::net::{TcpListener, TcpStream};
|
|
use std::os::fd::AsRawFd;
|
|
|
|
#[test]
|
|
fn registrar_crosses_threads_but_registration_ends_before_worker_exit() {
|
|
fn assert_send_sync<T: Send + Sync>() {}
|
|
assert_send_sync::<AcceptRegistrar>();
|
|
|
|
let monitor = AcceptMonitor::start(|_| true).unwrap();
|
|
let registrar = monitor.registrar();
|
|
std::thread::spawn(move || {
|
|
let registration = registrar.register(3).unwrap();
|
|
assert!(registrar.register(3).is_err());
|
|
assert!(lock(®istrar.0.workers).contains_key(&3));
|
|
drop(registration);
|
|
assert!(lock(®istrar.0.workers).is_empty());
|
|
})
|
|
.join()
|
|
.unwrap();
|
|
assert!(lock(&monitor.state.workers).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn cancellation_interrupts_competing_accept_after_readiness_was_consumed() {
|
|
let valid = Arc::new(AtomicBool::new(true));
|
|
let monitored = valid.clone();
|
|
let monitor = AcceptMonitor::start(move |_| monitored.load(Ordering::Acquire)).unwrap();
|
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
let client = TcpStream::connect(listener.local_addr().unwrap()).unwrap();
|
|
// Both contenders could observe this same readable listener. Consume
|
|
// its only connection before the second contender actually accepts.
|
|
let accepted = listener.accept().unwrap();
|
|
let registrar = monitor.registrar();
|
|
let (ready_tx, ready_rx) = std::sync::mpsc::channel();
|
|
let (done_tx, done_rx) = std::sync::mpsc::channel();
|
|
let worker = std::thread::spawn(move || {
|
|
let registration = registrar.register(1).unwrap();
|
|
ready_tx.send(()).unwrap();
|
|
// SAFETY: the listener is live and null address outputs are valid.
|
|
// Use the syscall directly: std::net retries EINTR internally.
|
|
let result = unsafe {
|
|
libc::accept4(
|
|
listener.as_raw_fd(),
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
libc::SOCK_CLOEXEC,
|
|
)
|
|
};
|
|
assert_eq!(result, -1);
|
|
let error = io::Error::last_os_error();
|
|
assert_eq!(error.kind(), io::ErrorKind::Interrupted);
|
|
drop(registration);
|
|
done_tx.send(()).unwrap();
|
|
});
|
|
ready_rx.recv_timeout(Duration::from_secs(2)).unwrap();
|
|
valid.store(false, Ordering::Release);
|
|
done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
|
|
worker.join().unwrap();
|
|
drop((accepted, client, monitor));
|
|
}
|
|
|
|
#[test]
|
|
fn shutdown_interrupts_registered_accepts_and_reclaims_the_monitor() {
|
|
let monitor = AcceptMonitor::start(|_| true).unwrap();
|
|
let registrar = monitor.registrar();
|
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
let (ready_tx, ready_rx) = std::sync::mpsc::channel();
|
|
let (done_tx, done_rx) = std::sync::mpsc::channel();
|
|
let worker = std::thread::spawn(move || {
|
|
let registration = registrar.register(2).unwrap();
|
|
ready_tx.send(()).unwrap();
|
|
// SAFETY: owned listener and optional null address outputs.
|
|
let result = unsafe {
|
|
libc::accept4(
|
|
listener.as_raw_fd(),
|
|
std::ptr::null_mut(),
|
|
std::ptr::null_mut(),
|
|
libc::SOCK_CLOEXEC,
|
|
)
|
|
};
|
|
assert_eq!(result, -1);
|
|
assert!(registration.ensure_running().is_err());
|
|
drop(registration);
|
|
done_tx.send(()).unwrap();
|
|
});
|
|
ready_rx.recv_timeout(Duration::from_secs(2)).unwrap();
|
|
let shutdown = std::thread::spawn(move || drop(monitor));
|
|
done_rx.recv_timeout(Duration::from_secs(2)).unwrap();
|
|
worker.join().unwrap();
|
|
shutdown.join().unwrap();
|
|
}
|
|
}
|