Files
OpenShell/crates/openshell-sandbox/src/accept_interrupt.rs
Drew Newberry c1f2e7189f feat(isolation): implement the RFC 0012 sandbox architecture (#2942)
* 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>
2026-09-16 00:49:14 +00:00

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(&notification_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 (&notification_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(&registrar.0.workers).contains_key(&3));
drop(registration);
assert!(lock(&registrar.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();
}
}