OneForOne supervisor on the root actor, Restart::Permanent per listener. ChildSpec factory owns its fd via Arc<OwnedFd> (OwnedFd gains Sync); restarts reuse the fd — no re-dup, no fd-less window. wait_readable failure now exits-and-restarts instead of being fatal. Conn actors stay unsupervised bare spawns. Test hook INJECT_LISTENER_PANICS panics before accept so a pending connection must survive into the restarted listener. Roadmap: chunk 1 marked done; chunk 3 fork resolved to smarm-native timed waits (RFC 008 landed at 393cdd0, reaper option removed); chunk 2 drain-then-stop decided; v0.4-3(b) unblocked.
173 lines
5.7 KiB
Rust
173 lines
5.7 KiB
Rust
//! Thin TCP socket layer.
|
|
//!
|
|
//! We don't use `std::net::TcpListener` because we need non-blocking accept
|
|
//! integrated with smarm's epoll loop. So this drops down to libc: socket,
|
|
//! bind, listen, accept4. All fds carry `O_NONBLOCK | O_CLOEXEC` so smarm's
|
|
//! readiness primitives work as documented.
|
|
|
|
use std::io;
|
|
use std::net::SocketAddr;
|
|
use std::os::fd::RawFd;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// OwnedFd — a tiny RAII wrapper that closes on drop.
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// Sent through smarm channels (which require `Send`) for zero-copy fd
|
|
// handoff between listener and connection actor. `Send` is safe: fd values
|
|
// are just integers, and ownership semantics — exactly-one closer — are
|
|
// enforced by the type itself.
|
|
|
|
#[derive(Debug)]
|
|
pub struct OwnedFd {
|
|
fd: RawFd,
|
|
}
|
|
|
|
impl OwnedFd {
|
|
/// Wrap a raw fd. The wrapper now owns the fd and will close it on drop.
|
|
pub fn from_raw(fd: RawFd) -> Self {
|
|
Self { fd }
|
|
}
|
|
|
|
pub fn as_raw(&self) -> RawFd { self.fd }
|
|
|
|
/// Release ownership without closing. The caller must close the fd.
|
|
pub fn into_raw(self) -> RawFd {
|
|
let fd = self.fd;
|
|
std::mem::forget(self);
|
|
fd
|
|
}
|
|
}
|
|
|
|
impl Drop for OwnedFd {
|
|
fn drop(&mut self) {
|
|
if self.fd >= 0 {
|
|
unsafe { libc::close(self.fd); }
|
|
}
|
|
}
|
|
}
|
|
|
|
// fd handoff: SAFETY: RawFd is an integer; sending one across threads
|
|
// transfers ownership in the same way moving an i32 would. The recipient
|
|
// becomes the unique closer.
|
|
unsafe impl Send for OwnedFd {}
|
|
|
|
// SAFETY: shared references only expose `as_raw(&self)` — reading an
|
|
// integer. No interior mutability; `into_raw` takes `self` by value and is
|
|
// unreachable through a shared reference. Needed so a supervisor
|
|
// `ChildSpec` factory (an `Fn` closure, hence shared across restarts) can
|
|
// own its listener fd via `Arc<OwnedFd>`.
|
|
unsafe impl Sync for OwnedFd {}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// bind_and_listen
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const LISTEN_BACKLOG: i32 = 1024;
|
|
|
|
/// Create a non-blocking, SO_REUSEADDR TCP listener bound to `addr`. The
|
|
/// returned fd is ready for `accept4` calls; the caller registers it with
|
|
/// smarm via `wait_readable` between accepts.
|
|
pub fn bind_and_listen(addr: SocketAddr) -> io::Result<OwnedFd> {
|
|
let family = match addr {
|
|
SocketAddr::V4(_) => libc::AF_INET,
|
|
SocketAddr::V6(_) => libc::AF_INET6,
|
|
};
|
|
|
|
let fd = unsafe {
|
|
libc::socket(
|
|
family,
|
|
libc::SOCK_STREAM | libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC,
|
|
0,
|
|
)
|
|
};
|
|
if fd < 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
let owned = OwnedFd::from_raw(fd);
|
|
|
|
// SO_REUSEADDR — standard for servers; avoids TIME_WAIT bind failures
|
|
// on restart.
|
|
let opt: libc::c_int = 1;
|
|
let r = unsafe {
|
|
libc::setsockopt(
|
|
fd,
|
|
libc::SOL_SOCKET,
|
|
libc::SO_REUSEADDR,
|
|
&opt as *const _ as *const libc::c_void,
|
|
std::mem::size_of_val(&opt) as libc::socklen_t,
|
|
)
|
|
};
|
|
if r < 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
|
|
// bind().
|
|
match addr {
|
|
SocketAddr::V4(a) => {
|
|
let sa = libc::sockaddr_in {
|
|
sin_family: libc::AF_INET as u16,
|
|
sin_port: a.port().to_be(),
|
|
sin_addr: libc::in_addr { s_addr: u32::from_ne_bytes(a.ip().octets()) },
|
|
sin_zero: [0; 8],
|
|
};
|
|
let r = unsafe {
|
|
libc::bind(
|
|
fd,
|
|
&sa as *const _ as *const libc::sockaddr,
|
|
std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
|
|
)
|
|
};
|
|
if r < 0 { return Err(io::Error::last_os_error()); }
|
|
}
|
|
SocketAddr::V6(a) => {
|
|
let sa = libc::sockaddr_in6 {
|
|
sin6_family: libc::AF_INET6 as u16,
|
|
sin6_port: a.port().to_be(),
|
|
sin6_flowinfo: a.flowinfo(),
|
|
sin6_addr: libc::in6_addr { s6_addr: a.ip().octets() },
|
|
sin6_scope_id: a.scope_id(),
|
|
};
|
|
let r = unsafe {
|
|
libc::bind(
|
|
fd,
|
|
&sa as *const _ as *const libc::sockaddr,
|
|
std::mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t,
|
|
)
|
|
};
|
|
if r < 0 { return Err(io::Error::last_os_error()); }
|
|
}
|
|
}
|
|
|
|
// listen().
|
|
let r = unsafe { libc::listen(fd, LISTEN_BACKLOG) };
|
|
if r < 0 { return Err(io::Error::last_os_error()); }
|
|
|
|
Ok(owned)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// accept_nonblocking
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// One non-blocking `accept4`. Returns the new fd on success,
|
|
/// `Err(WouldBlock)` if no connection is pending (caller should park on
|
|
/// `wait_readable(listener)` and retry), or other errors directly.
|
|
pub fn accept_nonblocking(listener: RawFd) -> io::Result<OwnedFd> {
|
|
let mut addr: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
|
|
let mut len: libc::socklen_t = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
|
|
|
|
let fd = unsafe {
|
|
libc::accept4(
|
|
listener,
|
|
&mut addr as *mut _ as *mut libc::sockaddr,
|
|
&mut len,
|
|
libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC,
|
|
)
|
|
};
|
|
if fd < 0 {
|
|
return Err(io::Error::last_os_error());
|
|
}
|
|
Ok(OwnedFd::from_raw(fd))
|
|
}
|