use core::future::poll_fn;
use core::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[cfg(ring)]
use super::Driver;
use super::Shard;
use super::backoff::Backoff;
use crate::io::Source;
impl Shard {
pub(crate) fn block_on_then_leave<F: core::future::Future>(&self, future: F) -> F::Output {
let mut future = core::pin::pin!(future);
crate::rt::context::deeper(self.limits.max_nesting, || {
let signal = self.signal();
let output = self.run_then_leave(future.as_mut(), &signal);
self.keep_signal(signal);
output
})
}
#[inline]
pub(crate) fn leave(&self) {
if !self.sources.borrow().is_untouched() {
self.let_go_of_sources();
}
if self.nothing_else_to_let_go() {
return;
}
self.let_go_of_the_rest();
}
#[inline(always)]
fn nothing_else_to_let_go(&self) -> bool {
#[cfg(ring)]
if let Driver::Ring(ring) = &self.driver {
return ring.in_flight() == 0 && self.shared.io_ops.is_empty();
}
!cfg!(port) && self.children.borrow().is_untouched()
}
#[inline(never)]
fn let_go_of_sources(&self) {
let gone: Vec<(u64, Arc<Source>)> = self.sources.borrow_mut().drain_tokens().collect();
evict_all(&gone, self.shared_addr());
give_up_claims(&gone, &self.shared);
drain_unsent(&gone, |source| crate::rt::linger::hand(drain(source)));
}
#[inline(never)]
fn let_go_of_the_rest(&self) {
self.let_go_of_children();
#[cfg(ring)]
if let Driver::Ring(ring) = &self.driver {
if self.shared.io_ops.holds_any() {
self.apply_io_ops();
}
if ring.in_flight() != 0 {
ring.settle(|| self.apply_io_ops());
}
}
#[cfg(port)]
if !self.hand_off_port() {
self.settle_port();
}
}
}
fn drop_locals(values: Vec<Box<dyn core::any::Any + Send>>) {
for value in values {
if let Some(panic) = crate::rt::caught::catch(|| drop(value)) {
crate::rt::caught::lost(
format_args!("a shard-local value panicked as its worker stopped"),
panic,
);
}
}
}
fn evict_all(gone: &[(u64, Arc<Source>)], home: usize) {
for (_, source) in gone {
source.evict();
}
wait_for_evictions(gone, home);
}
fn give_up_claims(gone: &[(u64, Arc<Source>)], home: &Arc<crate::rt::shard::Shared>) {
for (_, source) in gone {
source.give_up_claims(home);
}
}
fn drain_unsent(gone: &[(u64, Arc<Source>)], spawn: impl Fn(Arc<Source>)) {
gone.iter()
.filter(|(_, source)| source.has_unsent())
.for_each(|(_, source)| spawn(Arc::clone(source)));
}
async fn drain(source: Arc<Source>) {
let _ = crate::time::timeout(
crate::rt::linger::time(),
poll_fn(|cx| source.poll_drain(cx)),
)
.await;
}
const EVICTION_WAIT: Duration = Duration::from_secs(1);
fn wait_for_evictions(gone: &[(u64, Arc<Source>)], home: usize) {
wait_for_evictions_within(gone, home, EVICTION_WAIT);
}
fn wait_for_evictions_within(gone: &[(u64, Arc<Source>)], home: usize, within: Duration) {
let mut waited: Option<(Instant, Backoff)> = None;
for (token, source) in gone {
loop {
let asked = source.attach_asked_of(home);
if !asked && !source.registered_with(home, *token) {
break;
}
#[cfg(test)]
if asked {
crate::io::source::ATTACH_WAITS.fetch_add(1, Ordering::SeqCst);
}
let (deadline, backoff) =
waited.get_or_insert_with(|| (Instant::now() + within, Backoff::new()));
if !backoff.yielding() && Instant::now() >= *deadline {
crate::rt::counters::eviction_given_up();
log::warn!(
"a worker stopped before another thread let go of one of its sockets or files; its registration ends when that thread does"
);
return;
} else {
backoff.pause();
}
}
}
}
#[cfg(all(test, unix, not(loom)))]
mod tests {
use core::future::{Future, poll_fn};
use core::task::Poll;
use std::sync::Arc;
use std::time::Duration;
#[test]
fn a_shard_that_goes_does_not_wait_for_a_registration_elsewhere_under_its_token() {
let (mine, _peer) = std::os::unix::net::UnixStream::pair().unwrap();
let handle = crate::io::Handle::new(mine).unwrap();
let src = Arc::clone(handle.source());
crate::block_on(async {
let mut buf = [0u8; 1];
{
let mut read = Box::pin(handle.recv(&mut buf));
let polled = poll_fn(|cx| Poll::Ready(read.as_mut().poll(cx))).await;
assert!(polled.is_pending());
}
let (home, token) = src.registration();
assert_ne!(home, 0, "the receive did not register the source");
let (done_tx, done_rx) = std::sync::mpsc::channel();
let other = {
let src = Arc::clone(&src);
std::thread::spawn(move || {
crate::block_on(async move {
let there =
crate::rt::context::current(|s| s.map(super::Shard::shared_addr))
.expect("a block_on runs a shard");
assert_ne!(there, home);
super::wait_for_evictions(&[(token, src)], there);
});
let _ = done_tx.send(());
})
};
assert!(
done_rx.recv_timeout(Duration::from_secs(10)).is_ok(),
"the other shard waited for this shard's registration"
);
other.join().unwrap();
assert_eq!(
src.registration(),
(home, token),
"the registration here moved"
);
});
drop(handle);
}
#[test]
fn a_shard_that_goes_gives_up_waiting_for_a_holder_that_does_not_let_go() {
let (mine, _peer) = std::os::unix::net::UnixStream::pair().unwrap();
let handle = crate::io::Handle::new(mine).unwrap();
let src = Arc::clone(handle.source());
crate::block_on(async {
let mut buf = [0u8; 1];
{
let mut read = Box::pin(handle.recv(&mut buf));
let polled = poll_fn(|cx| Poll::Ready(read.as_mut().poll(cx))).await;
assert!(polled.is_pending());
}
let (home, token) = src.registration();
assert_ne!(home, 0, "the receive did not register the source");
let (done_tx, done_rx) = std::sync::mpsc::channel();
let waiter = {
let src = Arc::clone(&src);
std::thread::spawn(move || {
crate::block_on(async move {
let given_up = || {
crate::rt::context::current(|s| {
s.expect("a block_on runs a shard")
.shared
.stats()
.errors
.evictions_given_up
})
};
let before = given_up();
super::wait_for_evictions_within(
&[(token, src)],
home,
Duration::from_millis(50),
);
let _ = done_tx.send((before, given_up()));
});
})
};
let (before, after) = done_rx
.recv_timeout(Duration::from_secs(10))
.expect("the wait did not end at its bound");
waiter.join().unwrap();
assert_eq!(after, before + 1, "the give-up was not counted");
});
drop(handle);
}
}
impl Drop for Shard {
fn drop(&mut self) {
let shared = Arc::clone(&self.shared);
crate::rt::context::letting_go(&shared, || self.go());
}
}
impl Shard {
fn go(&mut self) {
#[cfg(ring)]
let started = std::time::Instant::now();
self.shared.counters.went();
self.shared.draining.store(true, Ordering::SeqCst);
self.shared.stopped.store(true, Ordering::SeqCst);
drop(self.shared.stop_waker.take());
let gone: Vec<(u64, Arc<Source>)> = self.sources.get_mut().drain_tokens().collect();
evict_all(&gone, self.shared_addr());
drain_unsent(&gone, |source| crate::rt::linger::hand(drain(source)));
self.let_go_of_children();
#[cfg(ring)]
if let Driver::Ring(ring) = &self.driver {
if ring.reapable() {
self.apply_io_ops();
ring.cancel_all();
} else {
ring.settle(|| self.apply_io_ops());
self.pool
.unreserve(ring.shut_down_from(started, || self.apply_io_ops()));
}
}
#[cfg(port)]
if !self.port_kept() && !self.hand_off_port() {
self.settle_port();
}
drop_locals(self.locals.take());
self.shared.drain_timer_ops();
loop {
let lifo = self.lifo.take();
let queue = core::mem::take(&mut *self.queue.borrow_mut());
let timers = self.wheel.borrow_mut().take_wakers();
let precise = self.precise.borrow_mut().take_wakers();
let fired = core::mem::take(&mut *self.fired.borrow_mut());
let mut quiet = lifo.is_none()
&& queue.is_empty()
&& timers.is_empty()
&& precise.is_empty()
&& fired.is_empty();
drop((lifo, queue, timers, precise, fired));
while let Some(runnable) = self.pop_inbox() {
drop(runnable);
quiet = false;
}
quiet &= unsafe { self.shared.tasks.cancel_all() } == 0;
if let Some(home) = self.home.get() {
quiet &= unsafe { home.cancel_listed() } == 0;
}
if quiet {
break;
}
}
if let Some(home) = self.home.get()
&& !unsafe { home.is_idle() }
{
unsafe {
home.cancel_all(
Some(self),
Instant::now() + crate::rt::workers::TEARDOWN_WAIT,
);
}
}
unsafe { self.shared.tasks.forget_all() };
self.shared.release_inbox();
give_up_claims(&gone, &self.shared);
#[cfg(ring)]
let kept = self.keep_ring();
#[cfg(not(ring))]
let kept = false;
if !kept {
self.shared.close_io();
}
}
}
impl Shard {
#[cfg(ring)]
fn keep_ring(&mut self) -> bool {
let Driver::Ring(ring) = &self.driver else {
return false;
};
if !ring.reapable() {
return false;
}
let looked = ring.wait(None, Some(std::time::Duration::ZERO)).is_ok();
if looked && ring.in_flight() == 0 {
return false;
}
let Driver::Ring(ring) = core::mem::replace(&mut self.driver, Driver::Gone) else {
return false;
};
super::ring::keeper::hand_off(ring, Arc::clone(&self.shared));
true
}
}