Implement namespace-owned stream rendezvous with incarnation isolation, cancellation, recovery, bounded local byte rings, and host/child actor protocols. Add a replaceable stream transport boundary plus an Iroh ALPN adapter with framed records, backpressure, reconnect handling, and terminal propagation. Wire stream sources and sinks through Myelin job deployment and Python bindings, with guarantee coverage across blob, namespace, job, byte-ring, and transport paths. Sanitize nested Cargo build context in xtask so lint, Nextest, doctest, and maturin invocations retain stable fingerprints.
137 lines
4.4 KiB
Rust
137 lines
4.4 KiB
Rust
pub mod common;
|
|
|
|
use std::sync::Arc;
|
|
use std::sync::mpsc::{self, Receiver, Sender};
|
|
use std::time::Duration;
|
|
|
|
use data_plane::arena::{ArenaConfig, ArenaManager, NodeId};
|
|
use data_plane::byte_ring::{ByteRingSpec, RecordKind, Role, attach, install};
|
|
use data_plane::namespace::StreamIncarnation;
|
|
use data_plane::stream_transport::{
|
|
StreamSinkRequest, StreamSourceRequest, StreamTransport, StreamTransportEvent,
|
|
StreamTransportNotifier,
|
|
};
|
|
|
|
use common::iroh::make_driver;
|
|
|
|
struct ChannelNotifier(Sender<StreamTransportEvent>);
|
|
|
|
impl StreamTransportNotifier for ChannelNotifier {
|
|
fn notify(&self, event: StreamTransportEvent) {
|
|
let _ = self.0.send(event);
|
|
}
|
|
}
|
|
|
|
fn arena(node: u64) -> ArenaManager {
|
|
ArenaManager::boot(ArenaConfig {
|
|
node_id: NodeId(node),
|
|
reservation_ceiling: 1 << 20,
|
|
base_alignment: 64,
|
|
})
|
|
.expect("arena")
|
|
}
|
|
|
|
fn recv_until(receiver: &Receiver<StreamTransportEvent>, expected: StreamTransportEvent) {
|
|
loop {
|
|
let event = receiver
|
|
.recv_timeout(Duration::from_secs(10))
|
|
.expect("transport event deadline");
|
|
if event == expected {
|
|
return;
|
|
}
|
|
if let StreamTransportEvent::Fault(reason) = event {
|
|
panic!("unexpected stream fault: {reason}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn iroh_adapter_satisfies_ordering_and_terminal_contract() {
|
|
let source_node = make_driver();
|
|
let sink_node = make_driver();
|
|
let source_transport = source_node.driver.stream_transport();
|
|
let sink_transport = sink_node.driver.stream_transport();
|
|
|
|
let mut source_arena = arena(1);
|
|
let source_handle = install(
|
|
&mut source_arena,
|
|
ByteRingSpec {
|
|
capacity: 128,
|
|
generation: 1,
|
|
alignment: 64,
|
|
request_id: 1,
|
|
},
|
|
)
|
|
.expect("source ring");
|
|
let mut sink_arena = arena(2);
|
|
let sink_handle = install(
|
|
&mut sink_arena,
|
|
ByteRingSpec {
|
|
capacity: 128,
|
|
generation: 2,
|
|
alignment: 64,
|
|
request_id: 2,
|
|
},
|
|
)
|
|
.expect("sink ring");
|
|
let mut writer = attach(&source_arena, source_handle, Role::Producer).expect("writer");
|
|
let source_pump = attach(&source_arena, source_handle, Role::Consumer).expect("source pump");
|
|
let sink_pump = attach(&sink_arena, sink_handle, Role::Producer).expect("sink pump");
|
|
let mut reader = attach(&sink_arena, sink_handle, Role::Consumer).expect("reader");
|
|
|
|
let incarnation = StreamIncarnation {
|
|
authority_epoch: 17,
|
|
revision: 23,
|
|
};
|
|
let (source_tx, source_rx) = mpsc::channel();
|
|
let (sink_tx, sink_rx) = mpsc::channel();
|
|
sink_transport
|
|
.install_sink(StreamSinkRequest {
|
|
incarnation,
|
|
endpoint: sink_pump,
|
|
notifier: Arc::new(ChannelNotifier(sink_tx)),
|
|
})
|
|
.expect("install sink");
|
|
source_transport
|
|
.install_source(StreamSourceRequest {
|
|
incarnation,
|
|
peer: sink_transport.descriptor().expect("sink descriptor"),
|
|
endpoint: source_pump,
|
|
notifier: Arc::new(ChannelNotifier(source_tx)),
|
|
})
|
|
.expect("install source");
|
|
recv_until(&source_rx, StreamTransportEvent::Ready);
|
|
recv_until(&sink_rx, StreamTransportEvent::Ready);
|
|
|
|
writer
|
|
.send_record(RecordKind::Data, b"one")
|
|
.expect("write one");
|
|
writer
|
|
.send_record(RecordKind::Data, b"two")
|
|
.expect("write two");
|
|
source_transport.source_progress(incarnation);
|
|
recv_until(&sink_rx, StreamTransportEvent::DataAvailable);
|
|
recv_until(&sink_rx, StreamTransportEvent::DataAvailable);
|
|
assert_eq!(
|
|
reader.recv_record().expect("read one"),
|
|
Some((RecordKind::Data, b"one".to_vec()))
|
|
);
|
|
assert_eq!(
|
|
reader.recv_record().expect("read two"),
|
|
Some((RecordKind::Data, b"two".to_vec()))
|
|
);
|
|
sink_transport.sink_progress(incarnation);
|
|
|
|
writer.send_record(RecordKind::Eof, &[]).expect("write eof");
|
|
source_transport.source_progress(incarnation);
|
|
recv_until(&sink_rx, StreamTransportEvent::DataAvailable);
|
|
assert_eq!(
|
|
reader.recv_record().expect("read eof"),
|
|
Some((RecordKind::Eof, Vec::new()))
|
|
);
|
|
recv_until(&source_rx, StreamTransportEvent::Quiesced);
|
|
recv_until(&sink_rx, StreamTransportEvent::Quiesced);
|
|
|
|
source_transport.terminate(incarnation);
|
|
sink_transport.terminate(incarnation);
|
|
}
|