use super::{
ConnectionProcess, ConnectionTransport, InboundPending, READ_BUFFER_BYTES, ReadStatus,
};
use crate::ServerError;
use crate::server::connection::readiness_refusal::{ReadinessCall, readiness_refusal};
use crate::server::connection::supervisor::runtime::ConnectionRuntime;
use crate::server::mount::MountKind;
use beamr::native::native_process::NativeContext;
use beamr::scheduler::{Interest, ReadinessToken};
use liminal_protocol::wire::ConnectionIncarnation;
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpStream};
use std::os::fd::AsRawFd;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub(in crate::server::connection) struct TcpTransport {
stream: Option<TcpStream>,
readiness_token: Option<ReadinessToken>,
}
impl ConnectionTransport for TcpTransport {
const MOUNT: MountKind = MountKind::Tcp;
fn is_connected(&self) -> bool {
self.stream.is_some()
}
fn read_available(&mut self, buffer: &mut Vec<u8>) -> Result<ReadStatus, ServerError> {
let Some(stream) = self.stream.as_mut() else {
return Ok(ReadStatus::Closed);
};
read_available(stream, buffer)
}
fn sink(&mut self) -> Option<&mut dyn Write> {
self.stream
.as_mut()
.map(|stream| stream as &mut dyn std::io::Write)
}
fn probe_inbound(&self) -> Result<bool, ServerError> {
self.stream
.as_ref()
.map_or(Ok(true), InboundPending::inbound_pending)
}
fn arm_readiness(
&mut self,
pid: u64,
ctx: &NativeContext<'_>,
interest: Interest,
runtime: &ConnectionRuntime,
) -> Result<(), ServerError> {
let facility = ctx
.readiness_facility()
.ok_or_else(|| ServerError::ListenerAccept {
message: "connection scheduler started without its required readiness service"
.to_owned(),
})?;
if let Some(token) = self.readiness_token {
return facility
.rearm(&token, interest)
.map_err(|error| readiness_refusal(pid, ReadinessCall::Rearm, &error));
}
let stream = self
.stream
.as_ref()
.ok_or_else(|| ServerError::ListenerAccept {
message: "cannot register readiness for a missing connection stream".to_owned(),
})?;
let token = facility
.register(stream.as_raw_fd(), interest, pid, runtime.ready_atom())
.map_err(|error| readiness_refusal(pid, ReadinessCall::Register, &error))?;
if let Err(error) = runtime.set_readiness_token_once(pid, token, stream.as_raw_fd()) {
runtime.deregister_unpublished_readiness(token);
return Err(error);
}
self.readiness_token = Some(token);
Ok(())
}
fn install_wake(&mut self, _pid: u64, _runtime: &ConnectionRuntime) -> Result<(), ServerError> {
Ok(())
}
fn release(&mut self) {
self.stream.take();
}
#[cfg(test)]
fn note_process_drop(&mut self, runtime: &ConnectionRuntime) {
if let Some(stream) = self.stream.take() {
let fd = stream.as_raw_fd();
drop(stream);
runtime.record_process_stream_drop(fd);
}
}
}
impl ConnectionProcess {
pub(in crate::server::connection) fn from_holder(
runtime: Arc<ConnectionRuntime>,
peer_addr: Option<SocketAddr>,
holder: &Arc<Mutex<Option<TcpStream>>>,
connection_incarnation: Option<ConnectionIncarnation>,
) -> Self {
let stream = match holder.lock() {
Ok(mut held) => held.take(),
Err(poisoned) => {
tracing::error!(
peer_addr = ?peer_addr,
error = %poisoned,
"connection stream handoff failed: stream holder mutex was poisoned; \
the connection process will start without a stream and stop immediately"
);
None
}
};
Self::over_transport(
runtime,
peer_addr,
TcpTransport {
stream,
readiness_token: None,
},
connection_incarnation,
)
}
}
impl InboundPending for TcpStream {
fn inbound_pending(&self) -> Result<bool, ServerError> {
let mut byte = [0_u8; 1];
match self.peek(&mut byte) {
Ok(_) => Ok(true),
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => Ok(false),
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => Ok(true),
Err(error) => Err(ServerError::ListenerAccept {
message: format!("connection readiness probe failed: {error}"),
}),
}
}
}
fn read_available(stream: &mut TcpStream, buffer: &mut Vec<u8>) -> Result<ReadStatus, ServerError> {
let mut chunk = [0_u8; READ_BUFFER_BYTES];
match stream.read(&mut chunk) {
Ok(0) => Ok(ReadStatus::Closed),
Ok(bytes_read) => {
buffer.extend_from_slice(chunk.get(..bytes_read).unwrap_or(&[]));
Ok(ReadStatus::Read)
}
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => Ok(ReadStatus::WouldBlock),
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => Ok(ReadStatus::WouldBlock),
Err(error) => Err(ServerError::ListenerAccept {
message: format!("failed to read connection stream: {error}"),
}),
}
}