use std::io::{Read, Write};
use std::sync::Arc;
use beamr::native::native_process::NativeContext;
use beamr::scheduler::Interest;
use liminal_protocol::wire::ConnectionIncarnation;
use super::super::process::{
ConnectionTransport, InboundPending, READ_BUFFER_BYTES, ReadStatus, TransportConnectionProcess,
};
use super::super::supervisor::ConnectionRuntime;
use super::duplex::LoopbackServerEnd;
use crate::ServerError;
use crate::server::mount::MountKind;
#[cfg(test)]
#[path = "process_tests.rs"]
mod tests;
pub(in super::super) type LoopbackConnectionProcess = TransportConnectionProcess<LoopbackTransport>;
#[derive(Debug)]
pub(in super::super) struct LoopbackTransport {
end: Option<LoopbackServerEnd>,
}
impl ConnectionTransport for LoopbackTransport {
const MOUNT: MountKind = MountKind::Loopback;
fn is_connected(&self) -> bool {
self.end.is_some()
}
fn read_available(&mut self, buffer: &mut Vec<u8>) -> Result<ReadStatus, ServerError> {
let Some(end) = self.end.as_mut() else {
return Ok(ReadStatus::Closed);
};
let mut chunk = [0_u8; READ_BUFFER_BYTES];
match end.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 loopback connection: {error}"),
}),
}
}
fn sink(&mut self) -> Option<&mut dyn Write> {
self.end.as_mut().map(|end| end as &mut dyn Write)
}
fn probe_inbound(&self) -> Result<bool, ServerError> {
self.end
.as_ref()
.map_or(Ok(true), InboundPending::inbound_pending)
}
fn arm_readiness(
&mut self,
_pid: u64,
_ctx: &NativeContext<'_>,
_interest: Interest,
_runtime: &ConnectionRuntime,
) -> Result<(), ServerError> {
Ok(())
}
fn install_wake(&mut self, pid: u64, runtime: &ConnectionRuntime) -> Result<(), ServerError> {
let Some(end) = self.end.as_ref() else {
return Ok(());
};
let waker = runtime
.ready_waker(pid)
.ok_or_else(|| ServerError::ListenerAccept {
message: format!(
"loopback connection {pid} has no READY waker; it could never be told \
about inbound bytes"
),
})?;
end.set_waker(Box::new(move || {
waker.fire();
}));
Ok(())
}
fn release(&mut self) {
self.end.take();
}
#[cfg(test)]
fn note_process_drop(&mut self, _runtime: &ConnectionRuntime) {}
}
impl LoopbackConnectionProcess {
pub(in super::super) fn from_loopback_holder(
runtime: Arc<ConnectionRuntime>,
holder: &Arc<std::sync::Mutex<Option<LoopbackServerEnd>>>,
connection_incarnation: Option<ConnectionIncarnation>,
) -> Self {
let end = match holder.lock() {
Ok(mut held) => held.take(),
Err(poisoned) => {
tracing::error!(
error = %poisoned,
"loopback connection handoff failed: duplex holder mutex was poisoned; \
the connection process will start without a transport and stop immediately"
);
None
}
};
Self::over_transport(
runtime,
None,
LoopbackTransport { end },
connection_incarnation,
)
}
}