1use std::{
27 sync::{
28 Arc,
29 atomic::{AtomicBool, AtomicU64, Ordering},
30 },
31 time::Duration,
32};
33
34use ahash::AHashMap;
35use nautilus_live::task::{SharedTaskSlot, TaskJoinOutcome};
36use nautilus_network::{
37 RECONNECTED,
38 websocket::{AuthTracker, WebSocketClient},
39};
40use serde_json::Value;
41use tokio_tungstenite::tungstenite::Message;
42
43use super::{
44 client::UNAUTHENTICATED_CONNECTION_EPOCH,
45 error::DeriveWsError,
46 messages::{DeriveWsChannel, DeriveWsFrame, WsSubscribeParams, WsSubscriptionPayload},
47};
48use crate::http::models::JsonRpcRequest;
49
50#[derive(Debug)]
52pub(super) enum HandlerCommand {
53 SetClient(WebSocketClient),
55 Request {
59 method: &'static str,
60 params: Value,
61 connection_epoch: Option<u64>,
62 response_tx: tokio::sync::oneshot::Sender<Result<Value, DeriveWsError>>,
63 },
64 Disconnect,
66}
67
68#[derive(Debug, Clone)]
70pub enum DeriveWsMessage {
71 Authenticated,
74 Reconnected,
77 SessionRecoveryFailed(String),
79 Subscription(WsSubscriptionPayload),
81}
82
83#[derive(Debug)]
84struct SendCommand {
85 id: u64,
86 token: u64,
87 connection_epoch: u64,
88 payload: String,
89}
90
91#[derive(Debug)]
92struct SendFailure {
93 id: u64,
94 token: u64,
95 reason: String,
96}
97
98#[derive(Debug)]
99struct PendingRequest {
100 token: u64,
101 response_tx: tokio::sync::oneshot::Sender<Result<Value, DeriveWsError>>,
102}
103
104pub(super) struct FeedHandler {
107 signal: Arc<AtomicBool>,
108 client: Option<Arc<WebSocketClient>>,
109 cmd_rx: tokio::sync::mpsc::UnboundedReceiver<HandlerCommand>,
110 cmd_closed: bool,
111 raw_rx: tokio::sync::mpsc::UnboundedReceiver<Message>,
112 raw_closed: bool,
113 next_id: Arc<AtomicU64>,
114 next_send_token: u64,
115 pending: AHashMap<u64, PendingRequest>,
116 send_tx: Option<tokio::sync::mpsc::UnboundedSender<SendCommand>>,
117 send_failure_rx: tokio::sync::mpsc::UnboundedReceiver<SendFailure>,
118 send_task: Arc<SharedTaskSlot<()>>,
119 auth_tracker: AuthTracker,
120 authenticated_epoch: Arc<AtomicU64>,
121}
122
123impl FeedHandler {
124 pub(super) fn new_with_send_task(
125 signal: Arc<AtomicBool>,
126 cmd_rx: tokio::sync::mpsc::UnboundedReceiver<HandlerCommand>,
127 raw_rx: tokio::sync::mpsc::UnboundedReceiver<Message>,
128 next_id: Arc<AtomicU64>,
129 auth_tracker: AuthTracker,
130 authenticated_epoch: Arc<AtomicU64>,
131 send_task: Arc<SharedTaskSlot<()>>,
132 ) -> Self {
133 let (_, send_failure_rx) = tokio::sync::mpsc::unbounded_channel();
134 Self {
135 signal,
136 client: None,
137 cmd_rx,
138 cmd_closed: false,
139 raw_rx,
140 raw_closed: false,
141 next_id,
142 next_send_token: 0,
143 pending: AHashMap::new(),
144 send_tx: None,
145 send_failure_rx,
146 send_task,
147 auth_tracker,
148 authenticated_epoch,
149 }
150 }
151
152 pub(super) async fn next(&mut self) -> Option<DeriveWsMessage> {
156 loop {
157 tokio::select! {
158 cmd = self.cmd_rx.recv(), if !self.cmd_closed => {
159 match cmd {
160 None => {
161 self.cmd_closed = true;
162
163 if self.raw_closed {
164 self.shutdown_send_path(
165 "WebSocket handler stopped before response was received",
166 )
167 .await;
168 return None;
169 }
170 }
171 Some(HandlerCommand::SetClient(client)) => {
172 log::debug!("Setting WebSocket client in Derive handler");
173 let client = Arc::new(client);
174 self.stop_send_worker().await;
175 self.start_send_worker(Arc::clone(&client));
176 self.client = Some(client);
177 }
178 Some(HandlerCommand::Request {
179 method,
180 params,
181 connection_epoch,
182 response_tx,
183 }) => {
184 self.dispatch_request(method, params, connection_epoch, response_tx);
185 }
186 Some(HandlerCommand::Disconnect) => {
187 log::debug!("Derive handler received disconnect command");
188 self.shutdown_send_path(
189 "WebSocket disconnected before response was received",
190 )
191 .await;
192
193 if let Some(ref client) = self.client {
194 client.disconnect().await;
195 }
196 self.signal.store(true, Ordering::SeqCst);
197 return None;
198 }
199 }
200 }
201
202 raw = self.raw_rx.recv(), if !self.raw_closed => {
203 match raw {
204 None => {
205 self.raw_closed = true;
206
207 if self.cmd_closed {
208 self.shutdown_send_path(
209 "WebSocket handler stopped before response was received",
210 )
211 .await;
212 return None;
213 }
214 }
215 Some(Message::Text(text)) => {
216 if text.as_str() == RECONNECTED {
217 log::info!("Derive WebSocket reconnected sentinel received");
218 self.auth_tracker.invalidate();
219 self.authenticated_epoch.store(
220 UNAUTHENTICATED_CONNECTION_EPOCH,
221 Ordering::Release,
222 );
223 self.restart_send_worker(
224 "WebSocket reconnected before response was received",
225 )
226 .await;
227 return Some(DeriveWsMessage::Reconnected);
228 }
229
230 match DeriveWsFrame::parse(&text) {
231 Ok(DeriveWsFrame::Response { id, result, error }) => {
232 if let Some(pending) = self.pending.remove(&id) {
233 let outcome = match (result, error) {
234 (_, Some(err)) => Err(DeriveWsError::JsonRpc {
235 code: err.code,
236 message: err.message,
237 data: err.data,
238 }),
239 (Some(value), None) => Ok(value),
240 (None, None) => Ok(Value::Null),
241 };
242 let _ = pending.response_tx.send(outcome);
243 } else {
244 log::debug!(
245 "Derive WebSocket response with unknown id={id} dropped",
246 );
247 }
248 }
249 Ok(DeriveWsFrame::Subscription(payload)) => {
250 return Some(DeriveWsMessage::Subscription(payload));
251 }
252 Ok(DeriveWsFrame::UncorrelatedError(error)) => {
253 self.fail_uncorrelated_error(error);
254 }
255 Ok(DeriveWsFrame::Unknown(value)) => {
256 log::debug!("Derive WebSocket unknown frame: {value}");
257 }
258 Err(e) => {
259 log::error!(
260 "Derive WebSocket frame parse error: {e}, text: {text}",
261 );
262 }
263 }
264 }
265 Some(Message::Ping(data)) => {
266 if let Some(ref client) = self.client
267 && let Err(e) = client.send_pong(data.to_vec()).await {
268 log::error!("Derive WebSocket send_pong failed: {e}");
269 }
270 }
271 Some(Message::Close(_)) => {
272 log::debug!("Derive WebSocket close frame received");
273 self.shutdown_send_path(
274 "WebSocket closed before response was received",
275 )
276 .await;
277 return None;
278 }
279 Some(_) => {}
280 }
281 }
282
283 Some(failure) = self.send_failure_rx.recv() => {
284 self.handle_send_failure(failure);
285 }
286
287 else => {
288 log::debug!("Derive handler shutting down: channels closed");
289 self.shutdown_send_path(
290 "WebSocket handler stopped before response was received",
291 )
292 .await;
293 return None;
294 }
295 }
296 }
297 }
298
299 fn dispatch_request(
300 &mut self,
301 method: &'static str,
302 params: Value,
303 connection_epoch: Option<u64>,
304 response_tx: tokio::sync::oneshot::Sender<Result<Value, DeriveWsError>>,
305 ) {
306 let Some(client) = self.client.as_ref() else {
307 let _ = response_tx.send(Err(DeriveWsError::NotConnected));
308 return;
309 };
310 let current_epoch = client.connection_epoch();
311 let connection_epoch = if method.starts_with("private/") {
312 let authenticated_epoch = self.authenticated_epoch.load(Ordering::Acquire);
313 if !client.connection_mode().is_active() || authenticated_epoch != current_epoch {
314 let _ = response_tx.send(Err(DeriveWsError::Authentication {
315 operation: method.to_string(),
316 reason: "WebSocket session is not authenticated".to_string(),
317 }));
318 return;
319 }
320 authenticated_epoch
321 } else {
322 connection_epoch.unwrap_or(current_epoch)
323 };
324
325 if !client.connection_mode().is_active() || connection_epoch != current_epoch {
326 let _ = response_tx.send(Err(DeriveWsError::transport(format!(
327 "connection changed before `{method}` was sent",
328 ))));
329 return;
330 }
331
332 self.enqueue_request(method, params, connection_epoch, response_tx);
333 }
334
335 fn enqueue_request(
336 &mut self,
337 method: &'static str,
338 params: Value,
339 connection_epoch: u64,
340 response_tx: tokio::sync::oneshot::Sender<Result<Value, DeriveWsError>>,
341 ) {
342 let Some(send_tx) = self.send_tx.clone() else {
343 let _ = response_tx.send(Err(DeriveWsError::NotConnected));
344 return;
345 };
346 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
347 let token = self.next_send_token;
348 self.next_send_token = self.next_send_token.wrapping_add(1);
349 let request = JsonRpcRequest::new(id, method, params);
350 let payload = match serde_json::to_string(&request) {
351 Ok(p) => p,
352 Err(e) => {
353 let _ = response_tx.send(Err(DeriveWsError::Serde(e)));
354 return;
355 }
356 };
357 self.pending
358 .insert(id, PendingRequest { token, response_tx });
359 log::debug!("Derive WebSocket sending `{method}` id={id}");
360 if let Err(e) = send_tx.send(SendCommand {
361 id,
362 token,
363 connection_epoch,
364 payload,
365 }) && self
366 .pending
367 .get(&id)
368 .is_some_and(|pending| pending.token == token)
369 && let Some(pending) = self.pending.remove(&id)
370 {
371 let _ = pending
372 .response_tx
373 .send(Err(DeriveWsError::transport(format!(
374 "failed to queue WebSocket request: {e}",
375 ))));
376 }
377 }
378
379 fn start_send_worker(&mut self, client: Arc<WebSocketClient>) {
380 if !self.send_task.is_empty() {
381 log::error!("Cannot start Derive WebSocket send worker while the prior task is owned");
382 return;
383 }
384
385 let (send_tx, send_rx) = tokio::sync::mpsc::unbounded_channel();
386 let (failure_tx, failure_rx) = tokio::sync::mpsc::unbounded_channel();
387 self.send_tx = Some(send_tx);
388 self.send_failure_rx = failure_rx;
389
390 if let Err(e) = self
391 .send_task
392 .spawn(run_send_worker(client, send_rx, failure_tx))
393 {
394 self.send_tx.take();
395 log::error!("Failed to start Derive WebSocket send worker: {e}");
396 }
397 }
398
399 async fn stop_send_worker(&mut self) {
400 self.send_tx.take();
401
402 if let Some(outcome) = self
403 .send_task
404 .finish(Duration::from_secs(1), Duration::from_secs(2))
405 .await
406 {
407 match outcome {
408 TaskJoinOutcome::Completed(()) | TaskJoinOutcome::Aborted => {}
409 TaskJoinOutcome::Failed(e) => {
410 log::error!("Derive WebSocket send worker failed: {e}");
411 }
412 TaskJoinOutcome::Incomplete => {
413 log::error!("Derive WebSocket send worker did not stop after abort");
414 }
415 }
416 }
417 }
418
419 async fn restart_send_worker(&mut self, reason: &str) {
420 self.stop_send_worker().await;
421 self.fail_pending(reason);
422
423 if let Some(client) = self.client.clone() {
424 self.start_send_worker(client);
425 }
426 }
427
428 async fn shutdown_send_path(&mut self, reason: &str) {
429 self.stop_send_worker().await;
430 self.fail_pending(reason);
431 }
432
433 fn handle_send_failure(&mut self, failure: SendFailure) {
434 if !self
435 .pending
436 .get(&failure.id)
437 .is_some_and(|pending| pending.token == failure.token)
438 {
439 return;
440 }
441
442 if let Some(pending) = self.pending.remove(&failure.id) {
443 let _ = pending
444 .response_tx
445 .send(Err(DeriveWsError::transport(failure.reason)));
446 }
447 }
448
449 fn fail_uncorrelated_error(&mut self, error: crate::http::models::JsonRpcError) {
450 if self.pending.len() != 1 {
451 log::warn!(
452 "Derive WebSocket uncorrelated JSON-RPC error with {} pending requests: code={}, message={}",
453 self.pending.len(),
454 error.code,
455 error.message,
456 );
457 return;
458 }
459
460 if let Some((_, pending)) = self.pending.drain().next() {
461 let _ = pending.response_tx.send(Err(DeriveWsError::JsonRpc {
462 code: error.code,
463 message: error.message,
464 data: error.data,
465 }));
466 }
467 }
468
469 fn fail_pending(&mut self, reason: &str) {
470 if self.pending.is_empty() {
471 return;
472 }
473 log::debug!(
474 "Failing {} pending Derive WebSocket request(s): {reason}",
475 self.pending.len(),
476 );
477
478 for (_, pending) in self.pending.drain() {
479 let _ = pending
480 .response_tx
481 .send(Err(DeriveWsError::transport(reason.to_string())));
482 }
483 }
484}
485
486impl Drop for FeedHandler {
487 fn drop(&mut self) {
488 self.send_tx.take();
489 self.send_task.abort();
490 }
491}
492
493async fn run_send_worker(
494 client: Arc<WebSocketClient>,
495 mut send_rx: tokio::sync::mpsc::UnboundedReceiver<SendCommand>,
496 failure_tx: tokio::sync::mpsc::UnboundedSender<SendFailure>,
497) {
498 while let Some(command) = send_rx.recv().await {
499 if let Err(e) = client
500 .send_text_on_connection(command.payload, None, command.connection_epoch)
501 .await
502 && failure_tx
503 .send(SendFailure {
504 id: command.id,
505 token: command.token,
506 reason: e.to_string(),
507 })
508 .is_err()
509 {
510 return;
511 }
512 }
513}
514
515#[must_use]
517pub(super) fn subscribe_params(channel: DeriveWsChannel) -> WsSubscribeParams {
518 WsSubscribeParams {
519 channels: vec![channel],
520 }
521}
522
523#[must_use]
526pub(super) fn ticker_subscribe_params(instrument_name: &str, interval: &str) -> WsSubscribeParams {
527 subscribe_params(DeriveWsChannel::ticker_slim(instrument_name, interval))
528}
529
530#[must_use]
533pub(super) fn orderbook_subscribe_params(
534 instrument_name: &str,
535 group: &str,
536 depth: &str,
537) -> WsSubscribeParams {
538 subscribe_params(DeriveWsChannel::orderbook(instrument_name, group, depth))
539}
540
541#[must_use]
544pub(super) fn trades_subscribe_params(instrument_type: &str, currency: &str) -> WsSubscribeParams {
545 subscribe_params(DeriveWsChannel::trades(instrument_type, currency))
546}
547
548#[cfg(test)]
549mod tests {
550 use rstest::rstest;
551 use serde_json::json;
552
553 use super::*;
554
555 fn unauthenticated_epoch() -> Arc<AtomicU64> {
556 Arc::new(AtomicU64::new(u64::MAX))
557 }
558
559 fn feed_handler(
560 signal: Arc<AtomicBool>,
561 cmd_rx: tokio::sync::mpsc::UnboundedReceiver<HandlerCommand>,
562 raw_rx: tokio::sync::mpsc::UnboundedReceiver<Message>,
563 next_id: Arc<AtomicU64>,
564 auth_tracker: AuthTracker,
565 authenticated_epoch: Arc<AtomicU64>,
566 ) -> FeedHandler {
567 FeedHandler::new_with_send_task(
568 signal,
569 cmd_rx,
570 raw_rx,
571 next_id,
572 auth_tracker,
573 authenticated_epoch,
574 Arc::new(SharedTaskSlot::new()),
575 )
576 }
577
578 #[rstest]
579 fn test_subscribe_params_carries_single_channel() {
580 let params = subscribe_params(DeriveWsChannel::ticker_slim("ETH-PERP", "1000"));
581 assert_eq!(
582 params.channels,
583 vec![DeriveWsChannel::ticker_slim("ETH-PERP", "1000")],
584 );
585 }
586
587 #[rstest]
588 fn test_ticker_subscribe_params_formats_topic() {
589 let params = ticker_subscribe_params("ETH-PERP", "1000");
590 assert_eq!(
591 params.channels,
592 vec![DeriveWsChannel::ticker_slim("ETH-PERP", "1000")],
593 );
594 }
595
596 #[rstest]
597 fn test_orderbook_subscribe_params_formats_topic() {
598 let params = orderbook_subscribe_params("ETH-PERP", "1", "10");
599 assert_eq!(
600 params.channels,
601 vec![DeriveWsChannel::orderbook("ETH-PERP", "1", "10")],
602 );
603 }
604
605 #[rstest]
606 fn test_trades_subscribe_params_formats_topic() {
607 let params = trades_subscribe_params("perp", "ETH");
608 assert_eq!(
609 params.channels,
610 vec![DeriveWsChannel::trades("perp", "ETH")],
611 );
612 }
613
614 #[rstest]
615 #[tokio::test]
616 async fn test_dispatch_request_without_client_returns_not_connected() {
617 let signal = Arc::new(AtomicBool::new(false));
619 let (_cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
620 let (_raw_tx, raw_rx) = tokio::sync::mpsc::unbounded_channel();
621 let next_id = Arc::new(AtomicU64::new(1));
622 let auth_tracker = AuthTracker::new();
623 let mut handler = feed_handler(
624 signal,
625 cmd_rx,
626 raw_rx,
627 next_id,
628 auth_tracker,
629 unauthenticated_epoch(),
630 );
631
632 let (response_tx, response_rx) = tokio::sync::oneshot::channel();
633 let params = serde_json::to_value(WsSubscribeParams { channels: vec![] }).unwrap();
634 handler.dispatch_request("public/login", params, None, response_tx);
635
636 let outcome = response_rx.await.expect("oneshot resolved");
637 match outcome {
638 Err(DeriveWsError::NotConnected) => {}
639 other => panic!("expected NotConnected, was {other:?}"),
640 }
641 }
642
643 #[rstest]
644 #[tokio::test]
645 async fn test_dispatch_registers_pending_requests_before_ordered_queueing() {
646 let signal = Arc::new(AtomicBool::new(false));
647 let (_cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
648 let (_raw_tx, raw_rx) = tokio::sync::mpsc::unbounded_channel();
649 let next_id = Arc::new(AtomicU64::new(1));
650 let auth_tracker = AuthTracker::new();
651 let mut handler = feed_handler(
652 signal,
653 cmd_rx,
654 raw_rx,
655 next_id,
656 auth_tracker,
657 unauthenticated_epoch(),
658 );
659 let (send_tx, mut send_rx) = tokio::sync::mpsc::unbounded_channel();
660 handler.send_tx = Some(send_tx);
661
662 let (first_tx, _first_rx) = tokio::sync::oneshot::channel();
663 let (second_tx, _second_rx) = tokio::sync::oneshot::channel();
664 handler.enqueue_request("first", json!({"sequence": 1}), 0, first_tx);
665 handler.enqueue_request("second", json!({"sequence": 2}), 0, second_tx);
666
667 let first = send_rx.recv().await.expect("first queued send");
668 let second = send_rx.recv().await.expect("second queued send");
669 let first_payload: Value = serde_json::from_str(&first.payload).unwrap();
670 let second_payload: Value = serde_json::from_str(&second.payload).unwrap();
671
672 assert_eq!(first.id, 1);
673 assert_eq!(second.id, 2);
674 assert_eq!(first_payload["method"], "first");
675 assert_eq!(second_payload["method"], "second");
676 assert_eq!(handler.pending.get(&first.id).unwrap().token, first.token);
677 assert_eq!(handler.pending.get(&second.id).unwrap().token, second.token);
678 }
679
680 #[rstest]
681 #[tokio::test]
682 async fn test_late_send_failure_does_not_remove_reused_request_id() {
683 let signal = Arc::new(AtomicBool::new(false));
684 let (_cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
685 let (_raw_tx, raw_rx) = tokio::sync::mpsc::unbounded_channel();
686 let next_id = Arc::new(AtomicU64::new(1));
687 let auth_tracker = AuthTracker::new();
688 let mut handler = feed_handler(
689 signal,
690 cmd_rx,
691 raw_rx,
692 Arc::clone(&next_id),
693 auth_tracker,
694 unauthenticated_epoch(),
695 );
696 let (send_tx, mut send_rx) = tokio::sync::mpsc::unbounded_channel();
697 handler.send_tx = Some(send_tx);
698
699 let (old_tx, old_rx) = tokio::sync::oneshot::channel();
700 handler.enqueue_request("first", json!({}), 0, old_tx);
701 let old_send = send_rx.recv().await.expect("old queued send");
702 let old_pending = handler.pending.remove(&old_send.id).unwrap();
703 old_pending.response_tx.send(Ok(Value::Null)).unwrap();
704 old_rx.await.unwrap().unwrap();
705
706 next_id.store(old_send.id, Ordering::Relaxed);
707 let (new_tx, new_rx) = tokio::sync::oneshot::channel();
708 handler.enqueue_request("second", json!({}), 0, new_tx);
709 let new_send = send_rx.recv().await.expect("new queued send");
710 handler.handle_send_failure(SendFailure {
711 id: old_send.id,
712 token: old_send.token,
713 reason: "late failure".to_string(),
714 });
715
716 assert_eq!(old_send.id, new_send.id);
717 assert_ne!(old_send.token, new_send.token);
718 assert_eq!(
719 handler.pending.get(&new_send.id).unwrap().token,
720 new_send.token,
721 );
722
723 handler.handle_send_failure(SendFailure {
724 id: new_send.id,
725 token: new_send.token,
726 reason: "current failure".to_string(),
727 });
728 let error = new_rx.await.unwrap().expect_err("current request failed");
729 assert!(error.to_string().contains("current failure"));
730 }
731
732 #[rstest]
733 #[tokio::test]
734 async fn test_shutdown_aborts_send_worker_and_drains_pending_requests() {
735 let signal = Arc::new(AtomicBool::new(false));
736 let (_cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
737 let (_raw_tx, raw_rx) = tokio::sync::mpsc::unbounded_channel();
738 let next_id = Arc::new(AtomicU64::new(1));
739 let auth_tracker = AuthTracker::new();
740 let mut handler = feed_handler(
741 signal,
742 cmd_rx,
743 raw_rx,
744 next_id,
745 auth_tracker,
746 unauthenticated_epoch(),
747 );
748 let (send_tx, _send_rx) = tokio::sync::mpsc::unbounded_channel();
749 handler.send_tx = Some(send_tx);
750
751 let task = tokio::spawn(std::future::pending::<()>());
752 let abort_handle = task.abort_handle();
753 handler.send_task.insert(task);
754
755 let (first_tx, first_rx) = tokio::sync::oneshot::channel();
756 let (second_tx, second_rx) = tokio::sync::oneshot::channel();
757 handler.enqueue_request("first", json!({}), 0, first_tx);
758 handler.enqueue_request("second", json!({}), 0, second_tx);
759 handler.shutdown_send_path("disconnect requested").await;
760 tokio::task::yield_now().await;
761
762 let first_error = first_rx.await.unwrap().expect_err("first request failed");
763 let second_error = second_rx.await.unwrap().expect_err("second request failed");
764 assert!(abort_handle.is_finished());
765 assert!(handler.send_tx.is_none());
766 assert!(handler.send_task.is_empty());
767 assert!(handler.pending.is_empty());
768 assert!(first_error.to_string().contains("disconnect requested"));
769 assert!(second_error.to_string().contains("disconnect requested"));
770 }
771
772 #[rstest]
773 #[tokio::test]
774 async fn test_next_stops_when_input_channels_close_with_failure_channel_open() {
775 let signal = Arc::new(AtomicBool::new(false));
776 let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
777 let (raw_tx, raw_rx) = tokio::sync::mpsc::unbounded_channel();
778 let next_id = Arc::new(AtomicU64::new(1));
779 let auth_tracker = AuthTracker::new();
780 let mut handler = feed_handler(
781 signal,
782 cmd_rx,
783 raw_rx,
784 next_id,
785 auth_tracker,
786 unauthenticated_epoch(),
787 );
788 let (_failure_tx, failure_rx) = tokio::sync::mpsc::unbounded_channel();
789 handler.send_failure_rx = failure_rx;
790 drop(cmd_tx);
791 drop(raw_tx);
792
793 let outcome = tokio::time::timeout(std::time::Duration::from_millis(100), handler.next())
794 .await
795 .expect("handler stopped after both input channels closed");
796
797 assert!(outcome.is_none());
798 }
799
800 #[rstest]
801 #[tokio::test]
802 async fn test_uncorrelated_error_fails_only_pending_request() {
803 let signal = Arc::new(AtomicBool::new(false));
804 let (_cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
805 let (_raw_tx, raw_rx) = tokio::sync::mpsc::unbounded_channel();
806 let next_id = Arc::new(AtomicU64::new(1));
807 let auth_tracker = AuthTracker::new();
808 let mut handler = feed_handler(
809 signal,
810 cmd_rx,
811 raw_rx,
812 next_id,
813 auth_tracker,
814 unauthenticated_epoch(),
815 );
816 let (send_tx, _send_rx) = tokio::sync::mpsc::unbounded_channel();
817 handler.send_tx = Some(send_tx);
818 let (response_tx, response_rx) = tokio::sync::oneshot::channel();
819 handler.enqueue_request("request", json!({}), 0, response_tx);
820
821 handler.fail_uncorrelated_error(crate::http::models::JsonRpcError {
822 code: -32700,
823 message: "Parse error".to_string(),
824 data: Some(json!("invalid JSON")),
825 });
826 let error = response_rx.await.unwrap().expect_err("request failed");
827
828 match error {
829 DeriveWsError::JsonRpc {
830 code,
831 message,
832 data,
833 } => {
834 assert_eq!(code, -32700);
835 assert_eq!(message, "Parse error");
836 assert_eq!(data, Some(json!("invalid JSON")));
837 }
838 other => panic!("expected JsonRpc, was {other:?}"),
839 }
840 assert!(handler.pending.is_empty());
841 }
842
843 #[rstest]
844 #[tokio::test]
845 async fn test_uncorrelated_error_does_not_guess_between_pending_requests() {
846 let signal = Arc::new(AtomicBool::new(false));
847 let (_cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel();
848 let (_raw_tx, raw_rx) = tokio::sync::mpsc::unbounded_channel();
849 let next_id = Arc::new(AtomicU64::new(1));
850 let auth_tracker = AuthTracker::new();
851 let mut handler = feed_handler(
852 signal,
853 cmd_rx,
854 raw_rx,
855 next_id,
856 auth_tracker,
857 unauthenticated_epoch(),
858 );
859 let (send_tx, _send_rx) = tokio::sync::mpsc::unbounded_channel();
860 handler.send_tx = Some(send_tx);
861 let (first_tx, first_rx) = tokio::sync::oneshot::channel();
862 let (second_tx, second_rx) = tokio::sync::oneshot::channel();
863 handler.enqueue_request("first", json!({}), 0, first_tx);
864 handler.enqueue_request("second", json!({}), 0, second_tx);
865
866 handler.fail_uncorrelated_error(crate::http::models::JsonRpcError {
867 code: -32600,
868 message: "Invalid Request".to_string(),
869 data: None,
870 });
871
872 assert_eq!(handler.pending.len(), 2);
873 handler.fail_pending("test cleanup");
874 assert!(first_rx.await.unwrap().is_err());
875 assert!(second_rx.await.unwrap().is_err());
876 }
877}