1use parking_lot::Mutex;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt::Debug;
5use std::sync::Arc;
6use std::time::Duration;
7use tokio::sync::{Notify, RwLock, oneshot, watch};
8use tokio::task::AbortHandle;
9
10use crate::channel::message::MessageChannel;
11use crate::codec::{BincodeCodec, Codec};
12use crate::error::{Result, RpcError, TransportError};
13use crate::message::Message;
14use crate::message::types::{MessageId, MessageType};
15use crate::streaming::{StreamId, StreamManager, StreamReceiver, next_stream_id};
16
17type PendingRequests<C> = Arc<Mutex<HashMap<MessageId, oneshot::Sender<Result<Message<C>>>>>>;
18type ReceiveTaskAbort = Arc<Mutex<Option<AbortHandle>>>;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[non_exhaustive]
25pub enum CallTimeout {
26 ClientDefault,
28 After(Duration),
30 Disabled,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct CallOptions {
40 timeout: CallTimeout,
41}
42
43impl Default for CallOptions {
44 fn default() -> Self {
45 Self {
46 timeout: CallTimeout::ClientDefault,
47 }
48 }
49}
50
51impl CallOptions {
52 pub const fn with_timeout(timeout: Duration) -> Self {
54 Self {
55 timeout: CallTimeout::After(timeout),
56 }
57 }
58
59 pub const fn without_timeout() -> Self {
64 Self {
65 timeout: CallTimeout::Disabled,
66 }
67 }
68}
69
70#[derive(Debug, Default)]
71struct ClientState {
72 closed: bool,
73 started: bool,
74 running: bool,
75 terminal_error: Option<RpcError>,
76}
77
78#[derive(Debug, Clone, Default)]
79enum TransportCloseState {
80 #[default]
81 Open,
82 Closing,
83 Closed(Result<()>),
84}
85
86struct ClientTaskFinalizer<C: Codec> {
87 state: Arc<Mutex<ClientState>>,
88 pending: PendingRequests<C>,
89 stream_manager: Arc<StreamManager<C>>,
90 send_cancel_tx: watch::Sender<Option<RpcError>>,
91 receive_abort: ReceiveTaskAbort,
92 cleanup_work: bool,
93}
94
95impl<C: Codec> Drop for ClientTaskFinalizer<C> {
96 fn drop(&mut self) {
97 let (error, _) = mark_client_terminal(&self.state, RpcError::ConnectionClosed);
98 self.receive_abort.lock().take();
99 if self.cleanup_work {
100 self.send_cancel_tx.send_replace(Some(error.clone()));
101 close_client_work(&self.pending, &self.stream_manager, error);
102 }
103 }
104}
105
106struct PendingRequestRegistration<C: Codec> {
107 pending: PendingRequests<C>,
108 message_id: MessageId,
109}
110
111impl<C: Codec> PendingRequestRegistration<C> {
112 fn new(
113 pending: &PendingRequests<C>,
114 message_id: MessageId,
115 sender: oneshot::Sender<Result<Message<C>>>,
116 ) -> Self {
117 pending.lock().insert(message_id, sender);
118 Self {
119 pending: Arc::clone(pending),
120 message_id,
121 }
122 }
123}
124
125impl<C: Codec> Drop for PendingRequestRegistration<C> {
126 fn drop(&mut self) {
127 self.pending.lock().remove(&self.message_id);
128 }
129}
130
131fn close_client_work<C: Codec>(
132 pending: &PendingRequests<C>,
133 stream_manager: &StreamManager<C>,
134 error: RpcError,
135) {
136 let pending = {
137 let mut pending = pending.lock();
138 std::mem::take(&mut *pending)
139 };
140 for sender in pending.into_values() {
141 let _ = sender.send(Err(error.clone()));
142 }
143 stream_manager.close_all(error);
144}
145
146fn normalize_transport_error(error: TransportError) -> RpcError {
147 match error {
148 TransportError::ConnectionClosed => RpcError::ConnectionClosed,
149 error => RpcError::Transport(error),
150 }
151}
152
153fn mark_client_terminal(state: &Mutex<ClientState>, error: RpcError) -> (RpcError, bool) {
154 let mut state = state.lock();
155 state.closed = true;
156 state.running = false;
157 if let Some(error) = &state.terminal_error {
158 return (error.clone(), false);
159 }
160 state.terminal_error = Some(error.clone());
161 (error, true)
162}
163
164fn is_terminal_outbound_error(error: &TransportError, is_connected: bool) -> bool {
165 matches!(error, TransportError::ConnectionClosed) || !is_connected
166}
167
168struct TransportCloseFinalizer {
169 state: Arc<Mutex<TransportCloseState>>,
170 notify: Arc<Notify>,
171 completed: bool,
172}
173
174struct ClientCloseFinalizer {
175 state: Arc<Mutex<TransportCloseState>>,
176 notify: Arc<Notify>,
177 completed: bool,
178}
179
180impl ClientCloseFinalizer {
181 fn complete(&mut self, result: Result<()>) {
182 *self.state.lock() = TransportCloseState::Closed(result);
183 self.completed = true;
184 self.notify.notify_waiters();
185 }
186}
187
188impl Drop for ClientCloseFinalizer {
189 fn drop(&mut self) {
190 if self.completed {
191 return;
192 }
193 *self.state.lock() = TransportCloseState::Closed(Err(RpcError::ClientError(
194 "Graceful client close task was cancelled or panicked".to_string(),
195 )));
196 self.notify.notify_waiters();
197 }
198}
199
200impl TransportCloseFinalizer {
201 fn complete(&mut self, result: Result<()>) {
202 *self.state.lock() = TransportCloseState::Closed(result);
203 self.completed = true;
204 self.notify.notify_waiters();
205 }
206}
207
208impl Drop for TransportCloseFinalizer {
209 fn drop(&mut self) {
210 if self.completed {
211 return;
212 }
213 *self.state.lock() = TransportCloseState::Closed(Err(RpcError::ClientError(
214 "Transport close task was cancelled or panicked".to_string(),
215 )));
216 self.notify.notify_waiters();
217 }
218}
219
220async fn close_transport_once<T, C>(
221 transport: &Arc<T>,
222 close_state: &Arc<Mutex<TransportCloseState>>,
223 close_notify: &Arc<Notify>,
224) -> Result<()>
225where
226 T: MessageChannel<C> + 'static,
227 C: Codec + 'static,
228{
229 let should_start = {
230 let mut state = close_state.lock();
231 match &*state {
232 TransportCloseState::Open => {
233 *state = TransportCloseState::Closing;
234 true
235 }
236 TransportCloseState::Closing => false,
237 TransportCloseState::Closed(result) => return result.clone(),
238 }
239 };
240
241 if should_start {
242 let transport = Arc::clone(transport);
243 let close_state = Arc::clone(close_state);
244 let close_notify = Arc::clone(close_notify);
245 tokio::spawn(async move {
246 let mut finalizer = TransportCloseFinalizer {
247 state: close_state,
248 notify: close_notify,
249 completed: false,
250 };
251 let result = transport.close().await.map_err(normalize_transport_error);
252 finalizer.complete(result);
253 });
254 }
255
256 loop {
257 let notified = close_notify.notified();
258 if let TransportCloseState::Closed(result) = &*close_state.lock() {
259 return result.clone();
260 }
261 notified.await;
262 }
263}
264
265#[allow(clippy::too_many_arguments)]
266async fn close_client_gracefully_once<T, C>(
267 transport: &Arc<T>,
268 pending: &PendingRequests<C>,
269 stream_manager: &Arc<StreamManager<C>>,
270 admission: &Arc<RwLock<()>>,
271 stop_tx: &watch::Sender<bool>,
272 transport_close_state: &Arc<Mutex<TransportCloseState>>,
273 transport_close_notify: &Arc<Notify>,
274 client_close_state: &Arc<Mutex<TransportCloseState>>,
275 client_close_notify: &Arc<Notify>,
276 error: RpcError,
277) -> Result<()>
278where
279 T: MessageChannel<C> + 'static,
280 C: Codec + 'static,
281{
282 let should_start = {
283 let mut state = client_close_state.lock();
284 match &*state {
285 TransportCloseState::Open => {
286 *state = TransportCloseState::Closing;
287 true
288 }
289 TransportCloseState::Closing => false,
290 TransportCloseState::Closed(result) => return result.clone(),
291 }
292 };
293
294 if should_start {
295 let transport = Arc::clone(transport);
296 let pending = Arc::clone(pending);
297 let stream_manager = Arc::clone(stream_manager);
298 let admission = Arc::clone(admission);
299 let stop_tx = stop_tx.clone();
300 let transport_close_state = Arc::clone(transport_close_state);
301 let transport_close_notify = Arc::clone(transport_close_notify);
302 let client_close_state = Arc::clone(client_close_state);
303 let client_close_notify = Arc::clone(client_close_notify);
304 tokio::spawn(async move {
305 let mut finalizer = ClientCloseFinalizer {
306 state: client_close_state,
307 notify: client_close_notify,
308 completed: false,
309 };
310 let admission = admission.write().await;
311 stop_tx.send_replace(true);
312 close_client_work(&pending, &stream_manager, error);
313 drop(admission);
314
315 let result =
316 close_transport_once(&transport, &transport_close_state, &transport_close_notify)
317 .await;
318 finalizer.complete(result);
319 });
320 }
321
322 loop {
323 let notified = client_close_notify.notified();
324 if let TransportCloseState::Closed(result) = &*client_close_state.lock() {
325 return result.clone();
326 }
327 notified.await;
328 }
329}
330
331pub struct RpcClient<T, C: Codec = BincodeCodec>
333where
334 T: MessageChannel<C>,
335{
336 transport: Arc<T>,
337 pending: PendingRequests<C>,
338 stream_manager: Arc<StreamManager<C>>,
339 codec: C,
340 state: Arc<Mutex<ClientState>>,
341 admission: Arc<RwLock<()>>,
342 send_cancel_tx: watch::Sender<Option<RpcError>>,
343 stop_tx: watch::Sender<bool>,
344 receive_abort: ReceiveTaskAbort,
345 transport_close_state: Arc<Mutex<TransportCloseState>>,
346 transport_close_notify: Arc<Notify>,
347 client_close_state: Arc<Mutex<TransportCloseState>>,
348 client_close_notify: Arc<Notify>,
349 default_timeout: Duration,
350}
351
352impl<T, C> Drop for RpcClient<T, C>
353where
354 T: MessageChannel<C>,
355 C: Codec,
356{
357 fn drop(&mut self) {
358 let (error, _) = mark_client_terminal(&self.state, RpcError::ConnectionClosed);
359 self.send_cancel_tx.send_replace(Some(error.clone()));
360 self.stop_tx.send_replace(true);
361 if let Some(abort_handle) = self.receive_abort.lock().take() {
362 abort_handle.abort();
363 }
364 close_client_work(&self.pending, &self.stream_manager, error);
365 }
366}
367
368impl<T: MessageChannel<BincodeCodec> + 'static> RpcClient<T, BincodeCodec> {
369 pub fn new(transport: T) -> Self {
370 Self::with_timeout(transport, Duration::from_secs(30))
371 }
372
373 pub fn with_timeout(transport: T, default_timeout: Duration) -> Self {
374 let (send_cancel_tx, _) = watch::channel(None::<RpcError>);
375 let (stop_tx, _) = watch::channel(false);
376 Self {
377 transport: Arc::new(transport),
378 pending: Arc::new(Mutex::new(HashMap::new())),
379 stream_manager: Arc::new(StreamManager::new()),
380 codec: BincodeCodec,
381 state: Arc::new(Mutex::new(ClientState::default())),
382 admission: Arc::new(RwLock::new(())),
383 send_cancel_tx,
384 stop_tx,
385 receive_abort: Arc::new(Mutex::new(None)),
386 transport_close_state: Arc::new(Mutex::new(TransportCloseState::default())),
387 transport_close_notify: Arc::new(Notify::new()),
388 client_close_state: Arc::new(Mutex::new(TransportCloseState::default())),
389 client_close_notify: Arc::new(Notify::new()),
390 default_timeout,
391 }
392 }
393}
394
395impl<T, C> RpcClient<T, C>
396where
397 T: MessageChannel<C> + 'static,
398 C: Codec + Clone + Default + 'static,
399{
400 pub fn with_codec(transport: T, codec: C) -> Self {
401 let (send_cancel_tx, _) = watch::channel(None::<RpcError>);
402 let (stop_tx, _) = watch::channel(false);
403 Self {
404 transport: Arc::new(transport),
405 pending: Arc::new(Mutex::new(HashMap::new())),
406 stream_manager: Arc::new(StreamManager::with_codec(codec.clone())),
407 codec,
408 state: Arc::new(Mutex::new(ClientState::default())),
409 admission: Arc::new(RwLock::new(())),
410 send_cancel_tx,
411 stop_tx,
412 receive_abort: Arc::new(Mutex::new(None)),
413 transport_close_state: Arc::new(Mutex::new(TransportCloseState::default())),
414 transport_close_notify: Arc::new(Notify::new()),
415 client_close_state: Arc::new(Mutex::new(TransportCloseState::default())),
416 client_close_notify: Arc::new(Notify::new()),
417 default_timeout: Duration::from_secs(30),
418 }
419 }
420
421 pub fn with_codec_and_timeout(transport: T, codec: C, default_timeout: Duration) -> Self {
422 let (send_cancel_tx, _) = watch::channel(None::<RpcError>);
423 let (stop_tx, _) = watch::channel(false);
424 Self {
425 transport: Arc::new(transport),
426 pending: Arc::new(Mutex::new(HashMap::new())),
427 stream_manager: Arc::new(StreamManager::with_codec(codec.clone())),
428 codec,
429 state: Arc::new(Mutex::new(ClientState::default())),
430 admission: Arc::new(RwLock::new(())),
431 send_cancel_tx,
432 stop_tx,
433 receive_abort: Arc::new(Mutex::new(None)),
434 transport_close_state: Arc::new(Mutex::new(TransportCloseState::default())),
435 transport_close_notify: Arc::new(Notify::new()),
436 client_close_state: Arc::new(Mutex::new(TransportCloseState::default())),
437 client_close_notify: Arc::new(Notify::new()),
438 default_timeout,
439 }
440 }
441
442 pub fn start(&self) -> RpcClientHandle {
444 self.try_start().expect("RPC client cannot be started")
445 }
446
447 pub fn try_start(&self) -> Result<RpcClientHandle> {
449 {
450 let mut state = self.state.lock();
451 if state.closed {
452 return Err(RpcError::ConnectionClosed);
453 }
454 if state.started {
455 return Err(RpcError::ClientError(
456 "RPC client has already been started".to_string(),
457 ));
458 }
459 state.started = true;
460 state.running = true;
461 }
462
463 let transport = self.transport.clone();
464 let pending = self.pending.clone();
465 let stream_manager = self.stream_manager.clone();
466 let state = self.state.clone();
467 let admission = self.admission.clone();
468 let codec = self.codec.clone();
469 let send_cancel_tx = self.send_cancel_tx.clone();
470 let mut stop_rx = self.stop_tx.subscribe();
471 let receive_abort = self.receive_abort.clone();
472 let transport_close_state = self.transport_close_state.clone();
473 let transport_close_notify = self.transport_close_notify.clone();
474 let (task_ready_tx, task_ready_rx) = oneshot::channel();
475 let finalizer = ClientTaskFinalizer {
476 state: state.clone(),
477 pending: pending.clone(),
478 stream_manager: stream_manager.clone(),
479 send_cancel_tx: send_cancel_tx.clone(),
480 receive_abort: receive_abort.clone(),
481 cleanup_work: true,
482 };
483
484 let handle = tokio::spawn(async move {
485 let mut finalizer = finalizer;
486 if task_ready_rx.await.is_err() {
487 return;
488 }
489 loop {
490 tokio::select! {
491 stop_result = stop_rx.changed() => {
492 if stop_result.is_err() || *stop_rx.borrow() {
493 finalizer.cleanup_work = false;
494 break;
495 }
496 }
497 receive_result = transport.recv() => match receive_result {
498 Ok(message) => match message.msg_type {
499 MessageType::Reply => {
500 if let Some(tx) = pending.lock().remove(&message.id) {
501 let _ = tx.send(Ok(message));
502 }
503 }
504 MessageType::Error => {
505 if let Some(stream_id) = message.metadata.stream_id {
506 let error_msg: String = codec
507 .decode(&message.payload)
508 .unwrap_or_else(|_| "Unknown error".to_string());
509 stream_manager.send_error(stream_id, error_msg);
510 } else if let Some(tx) = pending.lock().remove(&message.id) {
511 let _ = tx.send(Ok(message));
512 }
513 }
514 MessageType::StreamChunk | MessageType::StreamEnd => {
515 stream_manager.handle_message(&message);
516 }
517 _ => {}
518 },
519 Err(error) => {
520 if matches!(&error, TransportError::Timeout { .. })
521 && transport.is_connected()
522 {
523 continue;
524 }
525
526 let error = normalize_transport_error(error);
527 let (error, _) = mark_client_terminal(&state, error);
528 send_cancel_tx.send_replace(Some(error.clone()));
529 close_client_work(&pending, &stream_manager, error);
530 finalizer.cleanup_work = false;
531 {
532 let _admission = admission.write().await;
533 }
534 let _ = close_transport_once(
535 &transport,
536 &transport_close_state,
537 &transport_close_notify,
538 ).await;
539 break;
540 },
541 }
542 }
543 }
544 });
545
546 *receive_abort.lock() = Some(handle.abort_handle());
547 let _ = task_ready_tx.send(());
548
549 Ok(RpcClientHandle { handle })
550 }
551
552 fn ensure_running(state: &ClientState) -> Result<()> {
553 if !state.started && !state.closed {
554 return Err(RpcError::ClientError(
555 "RPC client has not been started".to_string(),
556 ));
557 }
558 if state.closed || !state.running {
559 return Err(state
560 .terminal_error
561 .clone()
562 .unwrap_or(RpcError::ConnectionClosed));
563 }
564 Ok(())
565 }
566
567 async fn send_until_committed(&self, message: &Message<C>) -> Result<()> {
568 let mut cancel_rx = self.send_cancel_tx.subscribe();
569 if let Some(error) = cancel_rx.borrow().clone() {
570 return Err(error);
571 }
572
573 tokio::select! {
574 biased;
575 result = self.transport.send(message) => match result {
576 Ok(()) => Ok(()),
577 Err(error) => {
578 let terminal = is_terminal_outbound_error(
579 &error,
580 self.transport.is_connected(),
581 );
582 let error = normalize_transport_error(error);
583 if terminal {
584 Err(self.handle_terminal_outbound_error(error))
585 } else {
586 Err(error)
587 }
588 }
589 },
590 cancel_result = cancel_rx.changed() => {
591 let _ = cancel_result;
592 Err(cancel_rx
593 .borrow()
594 .clone()
595 .unwrap_or(RpcError::ConnectionClosed))
596 }
597 }
598 }
599
600 fn handle_terminal_outbound_error(&self, error: RpcError) -> RpcError {
601 let (error, claimed) = mark_client_terminal(&self.state, error);
602 if !claimed {
603 return error;
604 }
605
606 self.send_cancel_tx.send_replace(Some(error.clone()));
607 close_client_work(&self.pending, &self.stream_manager, error.clone());
608 self.stop_tx.send_replace(true);
609
610 let admission = Arc::clone(&self.admission);
611 let transport = Arc::clone(&self.transport);
612 let close_state = Arc::clone(&self.transport_close_state);
613 let close_notify = Arc::clone(&self.transport_close_notify);
614 tokio::spawn(async move {
615 {
616 let _admission = admission.write().await;
617 }
618 let _ = close_transport_once(&transport, &close_state, &close_notify).await;
619 });
620
621 error
622 }
623
624 pub fn transport(&self) -> Arc<T> {
625 self.transport.clone()
626 }
627
628 pub fn stream_manager(&self) -> Arc<StreamManager<C>> {
629 self.stream_manager.clone()
630 }
631
632 async fn wait_for_response(
633 &self,
634 msg_id: MessageId,
635 rx: oneshot::Receiver<Result<Message<C>>>,
636 timeout: CallTimeout,
637 ) -> Result<Message<C>> {
638 let timeout = match timeout {
639 CallTimeout::ClientDefault => Some(self.default_timeout),
640 CallTimeout::After(timeout) => Some(timeout),
641 CallTimeout::Disabled => None,
642 };
643
644 match timeout {
645 Some(timeout) => tokio::time::timeout(timeout, rx)
646 .await
647 .map_err(|_| {
648 RpcError::Timeout(format!("Request {} timed out after {:?}", msg_id, timeout))
649 })?
650 .map_err(|_| RpcError::ConnectionClosed)?,
651 None => {
652 let mut cancel_rx = self.send_cancel_tx.subscribe();
653 if let Some(error) = cancel_rx.borrow().clone() {
654 return Err(error);
655 }
656
657 tokio::select! {
658 biased;
659 response = rx => response.map_err(|_| RpcError::ConnectionClosed)?,
660 changed = cancel_rx.changed() => {
661 let _ = changed;
662 Err(cancel_rx
663 .borrow()
664 .clone()
665 .unwrap_or(RpcError::ConnectionClosed))
666 }
667 }
668 }
669 }
670 }
671
672 pub async fn call<Req, Resp>(&self, method: &str, request: &Req) -> Result<Resp>
674 where
675 Req: Serialize,
676 Resp: for<'de> Deserialize<'de>,
677 {
678 self.call_with_options(method, request, CallOptions::default())
679 .await
680 }
681
682 pub async fn call_with_timeout<Req, Resp>(
684 &self,
685 method: &str,
686 request: &Req,
687 timeout: Duration,
688 ) -> Result<Resp>
689 where
690 Req: Serialize,
691 Resp: for<'de> Deserialize<'de>,
692 {
693 self.call_with_options(method, request, CallOptions::with_timeout(timeout))
694 .await
695 }
696
697 pub async fn call_with_options<Req, Resp>(
703 &self,
704 method: &str,
705 request: &Req,
706 options: CallOptions,
707 ) -> Result<Resp>
708 where
709 Req: Serialize,
710 Resp: for<'de> Deserialize<'de>,
711 {
712 let _admission = self.admission.read().await;
713 {
714 let state = self.state.lock();
715 Self::ensure_running(&state)?;
716 }
717
718 let message = Message::call_with_codec(method, request, self.codec.clone())?;
719 let msg_id = message.id;
720 let (tx, rx) = oneshot::channel();
721 let _registration = PendingRequestRegistration::new(&self.pending, msg_id, tx);
722
723 self.send_until_committed(&message).await?;
724
725 let response = self.wait_for_response(msg_id, rx, options.timeout).await?;
726
727 match response.msg_type {
728 MessageType::Reply => self.codec.decode(&response.payload),
729 MessageType::Error => {
730 let error_msg: String = self
731 .codec
732 .decode(&response.payload)
733 .unwrap_or_else(|_| "Unknown error".to_string());
734 Err(RpcError::ServerError(error_msg))
735 }
736 _ => Err(RpcError::InvalidMessage(format!(
737 "Unexpected message type: {:?}",
738 response.msg_type
739 ))),
740 }
741 }
742
743 pub async fn call_server_stream<Req, Resp>(
745 &self,
746 method: &str,
747 request: &Req,
748 ) -> Result<StreamReceiver<Resp, C>>
749 where
750 Req: Serialize,
751 Resp: for<'de> Deserialize<'de>,
752 {
753 self.call_server_stream_with_id(method, request, next_stream_id())
754 .await
755 }
756
757 pub(crate) async fn call_server_stream_with_id<Req, Resp>(
758 &self,
759 method: &str,
760 request: &Req,
761 stream_id: StreamId,
762 ) -> Result<StreamReceiver<Resp, C>>
763 where
764 Req: Serialize,
765 Resp: for<'de> Deserialize<'de>,
766 {
767 let _admission = self.admission.read().await;
768 {
769 let state = self.state.lock();
770 Self::ensure_running(&state)?;
771 }
772
773 let mut message = Message::call_with_codec(method, request, self.codec.clone())?;
774 message.metadata = message.metadata.with_stream(stream_id, 0);
775 let receiver = self.stream_manager.create_receiver::<Resp>(stream_id);
776
777 self.send_until_committed(&message).await?;
778
779 Ok(receiver)
780 }
781
782 pub async fn notify<Req: Serialize>(&self, method: &str, request: &Req) -> Result<()> {
784 let _admission = self.admission.read().await;
785 {
786 let state = self.state.lock();
787 Self::ensure_running(&state)?;
788 }
789 let message = Message::notification_with_codec(method, request, self.codec.clone())?;
790
791 self.send_until_committed(&message).await
792 }
793
794 pub async fn call_raw(&self, method: &str, payload: Vec<u8>) -> Result<Vec<u8>> {
796 self.call_raw_with_options(method, payload, CallOptions::default())
797 .await
798 }
799
800 pub async fn call_raw_with_timeout(
802 &self,
803 method: &str,
804 payload: Vec<u8>,
805 timeout: Duration,
806 ) -> Result<Vec<u8>> {
807 self.call_raw_with_options(method, payload, CallOptions::with_timeout(timeout))
808 .await
809 }
810
811 pub async fn call_raw_with_options(
817 &self,
818 method: &str,
819 payload: Vec<u8>,
820 options: CallOptions,
821 ) -> Result<Vec<u8>> {
822 let _admission = self.admission.read().await;
823 {
824 let state = self.state.lock();
825 Self::ensure_running(&state)?;
826 }
827
828 let message = Message::new_with_codec(
829 MessageId::new(),
830 MessageType::Call,
831 method,
832 payload.into(),
833 Default::default(),
834 self.codec.clone(),
835 );
836 let msg_id = message.id;
837 let (tx, rx) = oneshot::channel();
838 let _registration = PendingRequestRegistration::new(&self.pending, msg_id, tx);
839
840 self.send_until_committed(&message).await?;
841
842 let response = self.wait_for_response(msg_id, rx, options.timeout).await?;
843
844 match response.msg_type {
845 MessageType::Reply => Ok(response.payload.to_vec()),
846 MessageType::Error => {
847 let error_msg: String = self
848 .codec
849 .decode(&response.payload)
850 .unwrap_or_else(|_| "Unknown error".to_string());
851 Err(RpcError::ServerError(error_msg))
852 }
853 _ => Err(RpcError::InvalidMessage(format!(
854 "Unexpected message type: {:?}",
855 response.msg_type
856 ))),
857 }
858 }
859
860 pub fn is_connected(&self) -> bool {
861 let state = self.state.lock();
862 state.running && !state.closed && self.transport.is_connected()
863 }
864
865 pub fn is_started(&self) -> bool {
866 self.state.lock().started
867 }
868
869 pub fn is_running(&self) -> bool {
870 self.state.lock().running
871 }
872
873 pub fn is_closed(&self) -> bool {
874 self.state.lock().closed
875 }
876
877 pub fn active_streams(&self) -> usize {
878 self.stream_manager.active_stream_count()
879 }
880
881 pub async fn close(&self) -> Result<()> {
882 let (error, _) = mark_client_terminal(&self.state, RpcError::ConnectionClosed);
883 self.send_cancel_tx.send_replace(Some(error.clone()));
884
885 close_client_gracefully_once(
886 &self.transport,
887 &self.pending,
888 &self.stream_manager,
889 &self.admission,
890 &self.stop_tx,
891 &self.transport_close_state,
892 &self.transport_close_notify,
893 &self.client_close_state,
894 &self.client_close_notify,
895 error,
896 )
897 .await
898 }
899}
900
901impl<T, C> Debug for RpcClient<T, C>
902where
903 T: MessageChannel<C>,
904 C: Codec + Clone,
905{
906 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
907 let state = self.state.lock();
908 f.debug_struct("RpcClient")
909 .field("closed", &state.closed)
910 .field("started", &state.started)
911 .field("running", &state.running)
912 .field("pending_requests", &self.pending.lock().len())
913 .field("active_streams", &self.stream_manager.active_stream_count())
914 .finish()
915 }
916}
917
918pub struct RpcClientHandle {
920 handle: tokio::task::JoinHandle<()>,
921}
922
923impl RpcClientHandle {
924 pub async fn join(self) -> std::result::Result<(), tokio::task::JoinError> {
926 self.handle.await
927 }
928
929 pub async fn shutdown(self) {
931 self.handle.abort();
932 let _ = self.handle.await;
933 }
934}
935
936#[cfg(test)]
937mod tests {
938 use super::*;
939 use crate::channel::message::MessageChannelAdapter;
940 use crate::codec::JsonCodec;
941 use crate::error::TransportResult;
942 use crate::transport::channel::{ChannelConfig, ChannelFrameTransport};
943 use std::collections::VecDeque;
944 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
945 use tokio::sync::Notify;
946
947 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
948 struct AddRequest {
949 a: i32,
950 b: i32,
951 }
952
953 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
954 struct AddResponse {
955 result: i32,
956 }
957
958 #[derive(Debug)]
959 struct LifecycleTestChannel {
960 block_close: bool,
961 block_send: bool,
962 close_count: AtomicUsize,
963 close_release: Notify,
964 close_started: Notify,
965 fail_send: bool,
966 fail_close: bool,
967 panic_close: bool,
968 receive_error: TransportError,
969 receive_failed: Notify,
970 send_release: Notify,
971 sent: Notify,
972 }
973
974 impl LifecycleTestChannel {
975 fn new(fail_send: bool, fail_close: bool) -> Self {
976 Self {
977 block_close: false,
978 block_send: false,
979 close_count: AtomicUsize::new(0),
980 close_release: Notify::new(),
981 close_started: Notify::new(),
982 fail_send,
983 fail_close,
984 panic_close: false,
985 receive_error: TransportError::ConnectionClosed,
986 receive_failed: Notify::new(),
987 send_release: Notify::new(),
988 sent: Notify::new(),
989 }
990 }
991
992 fn with_blocked_send() -> Self {
993 Self {
994 block_send: true,
995 ..Self::new(false, false)
996 }
997 }
998
999 fn with_blocked_close() -> Self {
1000 Self {
1001 block_close: true,
1002 ..Self::new(false, false)
1003 }
1004 }
1005
1006 fn with_blocked_failing_close() -> Self {
1007 Self {
1008 block_close: true,
1009 ..Self::new(false, true)
1010 }
1011 }
1012
1013 fn with_receive_error(receive_error: TransportError) -> Self {
1014 Self {
1015 receive_error,
1016 ..Self::new(false, false)
1017 }
1018 }
1019
1020 fn with_panicking_close() -> Self {
1021 Self {
1022 panic_close: true,
1023 ..Self::new(false, false)
1024 }
1025 }
1026 }
1027
1028 #[async_trait::async_trait]
1029 impl MessageChannel for LifecycleTestChannel {
1030 async fn send(&self, _message: &Message) -> TransportResult<()> {
1031 self.sent.notify_one();
1032 if self.block_send {
1033 self.send_release.notified().await;
1034 return Err(TransportError::ConnectionClosed);
1035 }
1036 if self.fail_send {
1037 return Err(TransportError::ConnectionClosed);
1038 }
1039 Ok(())
1040 }
1041
1042 async fn recv(&self) -> TransportResult<Message> {
1043 self.receive_failed.notified().await;
1044 Err(self.receive_error.clone())
1045 }
1046
1047 fn is_connected(&self) -> bool {
1048 true
1049 }
1050
1051 async fn close(&self) -> TransportResult<()> {
1052 self.close_count.fetch_add(1, Ordering::SeqCst);
1053 self.close_started.notify_one();
1054 if self.panic_close {
1055 panic!("test transport close panic");
1056 }
1057 if self.block_close {
1058 self.close_release.notified().await;
1059 }
1060 self.send_release.notify_one();
1061 if self.fail_close {
1062 return Err(TransportError::ConnectionClosed);
1063 }
1064 Ok(())
1065 }
1066 }
1067
1068 #[derive(Debug, Default)]
1069 struct TimeoutThenReplyChannel {
1070 message_id: Mutex<Option<MessageId>>,
1071 message_sent: Notify,
1072 receive_count: AtomicUsize,
1073 }
1074
1075 #[async_trait::async_trait]
1076 impl MessageChannel for TimeoutThenReplyChannel {
1077 async fn send(&self, message: &Message) -> TransportResult<()> {
1078 *self.message_id.lock() = Some(message.id);
1079 self.message_sent.notify_one();
1080 Ok(())
1081 }
1082
1083 async fn recv(&self) -> TransportResult<Message> {
1084 let receive_count = self.receive_count.fetch_add(1, Ordering::SeqCst);
1085 if receive_count == 0 {
1086 return Err(TransportError::Timeout {
1087 duration_ms: 10,
1088 operation: "receive".to_string(),
1089 });
1090 }
1091 if receive_count > 1 {
1092 return std::future::pending().await;
1093 }
1094
1095 loop {
1096 let notified = self.message_sent.notified();
1097 if let Some(message_id) = *self.message_id.lock() {
1098 return Message::reply(message_id, AddResponse { result: 42 })
1099 .map_err(|error| TransportError::Protocol(error.to_string()));
1100 }
1101 notified.await;
1102 }
1103 }
1104
1105 fn is_connected(&self) -> bool {
1106 true
1107 }
1108
1109 async fn close(&self) -> TransportResult<()> {
1110 Ok(())
1111 }
1112 }
1113
1114 #[derive(Debug, Default)]
1115 struct CommitTestChannel {
1116 close_count: AtomicUsize,
1117 committed: Notify,
1118 message_id: Mutex<Option<MessageId>>,
1119 reply_release: Notify,
1120 send_release: Notify,
1121 send_started: Notify,
1122 }
1123
1124 #[async_trait::async_trait]
1125 impl MessageChannel for CommitTestChannel {
1126 async fn send(&self, message: &Message) -> TransportResult<()> {
1127 self.send_started.notify_one();
1128 self.send_release.notified().await;
1129 *self.message_id.lock() = Some(message.id);
1130 self.committed.notify_one();
1131 Ok(())
1132 }
1133
1134 async fn recv(&self) -> TransportResult<Message> {
1135 self.reply_release.notified().await;
1136 let message_id = self
1137 .message_id
1138 .lock()
1139 .expect("send must commit before reply release");
1140 Message::reply(message_id, AddResponse { result: 3 })
1141 .map_err(|error| TransportError::Protocol(error.to_string()))
1142 }
1143
1144 fn is_connected(&self) -> bool {
1145 true
1146 }
1147
1148 async fn close(&self) -> TransportResult<()> {
1149 self.close_count.fetch_add(1, Ordering::SeqCst);
1150 Ok(())
1151 }
1152 }
1153
1154 #[derive(Debug)]
1155 struct TerminalSendChannel {
1156 close_count: AtomicUsize,
1157 close_started: Notify,
1158 connected: AtomicBool,
1159 fail_send: AtomicBool,
1160 sent: Notify,
1161 }
1162
1163 impl Default for TerminalSendChannel {
1164 fn default() -> Self {
1165 Self {
1166 close_count: AtomicUsize::new(0),
1167 close_started: Notify::new(),
1168 connected: AtomicBool::new(true),
1169 fail_send: AtomicBool::new(false),
1170 sent: Notify::new(),
1171 }
1172 }
1173 }
1174
1175 #[async_trait::async_trait]
1176 impl MessageChannel for TerminalSendChannel {
1177 async fn send(&self, _message: &Message) -> TransportResult<()> {
1178 if self.fail_send.load(Ordering::SeqCst) {
1179 return Err(TransportError::NotConnected);
1180 }
1181 self.sent.notify_one();
1182 Ok(())
1183 }
1184
1185 async fn recv(&self) -> TransportResult<Message> {
1186 std::future::pending().await
1187 }
1188
1189 fn is_connected(&self) -> bool {
1190 self.connected.load(Ordering::SeqCst)
1191 }
1192
1193 async fn close(&self) -> TransportResult<()> {
1194 self.close_count.fetch_add(1, Ordering::SeqCst);
1195 self.close_started.notify_one();
1196 Ok(())
1197 }
1198 }
1199
1200 #[derive(Debug)]
1201 struct ConnectedTimeoutChannel {
1202 close_count: AtomicUsize,
1203 fail_next: AtomicBool,
1204 }
1205
1206 impl Default for ConnectedTimeoutChannel {
1207 fn default() -> Self {
1208 Self {
1209 close_count: AtomicUsize::new(0),
1210 fail_next: AtomicBool::new(true),
1211 }
1212 }
1213 }
1214
1215 #[async_trait::async_trait]
1216 impl MessageChannel for ConnectedTimeoutChannel {
1217 async fn send(&self, _message: &Message) -> TransportResult<()> {
1218 if self.fail_next.swap(false, Ordering::SeqCst) {
1219 return Err(TransportError::Timeout {
1220 duration_ms: 10,
1221 operation: "send".to_string(),
1222 });
1223 }
1224 Ok(())
1225 }
1226
1227 async fn recv(&self) -> TransportResult<Message> {
1228 std::future::pending().await
1229 }
1230
1231 fn is_connected(&self) -> bool {
1232 true
1233 }
1234
1235 async fn close(&self) -> TransportResult<()> {
1236 self.close_count.fetch_add(1, Ordering::SeqCst);
1237 Ok(())
1238 }
1239 }
1240
1241 #[derive(Debug)]
1242 struct DelayedReplyChannel {
1243 connected: AtomicBool,
1244 delay: Duration,
1245 raw: bool,
1246 requests: Mutex<VecDeque<(MessageId, Vec<u8>)>>,
1247 request_notify: Notify,
1248 }
1249
1250 impl DelayedReplyChannel {
1251 fn typed(delay: Duration) -> Self {
1252 Self::new(delay, false)
1253 }
1254
1255 fn raw(delay: Duration) -> Self {
1256 Self::new(delay, true)
1257 }
1258
1259 fn new(delay: Duration, raw: bool) -> Self {
1260 Self {
1261 connected: AtomicBool::new(true),
1262 delay,
1263 raw,
1264 requests: Mutex::new(VecDeque::new()),
1265 request_notify: Notify::new(),
1266 }
1267 }
1268
1269 async fn next_request(&self) -> (MessageId, Vec<u8>) {
1270 loop {
1271 let notified = self.request_notify.notified();
1272 if let Some(request) = self.requests.lock().pop_front() {
1273 return request;
1274 }
1275 notified.await;
1276 }
1277 }
1278 }
1279
1280 #[async_trait::async_trait]
1281 impl MessageChannel for DelayedReplyChannel {
1282 async fn send(&self, message: &Message) -> TransportResult<()> {
1283 self.requests
1284 .lock()
1285 .push_back((message.id, message.payload.to_vec()));
1286 self.request_notify.notify_one();
1287 Ok(())
1288 }
1289
1290 async fn recv(&self) -> TransportResult<Message> {
1291 let (message_id, payload) = self.next_request().await;
1292 tokio::time::sleep(self.delay).await;
1293 if self.raw {
1294 Ok(Message::new(
1295 message_id,
1296 MessageType::Reply,
1297 "",
1298 payload.into(),
1299 Default::default(),
1300 ))
1301 } else {
1302 Message::reply(message_id, AddResponse { result: 42 })
1303 .map_err(|error| TransportError::Protocol(error.to_string()))
1304 }
1305 }
1306
1307 fn is_connected(&self) -> bool {
1308 self.connected.load(Ordering::Acquire)
1309 }
1310
1311 async fn close(&self) -> TransportResult<()> {
1312 self.connected.store(false, Ordering::Release);
1313 self.request_notify.notify_waiters();
1314 Ok(())
1315 }
1316 }
1317
1318 #[derive(Debug, Clone)]
1319 struct StatefulCodec {
1320 tag: Arc<str>,
1321 }
1322
1323 impl StatefulCodec {
1324 fn configured() -> Self {
1325 Self {
1326 tag: Arc::from("configured"),
1327 }
1328 }
1329 }
1330
1331 impl Default for StatefulCodec {
1332 fn default() -> Self {
1333 Self {
1334 tag: Arc::from("default"),
1335 }
1336 }
1337 }
1338
1339 impl Codec for StatefulCodec {
1340 fn encode<T: Serialize>(&self, data: &T) -> Result<Vec<u8>> {
1341 let mut payload = self.tag.as_bytes().to_vec();
1342 payload.push(b':');
1343 payload.extend(serde_json::to_vec(data)?);
1344 Ok(payload)
1345 }
1346
1347 fn decode<T: for<'de> Deserialize<'de>>(&self, data: &[u8]) -> Result<T> {
1348 let mut prefix = self.tag.as_bytes().to_vec();
1349 prefix.push(b':');
1350 let payload = data.strip_prefix(prefix.as_slice()).ok_or_else(|| {
1351 RpcError::Serialization(format!("missing {} codec prefix", self.tag))
1352 })?;
1353 Ok(serde_json::from_slice(payload)?)
1354 }
1355 }
1356
1357 #[tokio::test]
1358 async fn test_call_options_default_and_explicit_timeout_resolution() {
1359 let default_client = RpcClient::with_timeout(
1360 DelayedReplyChannel::typed(Duration::from_millis(75)),
1361 Duration::from_millis(20),
1362 );
1363 let default_handle = default_client.start();
1364 let result: Result<AddResponse> = default_client
1365 .call_with_options("add", &AddRequest { a: 20, b: 22 }, CallOptions::default())
1366 .await;
1367 assert!(matches!(result, Err(RpcError::Timeout(_))));
1368 default_client.close().await.unwrap();
1369 default_handle.join().await.unwrap();
1370
1371 let override_client = RpcClient::with_timeout(
1372 DelayedReplyChannel::typed(Duration::from_millis(75)),
1373 Duration::from_millis(20),
1374 );
1375 let override_handle = override_client.start();
1376 let response: AddResponse = override_client
1377 .call_with_options(
1378 "add",
1379 &AddRequest { a: 20, b: 22 },
1380 CallOptions::with_timeout(Duration::from_millis(250)),
1381 )
1382 .await
1383 .unwrap();
1384 assert_eq!(response.result, 42);
1385 override_client.close().await.unwrap();
1386 override_handle.join().await.unwrap();
1387 }
1388
1389 #[tokio::test]
1390 async fn test_deadline_free_typed_call_exceeds_client_default_timeout() {
1391 let client = RpcClient::with_timeout(
1392 DelayedReplyChannel::typed(Duration::from_millis(75)),
1393 Duration::from_millis(20),
1394 );
1395 let handle = client.start();
1396
1397 let response: AddResponse = client
1398 .call_with_options(
1399 "add",
1400 &AddRequest { a: 20, b: 22 },
1401 CallOptions::without_timeout(),
1402 )
1403 .await
1404 .unwrap();
1405 assert_eq!(response.result, 42);
1406
1407 client.close().await.unwrap();
1408 handle.join().await.unwrap();
1409 }
1410
1411 #[tokio::test]
1412 async fn test_deadline_free_raw_call_exceeds_client_default_timeout() {
1413 let client = RpcClient::with_timeout(
1414 DelayedReplyChannel::raw(Duration::from_millis(75)),
1415 Duration::from_millis(20),
1416 );
1417 let handle = client.start();
1418 let payload = vec![1, 2, 3, 4];
1419
1420 let response = client
1421 .call_raw_with_options("raw", payload.clone(), CallOptions::without_timeout())
1422 .await
1423 .unwrap();
1424 assert_eq!(response, payload);
1425
1426 client.close().await.unwrap();
1427 handle.join().await.unwrap();
1428 }
1429
1430 #[tokio::test]
1431 async fn test_client_close_unblocks_deadline_free_typed_call() {
1432 let client = Arc::new(RpcClient::new(LifecycleTestChannel::new(false, false)));
1433 let transport = client.transport();
1434 let handle = client.start();
1435 let call_client = Arc::clone(&client);
1436 let call = tokio::spawn(async move {
1437 call_client
1438 .call_with_options::<_, AddResponse>(
1439 "add",
1440 &AddRequest { a: 1, b: 2 },
1441 CallOptions::without_timeout(),
1442 )
1443 .await
1444 });
1445 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
1446 .await
1447 .unwrap();
1448
1449 tokio::time::timeout(Duration::from_secs(1), client.close())
1450 .await
1451 .expect("client close was blocked by a deadline-free typed call")
1452 .unwrap();
1453 assert!(matches!(
1454 call.await.unwrap(),
1455 Err(RpcError::ConnectionClosed)
1456 ));
1457 assert!(client.pending.lock().is_empty());
1458 handle.join().await.unwrap();
1459 }
1460
1461 #[tokio::test]
1462 async fn test_client_close_unblocks_deadline_free_raw_call() {
1463 let client = Arc::new(RpcClient::new(LifecycleTestChannel::new(false, false)));
1464 let transport = client.transport();
1465 let handle = client.start();
1466 let call_client = Arc::clone(&client);
1467 let call = tokio::spawn(async move {
1468 call_client
1469 .call_raw_with_options("raw", vec![1, 2, 3], CallOptions::without_timeout())
1470 .await
1471 });
1472 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
1473 .await
1474 .unwrap();
1475
1476 tokio::time::timeout(Duration::from_secs(1), client.close())
1477 .await
1478 .expect("client close was blocked by a deadline-free raw call")
1479 .unwrap();
1480 assert!(matches!(
1481 call.await.unwrap(),
1482 Err(RpcError::ConnectionClosed)
1483 ));
1484 assert!(client.pending.lock().is_empty());
1485 handle.join().await.unwrap();
1486 }
1487
1488 #[tokio::test]
1489 async fn test_terminal_failure_unblocks_deadline_free_typed_call() {
1490 let client = Arc::new(RpcClient::new(LifecycleTestChannel::with_receive_error(
1491 TransportError::Protocol("terminal typed".to_string()),
1492 )));
1493 let transport = client.transport();
1494 let handle = client.start();
1495 let call_client = Arc::clone(&client);
1496 let call = tokio::spawn(async move {
1497 call_client
1498 .call_with_options::<_, AddResponse>(
1499 "add",
1500 &AddRequest { a: 1, b: 2 },
1501 CallOptions::without_timeout(),
1502 )
1503 .await
1504 });
1505 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
1506 .await
1507 .unwrap();
1508 transport.receive_failed.notify_one();
1509
1510 assert!(matches!(
1511 call.await.unwrap(),
1512 Err(RpcError::Transport(TransportError::Protocol(message)))
1513 if message == "terminal typed"
1514 ));
1515 assert!(client.pending.lock().is_empty());
1516 handle.join().await.unwrap();
1517 client.close().await.unwrap();
1518 }
1519
1520 #[tokio::test]
1521 async fn test_terminal_failure_unblocks_deadline_free_raw_call() {
1522 let client = Arc::new(RpcClient::new(LifecycleTestChannel::with_receive_error(
1523 TransportError::Protocol("terminal raw".to_string()),
1524 )));
1525 let transport = client.transport();
1526 let handle = client.start();
1527 let call_client = Arc::clone(&client);
1528 let call = tokio::spawn(async move {
1529 call_client
1530 .call_raw_with_options("raw", vec![1, 2, 3], CallOptions::without_timeout())
1531 .await
1532 });
1533 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
1534 .await
1535 .unwrap();
1536 transport.receive_failed.notify_one();
1537
1538 assert!(matches!(
1539 call.await.unwrap(),
1540 Err(RpcError::Transport(TransportError::Protocol(message)))
1541 if message == "terminal raw"
1542 ));
1543 assert!(client.pending.lock().is_empty());
1544 handle.join().await.unwrap();
1545 client.close().await.unwrap();
1546 }
1547
1548 #[tokio::test]
1549 async fn test_dropped_deadline_free_typed_call_removes_pending_registration() {
1550 let client = Arc::new(RpcClient::new(LifecycleTestChannel::new(false, false)));
1551 let transport = client.transport();
1552 let handle = client.start();
1553 let call_client = Arc::clone(&client);
1554 let call = tokio::spawn(async move {
1555 call_client
1556 .call_with_options::<_, AddResponse>(
1557 "add",
1558 &AddRequest { a: 1, b: 2 },
1559 CallOptions::without_timeout(),
1560 )
1561 .await
1562 });
1563 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
1564 .await
1565 .unwrap();
1566 call.abort();
1567 assert!(call.await.unwrap_err().is_cancelled());
1568 assert!(client.pending.lock().is_empty());
1569
1570 client.close().await.unwrap();
1571 handle.join().await.unwrap();
1572 }
1573
1574 #[tokio::test]
1575 async fn test_dropped_deadline_free_raw_call_removes_pending_registration() {
1576 let client = Arc::new(RpcClient::new(LifecycleTestChannel::new(false, false)));
1577 let transport = client.transport();
1578 let handle = client.start();
1579 let call_client = Arc::clone(&client);
1580 let call = tokio::spawn(async move {
1581 call_client
1582 .call_raw_with_options("raw", vec![1, 2, 3], CallOptions::without_timeout())
1583 .await
1584 });
1585 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
1586 .await
1587 .unwrap();
1588 call.abort();
1589 assert!(call.await.unwrap_err().is_cancelled());
1590 assert!(client.pending.lock().is_empty());
1591
1592 client.close().await.unwrap();
1593 handle.join().await.unwrap();
1594 }
1595
1596 #[derive(Debug)]
1597 struct CapturedMessage {
1598 codec_tag: Arc<str>,
1599 method: String,
1600 payload: Vec<u8>,
1601 stream_id: Option<StreamId>,
1602 }
1603
1604 #[derive(Debug)]
1605 struct ConfiguredCodecChannel {
1606 captured: Mutex<Vec<CapturedMessage>>,
1607 codec: StatefulCodec,
1608 replies: Mutex<VecDeque<(MessageId, String)>>,
1609 reply_ready: Notify,
1610 }
1611
1612 impl ConfiguredCodecChannel {
1613 fn new(codec: StatefulCodec) -> Self {
1614 Self {
1615 captured: Mutex::new(Vec::new()),
1616 codec,
1617 replies: Mutex::new(VecDeque::new()),
1618 reply_ready: Notify::new(),
1619 }
1620 }
1621 }
1622
1623 #[async_trait::async_trait]
1624 impl MessageChannel<StatefulCodec> for ConfiguredCodecChannel {
1625 async fn send(&self, message: &Message<StatefulCodec>) -> TransportResult<()> {
1626 self.captured.lock().push(CapturedMessage {
1627 codec_tag: Arc::clone(&message.codec.tag),
1628 method: message.method.clone(),
1629 payload: message.payload.to_vec(),
1630 stream_id: message.metadata.stream_id,
1631 });
1632 if message.metadata.stream_id.is_none()
1633 && matches!(message.method.as_str(), "typed" | "raw")
1634 {
1635 self.replies
1636 .lock()
1637 .push_back((message.id, message.method.clone()));
1638 self.reply_ready.notify_one();
1639 }
1640 Ok(())
1641 }
1642
1643 async fn recv(&self) -> TransportResult<Message<StatefulCodec>> {
1644 loop {
1645 let notified = self.reply_ready.notified();
1646 let reply = self.replies.lock().pop_front();
1647 if let Some((id, method)) = reply {
1648 let payload = match method.as_str() {
1649 "typed" => self
1650 .codec
1651 .encode(&AddResponse { result: 3 })
1652 .map_err(|error| TransportError::Protocol(error.to_string()))?,
1653 "raw" => vec![9, 8, 7],
1654 _ => unreachable!(),
1655 };
1656 return Ok(Message::new_with_codec(
1657 id,
1658 MessageType::Reply,
1659 "",
1660 payload.into(),
1661 Default::default(),
1662 self.codec.clone(),
1663 ));
1664 }
1665 notified.await;
1666 }
1667 }
1668
1669 fn is_connected(&self) -> bool {
1670 true
1671 }
1672
1673 async fn close(&self) -> TransportResult<()> {
1674 Ok(())
1675 }
1676 }
1677
1678 #[tokio::test]
1679 async fn test_client_call_reply() {
1680 let config = ChannelConfig::default();
1681 let (t1, t2) = ChannelFrameTransport::create_pair("test", config).unwrap();
1682
1683 let client_channel = MessageChannelAdapter::new(t1);
1684 let server_channel = MessageChannelAdapter::new(t2);
1685
1686 let client = RpcClient::new(client_channel);
1687 let handle = client.start();
1688
1689 let server_handle = tokio::spawn(async move {
1690 let msg = server_channel.recv().await.unwrap();
1691 assert_eq!(msg.method, "add");
1692
1693 let req: AddRequest = msg.deserialize_payload().unwrap();
1694 let resp = AddResponse {
1695 result: req.a + req.b,
1696 };
1697
1698 let reply: Message = Message::reply(msg.id, resp).unwrap();
1699 server_channel.send(&reply).await.unwrap();
1700 });
1701
1702 let response: AddResponse = client
1703 .call("add", &AddRequest { a: 10, b: 32 })
1704 .await
1705 .unwrap();
1706 assert_eq!(response.result, 42);
1707
1708 server_handle.await.unwrap();
1709 client.close().await.unwrap();
1710 handle.join().await.unwrap();
1711 }
1712
1713 #[tokio::test]
1714 async fn test_configured_codec_is_retained_for_all_outbound_messages() {
1715 let codec = StatefulCodec::configured();
1716 let client = RpcClient::with_codec(ConfiguredCodecChannel::new(codec.clone()), codec);
1717 let transport = client.transport();
1718 let handle = client.start();
1719
1720 let response: AddResponse = client
1721 .call("typed", &AddRequest { a: 1, b: 2 })
1722 .await
1723 .unwrap();
1724 assert_eq!(response.result, 3);
1725 let stream: StreamReceiver<i32, StatefulCodec> =
1726 client.call_server_stream("stream", &17).await.unwrap();
1727 drop(stream);
1728 client.notify("notification", &23).await.unwrap();
1729 assert_eq!(
1730 client.call_raw("raw", vec![1, 2, 3]).await.unwrap(),
1731 vec![9, 8, 7]
1732 );
1733
1734 {
1735 let captured = transport.captured.lock();
1736 assert_eq!(captured.len(), 4);
1737 assert_eq!(
1738 captured
1739 .iter()
1740 .map(|message| message.method.as_str())
1741 .collect::<Vec<_>>(),
1742 vec!["typed", "stream", "notification", "raw"]
1743 );
1744 assert!(
1745 captured
1746 .iter()
1747 .all(|message| message.codec_tag.as_ref() == "configured")
1748 );
1749 assert!(
1750 captured[..3]
1751 .iter()
1752 .all(|message| message.payload.starts_with(b"configured:"))
1753 );
1754 assert!(captured[1].stream_id.is_some());
1755 assert_eq!(captured[3].payload, vec![1, 2, 3]);
1756 }
1757
1758 client.close().await.unwrap();
1759 handle.join().await.unwrap();
1760 }
1761
1762 #[tokio::test]
1763 async fn test_connected_receive_timeout_keeps_client_running() {
1764 let client = RpcClient::new(TimeoutThenReplyChannel::default());
1765 let handle = client.start();
1766
1767 let response: AddResponse = client
1768 .call("add", &AddRequest { a: 10, b: 32 })
1769 .await
1770 .unwrap();
1771
1772 assert_eq!(response.result, 42);
1773 assert!(client.is_running());
1774 client.close().await.unwrap();
1775 handle.join().await.unwrap();
1776 }
1777
1778 #[tokio::test]
1779 async fn test_committed_blocked_send_completes_before_close() {
1780 let client = Arc::new(RpcClient::new(CommitTestChannel::default()));
1781 let transport = client.transport();
1782 let handle = client.start();
1783 let call_client = Arc::clone(&client);
1784 let call = tokio::spawn(async move {
1785 call_client
1786 .call::<_, AddResponse>("add", &AddRequest { a: 1, b: 2 })
1787 .await
1788 });
1789
1790 tokio::time::timeout(Duration::from_secs(1), transport.send_started.notified())
1791 .await
1792 .unwrap();
1793 transport.send_release.notify_one();
1794 tokio::time::timeout(Duration::from_secs(1), transport.committed.notified())
1795 .await
1796 .unwrap();
1797
1798 let close_client = Arc::clone(&client);
1799 let mut close = tokio::spawn(async move { close_client.close().await });
1800 assert!(
1801 tokio::time::timeout(Duration::from_millis(50), &mut close)
1802 .await
1803 .is_err(),
1804 "close completed before the committed call received its reply"
1805 );
1806
1807 transport.reply_release.notify_one();
1808 let response = call.await.unwrap().unwrap();
1809 assert_eq!(response.result, 3);
1810 close.await.unwrap().unwrap();
1811 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
1812 handle.join().await.unwrap();
1813 }
1814
1815 #[tokio::test]
1816 async fn test_client_server_stream() {
1817 let config = ChannelConfig::default();
1818 let (t1, t2) = ChannelFrameTransport::create_pair("test", config).unwrap();
1819
1820 let client_channel = MessageChannelAdapter::new(t1);
1821 let server_channel = Arc::new(MessageChannelAdapter::new(t2));
1822
1823 let client = RpcClient::new(client_channel);
1824 let handle = client.start();
1825
1826 let server_channel_clone = server_channel.clone();
1827 let server_handle = tokio::spawn(async move {
1828 let msg = server_channel_clone.recv().await.unwrap();
1829 let stream_id = msg.metadata.stream_id.unwrap();
1830
1831 for i in 1..=3 {
1832 let chunk: Message = Message::stream_chunk(stream_id, i - 1, i as i32).unwrap();
1833 server_channel_clone.send(&chunk).await.unwrap();
1834 }
1835
1836 let end: Message = Message::stream_end(stream_id);
1837 server_channel_clone.send(&end).await.unwrap();
1838 });
1839
1840 let mut stream: StreamReceiver<i32> =
1841 client.call_server_stream("get_numbers", &()).await.unwrap();
1842
1843 let mut items = Vec::new();
1844 while let Some(result) = stream.recv().await {
1845 items.push(result.unwrap());
1846 }
1847
1848 assert_eq!(items, vec![1, 2, 3]);
1849 server_handle.await.unwrap();
1850 client.close().await.unwrap();
1851 handle.join().await.unwrap();
1852 }
1853
1854 #[tokio::test]
1855 async fn test_server_stream_uses_caller_provided_id() {
1856 let config = ChannelConfig::default();
1857 let (t1, t2) = ChannelFrameTransport::create_pair("test", config).unwrap();
1858
1859 let client = RpcClient::new(MessageChannelAdapter::new(t1));
1860 let server_channel = MessageChannelAdapter::new(t2);
1861 let handle = client.start();
1862 let stream_id = 42;
1863
1864 let server_handle = tokio::spawn(async move {
1865 server_channel
1866 .recv()
1867 .await
1868 .unwrap()
1869 .metadata
1870 .stream_id
1871 .unwrap()
1872 });
1873 let stream: StreamReceiver<i32> = client
1874 .call_server_stream_with_id("numbers", &(), stream_id)
1875 .await
1876 .unwrap();
1877
1878 assert_eq!(stream.stream_id(), stream_id);
1879 assert_eq!(server_handle.await.unwrap(), stream_id);
1880 client.close().await.unwrap();
1881 handle.join().await.unwrap();
1882 }
1883
1884 #[tokio::test]
1885 async fn test_client_stream_error_uses_configured_codec() {
1886 let config = ChannelConfig::default();
1887 let (t1, t2) = ChannelFrameTransport::create_pair("test", config).unwrap();
1888
1889 let client_channel = MessageChannelAdapter::<_, JsonCodec>::with_codec(t1);
1890 let server_channel = MessageChannelAdapter::<_, JsonCodec>::with_codec(t2);
1891
1892 let client = RpcClient::with_codec(client_channel, JsonCodec);
1893 let handle = client.start();
1894
1895 let server_handle = tokio::spawn(async move {
1896 let msg = server_channel.recv().await.unwrap();
1897 let stream_id = msg.metadata.stream_id.unwrap();
1898
1899 let error: Message<JsonCodec> =
1900 Message::stream_error(msg.id, stream_id, "method not found");
1901 server_channel.send(&error).await.unwrap();
1902 });
1903
1904 let mut stream: StreamReceiver<i32, JsonCodec> = client
1905 .call_server_stream("unknown_method", &())
1906 .await
1907 .unwrap();
1908
1909 assert!(matches!(
1910 stream.recv().await,
1911 Some(Err(RpcError::ServerError(error))) if error == "method not found"
1912 ));
1913
1914 server_handle.await.unwrap();
1915 client.close().await.unwrap();
1916 handle.join().await.unwrap();
1917 }
1918
1919 #[tokio::test]
1920 async fn test_call_before_start_fails_promptly() {
1921 let client = RpcClient::new(LifecycleTestChannel::new(false, false));
1922
1923 let result: Result<AddResponse> = client.call("add", &AddRequest { a: 1, b: 2 }).await;
1924
1925 assert!(matches!(result, Err(RpcError::ClientError(_))));
1926 assert!(client.pending.lock().is_empty());
1927 assert!(!client.is_started());
1928 }
1929
1930 #[tokio::test]
1931 async fn test_duplicate_and_post_close_start_fail() {
1932 let client = RpcClient::new(LifecycleTestChannel::new(false, false));
1933 let handle = client.try_start().unwrap();
1934
1935 assert!(client.is_started());
1936 assert!(client.is_running());
1937 assert!(matches!(client.try_start(), Err(RpcError::ClientError(_))));
1938
1939 client.close().await.unwrap();
1940
1941 assert!(client.is_closed());
1942 assert!(!client.is_running());
1943 assert!(matches!(
1944 client.try_start(),
1945 Err(RpcError::ConnectionClosed)
1946 ));
1947 handle.join().await.unwrap();
1948 }
1949
1950 #[tokio::test]
1951 async fn test_close_waits_for_committed_call_timeout() {
1952 let client = Arc::new(RpcClient::new(LifecycleTestChannel::new(false, false)));
1953 let transport = client.transport();
1954 let handle = client.start();
1955 let call_client = client.clone();
1956 let call_handle = tokio::spawn(async move {
1957 call_client
1958 .call_with_timeout::<_, AddResponse>(
1959 "add",
1960 &AddRequest { a: 1, b: 2 },
1961 Duration::from_millis(50),
1962 )
1963 .await
1964 });
1965
1966 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
1967 .await
1968 .unwrap();
1969 client.close().await.unwrap();
1970
1971 let result = call_handle.await.unwrap();
1972 assert!(matches!(result, Err(RpcError::Timeout(_))));
1973 assert!(client.pending.lock().is_empty());
1974 handle.join().await.unwrap();
1975 }
1976
1977 #[tokio::test]
1978 async fn test_terminal_receive_error_is_preserved_for_pending_work() {
1979 let client = Arc::new(RpcClient::new(LifecycleTestChannel::with_receive_error(
1980 TransportError::Protocol("invalid frame".to_string()),
1981 )));
1982 let transport = client.transport();
1983 let handle = client.start();
1984 let call_client = Arc::clone(&client);
1985 let call = tokio::spawn(async move {
1986 call_client
1987 .call::<_, AddResponse>("add", &AddRequest { a: 1, b: 2 })
1988 .await
1989 });
1990
1991 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
1992 .await
1993 .unwrap();
1994 let mut stream: StreamReceiver<i32> =
1995 client.call_server_stream("numbers", &()).await.unwrap();
1996 transport.receive_failed.notify_one();
1997
1998 assert!(matches!(
1999 call.await.unwrap(),
2000 Err(RpcError::Transport(TransportError::Protocol(error)))
2001 if error == "invalid frame"
2002 ));
2003 assert!(matches!(
2004 stream.recv().await,
2005 Some(Err(RpcError::Transport(TransportError::Protocol(error))))
2006 if error == "invalid frame"
2007 ));
2008 assert!(client.is_closed());
2009 handle.join().await.unwrap();
2010 client.close().await.unwrap();
2011 }
2012
2013 #[tokio::test]
2014 async fn test_active_stream_fails_when_client_closes() {
2015 let client = RpcClient::new(LifecycleTestChannel::new(false, false));
2016 let handle = client.start();
2017 let mut stream: StreamReceiver<i32> =
2018 client.call_server_stream("numbers", &()).await.unwrap();
2019
2020 assert_eq!(client.active_streams(), 1);
2021 client.close().await.unwrap();
2022
2023 assert!(matches!(
2024 stream.recv().await,
2025 Some(Err(RpcError::ConnectionClosed))
2026 ));
2027 assert_eq!(client.active_streams(), 0);
2028 handle.join().await.unwrap();
2029 }
2030
2031 #[tokio::test]
2032 async fn test_terminal_outbound_send_drains_work_and_closes_once() {
2033 let client = Arc::new(RpcClient::new(TerminalSendChannel::default()));
2034 let transport = client.transport();
2035 let handle = client.start();
2036 let call_client = Arc::clone(&client);
2037 let call = tokio::spawn(async move {
2038 call_client
2039 .call::<_, AddResponse>("pending", &AddRequest { a: 1, b: 2 })
2040 .await
2041 });
2042
2043 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
2044 .await
2045 .unwrap();
2046 let mut stream: StreamReceiver<i32> =
2047 client.call_server_stream("stream", &()).await.unwrap();
2048 transport.connected.store(false, Ordering::SeqCst);
2049 transport.fail_send.store(true, Ordering::SeqCst);
2050
2051 assert!(matches!(
2052 client.notify("fail", &()).await,
2053 Err(RpcError::Transport(TransportError::NotConnected))
2054 ));
2055 assert!(matches!(
2056 call.await.unwrap(),
2057 Err(RpcError::Transport(TransportError::NotConnected))
2058 ));
2059 assert!(matches!(
2060 stream.recv().await,
2061 Some(Err(RpcError::Transport(TransportError::NotConnected)))
2062 ));
2063 tokio::time::timeout(Duration::from_secs(1), transport.close_started.notified())
2064 .await
2065 .unwrap();
2066 tokio::time::timeout(Duration::from_secs(1), handle.join())
2067 .await
2068 .unwrap()
2069 .unwrap();
2070
2071 assert!(client.is_closed());
2072 assert!(!client.is_running());
2073 client.close().await.unwrap();
2074 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2075 }
2076
2077 #[tokio::test]
2078 async fn test_connected_outbound_timeout_is_not_terminal() {
2079 let client = RpcClient::new(ConnectedTimeoutChannel::default());
2080 let transport = client.transport();
2081 let handle = client.start();
2082
2083 assert!(matches!(
2084 client.notify("first", &()).await,
2085 Err(RpcError::Transport(TransportError::Timeout { .. }))
2086 ));
2087 assert!(client.is_running());
2088 assert!(!client.is_closed());
2089 assert_eq!(transport.close_count.load(Ordering::SeqCst), 0);
2090 client.notify("second", &()).await.unwrap();
2091
2092 client.close().await.unwrap();
2093 handle.join().await.unwrap();
2094 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2095 }
2096
2097 #[tokio::test]
2098 async fn test_stream_registration_rolls_back_when_send_fails() {
2099 let client = RpcClient::new(LifecycleTestChannel::new(true, false));
2100 let handle = client.start();
2101
2102 let result: Result<StreamReceiver<i32>> = client.call_server_stream("numbers", &()).await;
2103
2104 assert!(matches!(result, Err(RpcError::ConnectionClosed)));
2105 assert_eq!(client.active_streams(), 0);
2106 client.close().await.unwrap();
2107 handle.join().await.unwrap();
2108 }
2109
2110 #[tokio::test]
2111 async fn test_dropped_typed_call_cleans_pending_registration_during_send() {
2112 let client = Arc::new(RpcClient::new(LifecycleTestChannel::with_blocked_send()));
2113 let transport = client.transport();
2114 let handle = client.start();
2115 let call_client = Arc::clone(&client);
2116 let call = tokio::spawn(async move {
2117 call_client
2118 .call::<_, AddResponse>("add", &AddRequest { a: 1, b: 2 })
2119 .await
2120 });
2121
2122 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
2123 .await
2124 .unwrap();
2125 call.abort();
2126 assert!(call.await.unwrap_err().is_cancelled());
2127 assert!(client.pending.lock().is_empty());
2128
2129 client.close().await.unwrap();
2130 handle.join().await.unwrap();
2131 }
2132
2133 #[tokio::test]
2134 async fn test_dropped_raw_call_cleans_pending_registration_while_waiting() {
2135 let client = Arc::new(RpcClient::new(LifecycleTestChannel::new(false, false)));
2136 let transport = client.transport();
2137 let handle = client.start();
2138 let call_client = Arc::clone(&client);
2139 let call = tokio::spawn(async move { call_client.call_raw("raw", vec![1, 2, 3]).await });
2140
2141 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
2142 .await
2143 .unwrap();
2144 call.abort();
2145 assert!(call.await.unwrap_err().is_cancelled());
2146 assert!(client.pending.lock().is_empty());
2147
2148 client.close().await.unwrap();
2149 handle.join().await.unwrap();
2150 }
2151
2152 #[tokio::test]
2153 async fn test_dropped_stream_setup_cleans_registration_during_send() {
2154 let client = Arc::new(RpcClient::new(LifecycleTestChannel::with_blocked_send()));
2155 let transport = client.transport();
2156 let handle = client.start();
2157 let stream_client = Arc::clone(&client);
2158 let stream = tokio::spawn(async move {
2159 stream_client
2160 .call_server_stream::<_, i32>("numbers", &())
2161 .await
2162 });
2163
2164 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
2165 .await
2166 .unwrap();
2167 stream.abort();
2168 assert!(matches!(stream.await, Err(error) if error.is_cancelled()));
2169 assert_eq!(client.active_streams(), 0);
2170
2171 client.close().await.unwrap();
2172 handle.join().await.unwrap();
2173 }
2174
2175 #[tokio::test]
2176 async fn test_close_is_idempotent_after_transport_error() {
2177 let client = RpcClient::new(LifecycleTestChannel::new(false, true));
2178 let transport = client.transport();
2179 let handle = client.start();
2180
2181 assert!(matches!(
2182 client.close().await,
2183 Err(RpcError::ConnectionClosed)
2184 ));
2185 assert!(matches!(
2186 client.close().await,
2187 Err(RpcError::ConnectionClosed)
2188 ));
2189 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2190 assert!(matches!(
2191 client.notify("event", &()).await,
2192 Err(RpcError::ConnectionClosed)
2193 ));
2194 let stream_result: Result<StreamReceiver<i32>> =
2195 client.call_server_stream("numbers", &()).await;
2196 assert!(matches!(stream_result, Err(RpcError::ConnectionClosed)));
2197 handle.join().await.unwrap();
2198 }
2199
2200 #[tokio::test]
2201 async fn test_close_proceeds_while_outbound_send_is_blocked() {
2202 let client = Arc::new(RpcClient::new(LifecycleTestChannel::with_blocked_send()));
2203 let transport = client.transport();
2204 let handle = client.start();
2205 let call_client = client.clone();
2206 let call_handle = tokio::spawn(async move {
2207 call_client
2208 .call::<_, AddResponse>("add", &AddRequest { a: 1, b: 2 })
2209 .await
2210 });
2211
2212 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
2213 .await
2214 .unwrap();
2215 tokio::time::timeout(Duration::from_secs(1), client.close())
2216 .await
2217 .unwrap()
2218 .unwrap();
2219
2220 assert!(matches!(
2221 call_handle.await.unwrap(),
2222 Err(RpcError::ConnectionClosed)
2223 ));
2224 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2225 handle.join().await.unwrap();
2226 }
2227
2228 #[tokio::test]
2229 async fn test_receive_failure_claims_close_before_transport_await() {
2230 let client = RpcClient::new(LifecycleTestChannel::with_blocked_close());
2231 let transport = client.transport();
2232 let handle = client.start();
2233
2234 transport.receive_failed.notify_one();
2235 tokio::time::timeout(Duration::from_secs(1), transport.close_started.notified())
2236 .await
2237 .unwrap();
2238
2239 assert!(client.is_closed());
2240 assert!(!client.is_running());
2241 let result: Result<AddResponse> = client.call("add", &AddRequest { a: 1, b: 2 }).await;
2242 assert!(matches!(result, Err(RpcError::ConnectionClosed)));
2243 let close = client.close();
2244 tokio::pin!(close);
2245 assert!(
2246 tokio::time::timeout(Duration::from_millis(50), &mut close)
2247 .await
2248 .is_err(),
2249 "concurrent close returned before the in-flight transport close completed"
2250 );
2251 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2252
2253 transport.close_release.notify_one();
2254 close.await.unwrap();
2255 client.close().await.unwrap();
2256 tokio::time::timeout(Duration::from_secs(1), handle.join())
2257 .await
2258 .unwrap()
2259 .unwrap();
2260
2261 assert!(client.receive_abort.lock().is_none());
2262 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2263 }
2264
2265 #[tokio::test]
2266 async fn test_concurrent_close_waiters_share_transport_error() {
2267 let client = Arc::new(RpcClient::new(
2268 LifecycleTestChannel::with_blocked_failing_close(),
2269 ));
2270 let transport = client.transport();
2271 let handle = client.start();
2272
2273 let first_client = Arc::clone(&client);
2274 let first = tokio::spawn(async move { first_client.close().await });
2275 tokio::time::timeout(Duration::from_secs(1), transport.close_started.notified())
2276 .await
2277 .unwrap();
2278 let second_client = Arc::clone(&client);
2279 let second = tokio::spawn(async move { second_client.close().await });
2280 tokio::task::yield_now().await;
2281 assert!(!first.is_finished());
2282 assert!(!second.is_finished());
2283
2284 transport.close_release.notify_one();
2285 assert!(matches!(
2286 first.await.unwrap(),
2287 Err(RpcError::ConnectionClosed)
2288 ));
2289 assert!(matches!(
2290 second.await.unwrap(),
2291 Err(RpcError::ConnectionClosed)
2292 ));
2293 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2294 handle.join().await.unwrap();
2295 }
2296
2297 #[tokio::test]
2298 async fn test_cancelled_close_owner_does_not_strand_waiters() {
2299 let client = Arc::new(RpcClient::new(LifecycleTestChannel::with_blocked_close()));
2300 let transport = client.transport();
2301 let handle = client.start();
2302
2303 let first_client = Arc::clone(&client);
2304 let first = tokio::spawn(async move { first_client.close().await });
2305 tokio::time::timeout(Duration::from_secs(1), transport.close_started.notified())
2306 .await
2307 .unwrap();
2308 first.abort();
2309 assert!(first.await.unwrap_err().is_cancelled());
2310
2311 let second_client = Arc::clone(&client);
2312 let second = tokio::spawn(async move { second_client.close().await });
2313 tokio::task::yield_now().await;
2314 assert!(!second.is_finished());
2315
2316 transport.close_release.notify_one();
2317 second.await.unwrap().unwrap();
2318 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2319 handle.join().await.unwrap();
2320 }
2321
2322 #[tokio::test]
2323 async fn test_cancelled_close_before_admission_continues_in_background() {
2324 let client = Arc::new(RpcClient::new(CommitTestChannel::default()));
2325 let transport = client.transport();
2326 let handle = client.start();
2327 let call_client = Arc::clone(&client);
2328 let call = tokio::spawn(async move {
2329 call_client
2330 .call::<_, AddResponse>("add", &AddRequest { a: 1, b: 2 })
2331 .await
2332 });
2333
2334 tokio::time::timeout(Duration::from_secs(1), transport.send_started.notified())
2335 .await
2336 .unwrap();
2337 transport.send_release.notify_one();
2338 tokio::time::timeout(Duration::from_secs(1), transport.committed.notified())
2339 .await
2340 .unwrap();
2341
2342 let close_client = Arc::clone(&client);
2343 let first_close = tokio::spawn(async move { close_client.close().await });
2344 tokio::time::timeout(Duration::from_secs(1), async {
2345 while !matches!(
2346 &*client.client_close_state.lock(),
2347 TransportCloseState::Closing
2348 ) {
2349 tokio::task::yield_now().await;
2350 }
2351 })
2352 .await
2353 .unwrap();
2354 assert_eq!(transport.close_count.load(Ordering::SeqCst), 0);
2355
2356 first_close.abort();
2357 assert!(first_close.await.unwrap_err().is_cancelled());
2358 transport.reply_release.notify_one();
2359 assert_eq!(call.await.unwrap().unwrap().result, 3);
2360
2361 tokio::time::timeout(Duration::from_secs(1), async {
2362 while transport.close_count.load(Ordering::SeqCst) == 0 {
2363 tokio::task::yield_now().await;
2364 }
2365 })
2366 .await
2367 .unwrap();
2368 client.close().await.unwrap();
2369 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2370 handle.join().await.unwrap();
2371 }
2372
2373 #[tokio::test]
2374 async fn test_panicking_transport_close_releases_all_waiters() {
2375 let client = RpcClient::new(LifecycleTestChannel::with_panicking_close());
2376 let transport = client.transport();
2377 let handle = client.start();
2378
2379 let first = tokio::time::timeout(Duration::from_secs(1), client.close())
2380 .await
2381 .unwrap();
2382 let second = tokio::time::timeout(Duration::from_secs(1), client.close())
2383 .await
2384 .unwrap();
2385
2386 assert!(matches!(first, Err(RpcError::ClientError(error)) if error.contains("panicked")));
2387 assert!(matches!(second, Err(RpcError::ClientError(error)) if error.contains("panicked")));
2388 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2389 handle.join().await.unwrap();
2390 }
2391
2392 #[test]
2393 fn test_transport_error_normalization() {
2394 assert!(matches!(
2395 normalize_transport_error(TransportError::ConnectionClosed),
2396 RpcError::ConnectionClosed
2397 ));
2398 assert!(matches!(
2399 normalize_transport_error(TransportError::NotConnected),
2400 RpcError::Transport(TransportError::NotConnected)
2401 ));
2402 }
2403
2404 #[tokio::test]
2405 async fn test_drop_aborts_detached_task_and_drains_work() {
2406 let client = RpcClient::new(LifecycleTestChannel::new(false, false));
2407 let state = client.state.clone();
2408 let receive_abort = client.receive_abort.clone();
2409 let stream_manager = client.stream_manager();
2410 let (pending_tx, pending_rx) = oneshot::channel::<Result<Message>>();
2411 client.pending.lock().insert(MessageId::new(), pending_tx);
2412 let mut stream: StreamReceiver<i32> = stream_manager.create_receiver(next_stream_id());
2413 let handle = client.start();
2414
2415 assert!(receive_abort.lock().is_some());
2416 drop(handle);
2417 drop(client);
2418
2419 assert!(state.lock().closed);
2420 assert!(!state.lock().running);
2421 assert!(receive_abort.lock().is_none());
2422 assert!(matches!(
2423 pending_rx.await.unwrap(),
2424 Err(RpcError::ConnectionClosed)
2425 ));
2426 assert!(matches!(
2427 stream.recv().await,
2428 Some(Err(RpcError::ConnectionClosed))
2429 ));
2430 assert_eq!(stream_manager.active_stream_count(), 0);
2431 }
2432
2433 #[tokio::test]
2434 async fn test_handle_join_after_graceful_close() {
2435 let client = RpcClient::new(LifecycleTestChannel::new(false, false));
2436 let handle = client.start();
2437
2438 client.close().await.unwrap();
2439
2440 tokio::time::timeout(Duration::from_secs(1), handle.join())
2441 .await
2442 .unwrap()
2443 .unwrap();
2444 }
2445
2446 #[tokio::test]
2447 async fn test_forced_shutdown_cancels_blocked_send() {
2448 let client = Arc::new(RpcClient::new(LifecycleTestChannel::with_blocked_send()));
2449 let transport = client.transport();
2450 let handle = client.start();
2451 let call_client = Arc::clone(&client);
2452 let call = tokio::spawn(async move {
2453 call_client
2454 .call::<_, AddResponse>("add", &AddRequest { a: 1, b: 2 })
2455 .await
2456 });
2457
2458 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
2459 .await
2460 .unwrap();
2461 handle.shutdown().await;
2462
2463 assert!(matches!(
2464 tokio::time::timeout(Duration::from_secs(1), call)
2465 .await
2466 .unwrap()
2467 .unwrap(),
2468 Err(RpcError::ConnectionClosed)
2469 ));
2470 tokio::time::timeout(Duration::from_secs(1), client.close())
2471 .await
2472 .unwrap()
2473 .unwrap();
2474 assert_eq!(transport.close_count.load(Ordering::SeqCst), 1);
2475 }
2476
2477 #[tokio::test]
2478 async fn test_forced_shutdown_drains_pending_call() {
2479 let client = Arc::new(RpcClient::new(LifecycleTestChannel::new(false, false)));
2480 let transport = client.transport();
2481 let handle = client.start();
2482 let call_client = client.clone();
2483 let call_handle = tokio::spawn(async move {
2484 call_client
2485 .call::<_, AddResponse>("add", &AddRequest { a: 1, b: 2 })
2486 .await
2487 });
2488
2489 tokio::time::timeout(Duration::from_secs(1), transport.sent.notified())
2490 .await
2491 .unwrap();
2492 handle.shutdown().await;
2493
2494 assert!(matches!(
2495 call_handle.await.unwrap(),
2496 Err(RpcError::ConnectionClosed)
2497 ));
2498 assert!(client.is_closed());
2499 assert!(!client.is_running());
2500 assert!(client.receive_abort.lock().is_none());
2501 client.close().await.unwrap();
2502 }
2503}