use std::sync::Arc;
use std::time::Duration;
use futures::future::BoxFuture;
use velo::streaming::{AnchorManager, AnchorManagerBuilder, AttachError};
use velo_ext::{TransportKey, WorkerAddress, WorkerId};
struct LocalMockTransport;
impl velo::streaming::FrameTransport for LocalMockTransport {
fn key(&self) -> TransportKey {
TransportKey::new("mock-stream")
}
fn address(&self) -> WorkerAddress {
WorkerAddress::empty()
}
fn bind(
&self,
_anchor_id: u64,
_session_id: u64,
) -> BoxFuture<'_, anyhow::Result<flume::Receiver<Vec<u8>>>> {
Box::pin(async move {
let (_tx, rx) = flume::bounded::<Vec<u8>>(256);
Ok(rx)
})
}
fn connect(
&self,
_peer: WorkerId,
_anchor_id: u64,
_session_id: u64,
) -> BoxFuture<'_, anyhow::Result<flume::Sender<Vec<u8>>>> {
Box::pin(async move {
let (tx, _rx) = flume::bounded::<Vec<u8>>(1);
Ok(tx)
})
}
}
fn make_local_manager() -> Arc<AnchorManager> {
let worker_id = WorkerId::from_u64(1);
Arc::new(AnchorManager::new(worker_id, Arc::new(LocalMockTransport)))
}
#[tokio::test(flavor = "multi_thread")]
async fn test_cancel_01_drop_with_sender() {
let mgr = make_local_manager();
let anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
let sender = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("attach must succeed");
let cancel_token = sender.cancellation_token();
assert!(
!cancel_token.is_cancelled(),
"token must not be cancelled initially"
);
drop(anchor);
tokio::time::sleep(Duration::from_millis(50)).await;
assert!(
cancel_token.is_cancelled(),
"cancel_token must fire after StreamAnchor is dropped"
);
let result = sender.send(42u32).await;
assert!(
matches!(result, Err(velo::streaming::SendError::ChannelClosed)),
"send() must return ChannelClosed after anchor dropped, got {:?}",
result
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_cancel_02_controller_cancel() {
let mgr = make_local_manager();
let anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
let ctrl = anchor.controller();
let sender = mgr
.attach_stream_anchor::<u32>(handle)
.await
.expect("attach must succeed");
let cancel_token = sender.cancellation_token();
assert!(
!cancel_token.is_cancelled(),
"token must not be cancelled initially"
);
ctrl.cancel();
drop(anchor);
tokio::time::sleep(Duration::from_millis(50)).await;
assert!(
cancel_token.is_cancelled(),
"cancel_token must fire after StreamController::cancel()"
);
let result = sender.send(42u32).await;
assert!(
matches!(result, Err(velo::streaming::SendError::ChannelClosed)),
"send() must return ChannelClosed after controller cancel, got {:?}",
result
);
}
#[tokio::test]
async fn test_cancel_04_drop_no_sender() {
let mgr = make_local_manager();
let anchor = mgr.create_anchor::<u32>();
let handle = anchor.handle();
drop(anchor);
tokio::task::yield_now().await;
let result = mgr.attach_stream_anchor::<u32>(handle).await;
assert!(
matches!(result, Err(AttachError::AnchorNotFound { .. })),
"attach after no-sender drop must return AnchorNotFound, got {:?}",
result
);
}
use velo::messenger::Messenger;
use velo::streaming::control::{StreamCancelRequest, create_stream_cancel_handler};
use velo::transports::tcp::TcpTransportBuilder;
fn new_tcp_transport() -> Arc<velo::transports::tcp::TcpTransport> {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
Arc::new(
TcpTransportBuilder::new()
.from_listener(listener)
.unwrap()
.build()
.unwrap(),
)
}
async fn make_two_messengers() -> (Arc<Messenger>, Arc<Messenger>) {
let t1 = new_tcp_transport();
let t2 = new_tcp_transport();
let m1 = Messenger::builder()
.add_transport(t1)
.build()
.await
.expect("m1");
let m2 = Messenger::builder()
.add_transport(t2)
.build()
.await
.expect("m2");
m2.register_peer(m1.peer_info()).expect("register m1 on m2");
m1.register_peer(m2.peer_info()).expect("register m2 on m1");
tokio::time::sleep(Duration::from_millis(200)).await;
(m1, m2)
}
#[tokio::test(flavor = "multi_thread")]
async fn test_cancel_03_remote_cancel() {
let (messenger_a, messenger_b) = make_two_messengers().await;
let worker_id_b = messenger_b.instance_id().worker_id();
let mock_transport_b = Arc::new(LocalMockTransport);
let am_b = Arc::new(
AnchorManagerBuilder::default()
.worker_id(worker_id_b)
.transport(mock_transport_b)
.build()
.expect("am_b"),
);
let _ = messenger_b.register_streaming_handler(create_stream_cancel_handler(Arc::clone(
&am_b.sender_registry,
)));
let anchor_b = am_b.create_anchor::<u32>();
let handle_b = anchor_b.handle();
let sender_b = am_b
.attach_stream_anchor::<u32>(handle_b)
.await
.expect("attach sender B");
let cancel_token = sender_b.cancellation_token();
let sender_stream_id = 1u64;
assert!(
am_b.sender_registry.senders.contains_key(&sender_stream_id),
"SenderEntry must be in registry before cancel"
);
let payload = serde_json::to_vec(&StreamCancelRequest { sender_stream_id })
.expect("serialize StreamCancelRequest");
messenger_a
.am_send_streaming("_stream_cancel")
.expect("am_send_streaming builder")
.raw_payload(bytes::Bytes::from(payload))
.worker(worker_id_b)
.send()
.await
.expect("send _stream_cancel AM");
tokio::time::sleep(Duration::from_millis(300)).await;
assert!(
cancel_token.is_cancelled(),
"sender cancel_token must fire after _stream_cancel AM received"
);
}