use std::io;
use std::os::unix::net::{SocketAddr, UnixDatagram};
#[cfg(feature = "tokio")]
use std::sync::atomic::Ordering;
#[cfg(feature = "tokio")]
use std::sync::{Arc, Weak};
#[cfg(feature = "tokio")]
use crate::layout::Doorbell;
#[cfg(feature = "tokio")]
use crate::table::Table;
#[cfg(feature = "tokio")]
use crate::wake::Doorstep;
pub(crate) const ARMED: u32 = 1 << 31;
pub(crate) struct Bell {
prefix: String,
sender: UnixDatagram,
}
impl Bell {
pub(crate) fn new(segment: &str) -> io::Result<Self> {
let sender = UnixDatagram::unbound()?;
sender.set_nonblocking(true)?;
let uid = unsafe { libc::getuid() };
let segment = segment.trim_start_matches('/');
Ok(Self { prefix: format!("orbit-bell-{uid}/{segment}"), sender })
}
#[cfg(target_os = "linux")]
fn address(
&self,
node: usize,
) -> io::Result<SocketAddr> {
use std::os::linux::net::SocketAddrExt;
SocketAddr::from_abstract_name(format!("{}.{node}", self.prefix))
}
#[cfg(not(target_os = "linux"))]
fn path(
&self,
node: usize,
) -> std::path::PathBuf {
std::path::PathBuf::from(format!("/tmp/{}.{node}", self.prefix))
}
#[cfg(not(target_os = "linux"))]
fn address(
&self,
node: usize,
) -> io::Result<SocketAddr> {
SocketAddr::from_pathname(self.path(node))
}
pub(crate) fn ring(
&self,
node: usize,
) {
if let Ok(address) = self.address(node) {
let _ = self.sender.send_to_addr(&[1], &address);
}
}
#[cfg(feature = "tokio")]
fn bind(
&self,
node: usize,
) -> io::Result<UnixDatagram> {
#[cfg(not(target_os = "linux"))]
{
use std::os::unix::fs::DirBuilderExt;
let path = self.path(node);
if let Some(directory) = path.parent() {
match std::fs::DirBuilder::new().mode(0o700).create(directory) {
Err(error)
if error.kind() != io::ErrorKind::AlreadyExists =>
{
return Err(error);
}
_ => {}
}
}
let _ = std::fs::remove_file(&path);
}
let socket = UnixDatagram::bind_addr(&self.address(node)?)?;
socket.set_nonblocking(true)?;
Ok(socket)
}
#[cfg(feature = "tokio")]
fn unbind(
&self,
node: usize,
) {
#[cfg(not(target_os = "linux"))]
let _ = std::fs::remove_file(self.path(node));
#[cfg(target_os = "linux")]
let _ = node;
}
}
#[cfg(feature = "tokio")]
pub(crate) struct ReactorDriver {
task: tokio::task::JoinHandle<()>,
node: usize,
}
#[cfg(feature = "tokio")]
impl ReactorDriver {
pub(crate) fn start(
table: &Arc<Table>,
bell: &Bell,
runtime: &tokio::runtime::Handle,
) -> io::Result<Self> {
let node = usize::from(table.node());
let socket = {
let _entered = runtime.enter();
tokio::net::UnixDatagram::from_std(bell.bind(node)?)?
};
table
.own_doorbell()
.incarnation
.store(table.incarnation(), Ordering::SeqCst);
let weak = Arc::downgrade(table);
let task = runtime.spawn(answer(weak, socket));
Ok(Self { task, node })
}
pub(crate) fn stop(
self,
doorbell: &Doorbell,
bell: &Bell,
) {
self.task.abort();
doorbell.listening.fetch_and(!ARMED, Ordering::SeqCst);
bell.unbind(self.node);
}
}
#[cfg(feature = "tokio")]
async fn answer(
table: Weak<Table>,
socket: tokio::net::UnixDatagram,
) {
let mut scratch = [0u8; 64];
loop {
{
let Some(table) = table.upgrade() else { break };
let doorbell = table.own_doorbell();
loop {
let seen = doorbell.generation.load(Ordering::SeqCst);
table.drain();
doorbell.listening.fetch_or(ARMED, Ordering::SeqCst);
if doorbell.generation.load(Ordering::SeqCst) == seen {
break;
}
}
}
if socket.readable().await.is_err() {
break;
}
while socket.try_recv(&mut scratch).is_ok() {}
}
}