swactor/crates/iroh-driver/src/stream_transport.rs

432 lines
14 KiB
Rust
Raw Normal View History

//! Iroh-specific implementation of the data-plane SPSC stream transport port.
use std::collections::BTreeMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use data_plane::byte_ring::{Endpoint as RingEndpoint, FlowError, RecordKind, RingProbe};
use data_plane::namespace::StreamIncarnation;
use data_plane::stream_transport::{
StreamPeerDescriptor, StreamSinkRequest, StreamSourceRequest, StreamTransport,
StreamTransportEvent, StreamTransportNotifier,
};
use iroh::endpoint::{Connection, RecvStream, SendStream};
use iroh::{Endpoint as IrohEndpoint, EndpointAddr};
use parking_lot::Mutex;
use swactor_engine::EngineHandle;
use tokio::io::AsyncWriteExt;
use tokio::sync::mpsc;
pub const STREAM_ALPN: &[u8] = b"swactor/data-plane-spsc/1";
const PREAMBLE_LEN: usize = 16;
const RECORD_HEADER_LEN: usize = 5;
#[derive(Clone)]
struct TaskControl {
wake: mpsc::Sender<()>,
progress_pending: Arc<AtomicBool>,
cancelled: Arc<AtomicBool>,
}
impl TaskControl {
fn pair() -> (Self, mpsc::Receiver<()>) {
let (wake, receiver) = mpsc::channel(1);
(
Self {
wake,
progress_pending: Arc::new(AtomicBool::new(false)),
cancelled: Arc::new(AtomicBool::new(false)),
},
receiver,
)
}
fn progress(&self) {
if !self.progress_pending.swap(true, Ordering::AcqRel) {
let _ = self.wake.try_send(());
}
}
fn cancel(&self) {
self.cancelled.store(true, Ordering::Release);
self.progress();
}
async fn wait(&self, receiver: &mut mpsc::Receiver<()>) -> bool {
if self.cancelled.load(Ordering::Acquire) {
return false;
}
if receiver.recv().await.is_none() {
return false;
}
self.progress_pending.store(false, Ordering::Release);
!self.cancelled.load(Ordering::Acquire)
}
}
struct PendingSink {
endpoint: RingEndpoint,
notifier: Arc<dyn StreamTransportNotifier>,
}
#[derive(Default)]
struct TransportState {
pending_sinks: BTreeMap<StreamIncarnation, PendingSink>,
controls: BTreeMap<StreamIncarnation, Vec<TaskControl>>,
source_probes: BTreeMap<StreamIncarnation, RingProbe>,
sink_probes: BTreeMap<StreamIncarnation, RingProbe>,
}
struct Inner {
engine: EngineHandle,
endpoint: IrohEndpoint,
state: Mutex<TransportState>,
}
/// Cloneable adapter capability installed into `HostDataPlaneConfig`.
#[derive(Clone)]
pub struct IrohStreamTransport {
inner: Arc<Inner>,
}
impl IrohStreamTransport {
pub fn new(engine: EngineHandle, endpoint: IrohEndpoint) -> Self {
Self {
inner: Arc::new(Inner {
engine,
endpoint,
state: Mutex::new(TransportState::default()),
}),
}
}
pub(crate) fn accept_connection(&self, connection: Connection) {
let transport = self.clone();
self.inner.engine.spawn(async move {
while let Ok(mut recv) = connection.accept_uni().await {
let mut preamble = [0_u8; PREAMBLE_LEN];
if recv.read_exact(&mut preamble).await.is_err() {
continue;
}
let incarnation = decode_incarnation(preamble);
let pending = transport
.inner
.state
.lock()
.pending_sinks
.remove(&incarnation);
let Some(pending) = pending else {
continue;
};
let (control, receiver) = TaskControl::pair();
transport
.inner
.state
.lock()
.controls
.entry(incarnation)
.or_default()
.push(control.clone());
transport.inner.engine.spawn(run_sink(
incarnation,
recv,
pending.endpoint,
pending.notifier,
control,
receiver,
));
}
});
}
fn register_control(&self, incarnation: StreamIncarnation, control: TaskControl) {
self.inner
.state
.lock()
.controls
.entry(incarnation)
.or_default()
.push(control);
}
fn progress_controls(&self, incarnation: StreamIncarnation) {
if let Some(controls) = self.inner.state.lock().controls.get(&incarnation) {
for control in controls {
control.progress();
}
}
}
}
impl StreamTransport for IrohStreamTransport {
fn descriptor(&self) -> Result<StreamPeerDescriptor, String> {
serde_json::to_vec(&self.inner.endpoint.addr())
.map(StreamPeerDescriptor)
.map_err(|error| format!("encode iroh stream endpoint: {error}"))
}
fn install_source(&self, request: StreamSourceRequest) -> Result<(), String> {
let peer: EndpointAddr = serde_json::from_slice(&request.peer.0)
.map_err(|error| format!("decode iroh stream endpoint: {error}"))?;
let (control, receiver) = TaskControl::pair();
self.register_control(request.incarnation, control.clone());
let endpoint = self.inner.endpoint.clone();
let probe = request.endpoint.probe();
self.inner
.state
.lock()
.source_probes
.insert(request.incarnation, probe);
self.inner.engine.spawn(run_source(
request.incarnation,
endpoint,
peer,
request.endpoint,
request.notifier,
control,
receiver,
));
Ok(())
}
fn install_sink(&self, request: StreamSinkRequest) -> Result<(), String> {
let mut state = self.inner.state.lock();
if state.pending_sinks.contains_key(&request.incarnation) {
return Err("iroh stream sink is already installed".to_owned());
}
let probe = request.endpoint.probe();
state.sink_probes.insert(request.incarnation, probe);
state.pending_sinks.insert(
request.incarnation,
PendingSink {
endpoint: request.endpoint,
notifier: request.notifier,
},
);
Ok(())
}
fn source_progress(&self, incarnation: StreamIncarnation) {
self.progress_controls(incarnation);
}
fn sink_progress(&self, incarnation: StreamIncarnation) {
self.progress_controls(incarnation);
}
fn source_has_capacity(&self, incarnation: StreamIncarnation) -> bool {
self.inner
.state
.lock()
.source_probes
.get(&incarnation)
.is_some_and(RingProbe::has_capacity)
}
fn sink_has_data(&self, incarnation: StreamIncarnation) -> bool {
self.inner
.state
.lock()
.sink_probes
.get(&incarnation)
.is_some_and(RingProbe::has_data)
}
fn terminate(&self, incarnation: StreamIncarnation) {
let (pending, controls) = {
let mut state = self.inner.state.lock();
let pending = state.pending_sinks.remove(&incarnation);
let controls = state.controls.remove(&incarnation).unwrap_or_default();
state.source_probes.remove(&incarnation);
state.sink_probes.remove(&incarnation);
(pending, controls)
};
if let Some(pending) = pending {
pending.notifier.notify(StreamTransportEvent::Quiesced);
}
for control in controls {
control.cancel();
}
}
}
async fn run_source(
incarnation: StreamIncarnation,
endpoint: IrohEndpoint,
peer: EndpointAddr,
mut source: RingEndpoint,
notifier: Arc<dyn StreamTransportNotifier>,
control: TaskControl,
mut receiver: mpsc::Receiver<()>,
) {
let result = async {
let connection = endpoint
.connect(peer, STREAM_ALPN)
.await
.map_err(|error| format!("connect stream incarnation: {error}"))?;
let mut send = connection
.open_uni()
.await
.map_err(|error| format!("open stream incarnation: {error}"))?;
send.write_all(&encode_incarnation(incarnation))
.await
.map_err(|error| format!("write stream preamble: {error}"))?;
send.flush()
.await
.map_err(|error| format!("flush stream preamble: {error}"))?;
notifier.notify(StreamTransportEvent::Ready);
loop {
if control.cancelled.load(Ordering::Acquire) {
return Ok(());
}
let mut moved = false;
while let Some(meta) = source
.next_record_meta()
.map_err(|error| format!("inspect source ring: {error:?}"))?
{
let view = source
.peek_record()
.map_err(|error| format!("pin source ring: {error:?}"))?
.ok_or_else(|| "source record disappeared after inspection".to_owned())?;
write_record(&mut send, meta.kind, view.spans())
.await
.map_err(|error| format!("write stream record: {error}"))?;
view.release()
.map_err(|error| format!("release source ring: {error:?}"))?;
notifier.notify(StreamTransportEvent::CapacityAvailable);
moved = true;
if matches!(meta.kind, RecordKind::Eof | RecordKind::Fault) {
send.finish()
.map_err(|error| format!("finish stream incarnation: {error}"))?;
match send
.stopped()
.await
.map_err(|error| format!("await stream finish: {error}"))?
{
Some(code) => {
return Err(format!("peer stopped stream incarnation: {code}"));
}
None => return Ok(()),
}
}
}
if !moved && !control.wait(&mut receiver).await {
return Ok(());
}
}
}
.await;
if let Err(reason) = result {
notifier.notify(StreamTransportEvent::Fault(reason));
}
notifier.notify(StreamTransportEvent::Quiesced);
}
async fn write_record(
send: &mut SendStream,
kind: RecordKind,
spans: (&[u8], &[u8]),
) -> Result<(), String> {
let len = spans.0.len() + spans.1.len();
let len = u32::try_from(len).map_err(|_| "stream record exceeds u32 framing".to_owned())?;
let mut header = [0_u8; RECORD_HEADER_LEN];
header[0] = kind.to_byte();
header[1..].copy_from_slice(&len.to_le_bytes());
send.write_all(&header)
.await
.map_err(|error| error.to_string())?;
if !spans.0.is_empty() {
send.write_all(spans.0)
.await
.map_err(|error| error.to_string())?;
}
if !spans.1.is_empty() {
send.write_all(spans.1)
.await
.map_err(|error| error.to_string())?;
}
send.flush().await.map_err(|error| error.to_string())
}
async fn run_sink(
_incarnation: StreamIncarnation,
mut recv: RecvStream,
mut sink: RingEndpoint,
notifier: Arc<dyn StreamTransportNotifier>,
control: TaskControl,
mut receiver: mpsc::Receiver<()>,
) {
notifier.notify(StreamTransportEvent::Ready);
let result = async {
loop {
if control.cancelled.load(Ordering::Acquire) {
return Ok(());
}
let mut header = [0_u8; RECORD_HEADER_LEN];
recv.read_exact(&mut header)
.await
.map_err(|error| format!("read stream record header: {error}"))?;
let kind = RecordKind::from_byte(header[0])
.ok_or_else(|| format!("invalid stream record kind {}", header[0]))?;
let len = u64::from(u32::from_le_bytes(header[1..].try_into().unwrap()));
let mut reservation = loop {
match sink.reserve_record(kind, len) {
Ok(reservation) => break reservation,
Err(FlowError::InsufficientSpace { .. }) => {
if !control.wait(&mut receiver).await {
return Ok(());
}
}
Err(error) => return Err(format!("reserve sink ring: {error:?}")),
}
};
let (first, second) = reservation.spans_mut();
if !first.is_empty() {
recv.read_exact(first)
.await
.map_err(|error| format!("read first stream span: {error}"))?;
}
if !second.is_empty() {
recv.read_exact(second)
.await
.map_err(|error| format!("read second stream span: {error}"))?;
}
reservation
.commit()
.map_err(|error| format!("commit sink ring: {error:?}"))?;
notifier.notify(StreamTransportEvent::DataAvailable);
if matches!(kind, RecordKind::Eof | RecordKind::Fault) {
let mut trailing = [0_u8; 1];
match recv
.read(&mut trailing)
.await
.map_err(|error| format!("read stream finish: {error}"))?
{
None | Some(0) => return Ok(()),
Some(_) => return Err("bytes followed terminal stream record".to_owned()),
}
}
}
}
.await;
if let Err(reason) = result {
notifier.notify(StreamTransportEvent::Fault(reason));
}
notifier.notify(StreamTransportEvent::Quiesced);
}
fn encode_incarnation(incarnation: StreamIncarnation) -> [u8; PREAMBLE_LEN] {
let mut encoded = [0_u8; PREAMBLE_LEN];
encoded[..8].copy_from_slice(&incarnation.authority_epoch.to_le_bytes());
encoded[8..].copy_from_slice(&incarnation.revision.to_le_bytes());
encoded
}
fn decode_incarnation(encoded: [u8; PREAMBLE_LEN]) -> StreamIncarnation {
StreamIncarnation {
authority_epoch: u64::from_le_bytes(encoded[..8].try_into().unwrap()),
revision: u64::from_le_bytes(encoded[8..].try_into().unwrap()),
}
}