1use super::envelope::{Header, IncomingEnvelope, MessageKind, Parity, Side};
10use super::operation::{
11 OperationHandle, OperationKey, OutgoingBody, OutgoingMessage, PendingOperation,
12};
13use super::promise::PromiseResult;
14use super::worker;
15use super::{
16 Closer, DEFAULT_AUTOREPLY_TIMEOUT, DEFAULT_MAX_INBOUND_BYTES, DEFAULT_MAX_INBOUND_REQUESTS,
17 Error, Message, Promise, Requester, Responder, schema,
18};
19use crate::LogId;
20use crate::transport::{self, Read, Stream, Verifier, Write};
21use prost::bytes::Bytes;
22use std::collections::{HashMap, HashSet, VecDeque};
23use std::fmt;
24use std::sync::atomic::{AtomicUsize, Ordering};
25use std::sync::{Arc, Condvar, Mutex};
26use std::time::{Duration, Instant};
27
28pub fn connect<R, W, V>(stream: Stream<R, W>, verifier: &V) -> Result<(Session, V::Info), Error>
34where
35 R: Read + Send + 'static,
36 W: Write + Send + 'static,
37 V: Verifier,
38{
39 let mut client = transport::Client::new(stream);
40
41 let (sender, info) = client.connect(verifier)?;
42 #[cfg(any(test, feature = "fuzz"))]
43 let workers = Arc::new(worker::Tracker::default());
44 let session = Session::start(
45 Side::Client,
46 sender,
47 Some(client.closer()),
48 #[cfg(any(test, feature = "fuzz"))]
49 workers.clone(),
50 );
51 let inner = session.inner.clone();
52
53 worker::spawn(
54 "wire-client-reader",
55 #[cfg(any(test, feature = "fuzz"))]
56 &workers,
57 move || run_reader(client, inner),
58 );
59 Ok((session, info))
60}
61
62fn run_reader<R: Read, W: Write>(mut client: transport::Client<R, W>, session: Arc<SessionInner>) {
66 loop {
67 let result = client
68 .recv()
69 .map_err(Error::from)
70 .and_then(|bytes| session.handle_message(bytes));
71 if let Err(error) = result {
72 session.close(error);
75 break;
76 }
77 }
78}
79
80pub struct Session {
100 pub(super) inner: Arc<SessionInner>,
102}
103
104impl Session {
105 pub fn set_autoreply_timeout(self, timeout: Duration) -> Self {
117 self.inner.set_autoreply_timeout(timeout);
118 self
119 }
120
121 pub fn set_inbound_limits(self, requests: usize, bytes: usize) -> Self {
140 self.inner.set_inbound_limits(requests, bytes);
141 self
142 }
143
144 pub fn requester(&self) -> Requester {
146 Requester::new(Arc::downgrade(&self.inner))
147 }
148
149 pub fn recv(&mut self) -> Result<(Message, Responder), Error> {
161 self.inner.recv()
162 }
163
164 pub fn closer(&self) -> Closer {
167 Closer::session(Arc::downgrade(&self.inner))
168 }
169
170 pub fn close(&self) {
176 self.inner.close(Error::Closed);
177 }
178}
179
180impl Drop for Session {
181 fn drop(&mut self) {
183 self.close();
184 }
185}
186
187impl fmt::Debug for Session {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191 let mut session = f.debug_struct("Session");
192 session.field("id", &self.inner.log_id);
193 if let Ok(state) = self.inner.state.try_lock() {
194 session.field("open", &matches!(*state, State::Open { .. }));
195 }
196 session.finish_non_exhaustive()
197 }
198}
199
200pub(super) struct SessionInner {
203 state: Mutex<State>,
205 changed: Condvar,
207 retained_bytes: Arc<AtomicUsize>,
211 side: Side,
213 pub(super) log_id: LogId,
215 stream_closer: Option<transport::Closer>,
218 #[cfg(any(test, feature = "fuzz"))]
220 pub(super) workers: Arc<worker::Tracker>,
221 #[cfg(any(test, feature = "fuzz"))]
223 time: Mutex<Option<Instant>>,
224 #[cfg(any(test, feature = "fuzz"))]
226 drop_hook: Mutex<Option<std::sync::mpsc::Sender<()>>>,
227 #[cfg(any(test, feature = "fuzz"))]
229 disconnect_hook: Mutex<Option<(std::sync::mpsc::Sender<()>, std::sync::mpsc::Receiver<()>)>>,
230}
231
232#[cfg_attr(any(test, feature = "fuzz"), allow(clippy::large_enum_variant))]
236enum State {
237 Open {
240 max_inbound_requests: usize,
242 max_inbound_bytes: usize,
244 autoreply_timeout: Duration,
246
247 incoming: VecDeque<(u64, IncomingEnvelope)>,
249 reserved_ids: HashSet<u64>,
255
256 outgoing: VecDeque<OutgoingMessage>,
258 next_id: Option<u64>,
260 outstanding: HashMap<u64, OperationKey>,
263 operations: HashMap<OperationKey, PendingOperation>,
266
267 #[cfg(any(test, feature = "fuzz"))]
269 wait_hook: Option<std::sync::mpsc::Sender<()>>,
270 },
271 Closed(Error),
273}
274
275impl SessionInner {
276 fn new(
278 side: Side,
279 log_id: LogId,
280 stream_closer: Option<transport::Closer>,
281 #[cfg(any(test, feature = "fuzz"))] workers: Arc<worker::Tracker>,
282 ) -> Self {
283 Self {
284 state: Mutex::new(State::Open {
285 max_inbound_requests: DEFAULT_MAX_INBOUND_REQUESTS,
286 max_inbound_bytes: DEFAULT_MAX_INBOUND_BYTES,
287 autoreply_timeout: DEFAULT_AUTOREPLY_TIMEOUT,
288
289 incoming: VecDeque::new(),
290 reserved_ids: HashSet::new(),
291
292 outgoing: VecDeque::new(),
293 next_id: Some(Parity::from(side).first()),
294 outstanding: HashMap::new(),
295 operations: HashMap::new(),
296
297 #[cfg(any(test, feature = "fuzz"))]
298 wait_hook: None,
299 }),
300 changed: Condvar::new(),
301 retained_bytes: Arc::new(AtomicUsize::new(0)),
302 side,
303 log_id,
304 stream_closer,
305 #[cfg(any(test, feature = "fuzz"))]
306 workers,
307 #[cfg(any(test, feature = "fuzz"))]
308 time: Mutex::new(None),
309 #[cfg(any(test, feature = "fuzz"))]
310 drop_hook: Mutex::new(None),
311 #[cfg(any(test, feature = "fuzz"))]
312 disconnect_hook: Mutex::new(None),
313 }
314 }
315
316 pub(super) fn set_autoreply_timeout(&self, timeout: Duration) {
319 let mut state = self.state.lock().expect("session state not poisoned");
320 if let State::Open {
321 autoreply_timeout, ..
322 } = &mut *state
323 {
324 *autoreply_timeout = timeout;
325 }
326 }
327
328 pub(super) fn set_inbound_limits(&self, requests: usize, bytes: usize) {
331 let removed = {
332 let mut state = self.state.lock().expect("session state not poisoned");
333 let State::Open {
334 max_inbound_requests,
335 max_inbound_bytes,
336 reserved_ids,
337 ..
338 } = &mut *state
339 else {
340 return;
341 };
342 *max_inbound_requests = requests;
343 *max_inbound_bytes = bytes;
344 let error = if reserved_ids.len() > *max_inbound_requests {
345 Some(Error::InboundRequestLimitExceeded(*max_inbound_requests))
346 } else if self.retained_bytes.load(Ordering::Relaxed) > *max_inbound_bytes {
347 Some(Error::InboundByteLimitExceeded(*max_inbound_bytes))
348 } else {
349 None
350 };
351 error.and_then(|error| {
352 let (pending, queued) = state.workload();
353 let removed = state.close(error.clone(), self.now());
354 if removed.is_some() {
355 self.log_closed(&error, pending, queued);
356 }
357 removed
358 })
359 };
360 if removed.is_some() {
361 self.finish_close(removed);
362 }
363 }
364
365 fn retain_incoming(
368 self: &Arc<Self>,
369 bytes: Bytes,
370 header: Header,
371 limit: usize,
372 ) -> Result<IncomingEnvelope, Error> {
373 IncomingEnvelope::new(
374 bytes,
375 header,
376 &self.retained_bytes,
377 limit,
378 self.side,
379 Arc::downgrade(self),
380 )
381 }
382
383 fn recv(self: &Arc<Self>) -> Result<(Message, Responder), Error> {
388 let mut state = self.state.lock().expect("session state not poisoned");
389 let (message, responder) = loop {
390 match &mut *state {
391 State::Closed(error) => return Err(error.clone()),
392 State::Open {
393 incoming,
394 #[cfg(any(test, feature = "fuzz"))]
395 wait_hook,
396 ..
397 } => {
398 if let Some((id, message)) = incoming.pop_front() {
399 break (message, Responder::new(Arc::downgrade(self), id));
400 }
401 #[cfg(any(test, feature = "fuzz"))]
402 if let Some(wait_hook) = wait_hook.take() {
403 let _ = wait_hook.send(());
404 }
405 state = self
406 .changed
407 .wait(state)
408 .expect("session state not poisoned");
409 }
410 }
411 };
412 drop(state);
413 Ok((message.decode()?, responder))
414 }
415
416 pub(super) fn close(&self, error: Error) {
420 let (removed, pending, queued) = {
421 let mut state = self.state.lock().expect("session state not poisoned");
422 let (pending, queued) = state.workload();
423 (state.close(error.clone(), self.now()), pending, queued)
424 };
425 if removed.is_some() {
426 self.log_closed(&error, pending, queued);
427 }
428 self.finish_close(removed);
429 }
430
431 fn log_closed(&self, error: &Error, pending: usize, queued: usize) {
434 match error {
435 Error::Closed => tracing::info!(
436 "wire session {} closed locally (pending {}, queued {})",
437 self.log_id,
438 pending,
439 queued
440 ),
441 _ if error.orderly() => tracing::info!(
442 "wire session {} closed: {} (pending {}, queued {})",
443 self.log_id,
444 error.reason(),
445 pending,
446 queued
447 ),
448 _ => tracing::warn!(
449 "wire session {} failed: {} (pending {}, queued {})",
450 self.log_id,
451 error.reason(),
452 pending,
453 queued
454 ),
455 }
456 }
457
458 fn finish_close(&self, removed: Option<State>) {
460 self.changed.notify_all();
463 drop(removed);
464 if let Some(stream_closer) = &self.stream_closer {
465 stream_closer.close();
466 }
467 }
468
469 pub(super) fn reply_unanswered(self: &Arc<Self>, id: u64) {
472 self.autoreply(
473 id,
474 "unanswered",
475 schema::Error::reserved(
476 schema::ReservedErrors::Unanswered,
477 "request left unanswered",
478 ),
479 );
480 }
481
482 fn reply_unknown(self: &Arc<Self>, id: u64) {
485 self.autoreply(
486 id,
487 "unknown",
488 schema::Error::reserved(schema::ReservedErrors::Unknown, "request not known"),
489 );
490 }
491
492 fn autoreply(self: &Arc<Self>, id: u64, reason: &str, error: schema::Error) {
497 let now = self.now();
498 let timeout = {
499 let state = self.state.lock().expect("session state not poisoned");
500 match &*state {
501 State::Open {
502 autoreply_timeout, ..
503 } => *autoreply_timeout,
504 State::Closed(_) => return,
505 }
506 };
507 tracing::debug!("answering request {} as {}", id, reason);
508
509 let deadline = now.checked_add(timeout).unwrap_or(now);
512 let _ = self.reply(id, Err(error), deadline);
513 }
514
515 pub(super) fn request(
518 self: &Arc<Self>,
519 request: Message,
520 deadline: Instant,
521 ) -> Result<Promise<Message>, Error> {
522 let (sender, promise) = Promise::pair(Arc::downgrade(self), deadline, true);
523 self.enqueue(
524 OutgoingBody::Request(request),
525 PendingOperation {
526 deadline,
527 sender,
528 log_id: None,
529 },
530 )?;
531 Ok(promise)
532 }
533
534 pub(super) fn reply(
537 self: &Arc<Self>,
538 id: u64,
539 result: Result<Message, schema::Error>,
540 deadline: Instant,
541 ) -> Result<Promise<()>, Error> {
542 let (sender, promise) = Promise::pair(Arc::downgrade(self), deadline, false);
543 self.enqueue(
544 OutgoingBody::Reply { id, result },
545 PendingOperation {
546 deadline,
547 sender,
548 log_id: Some(LogId::from(id)),
549 },
550 )?;
551 Ok(promise)
552 }
553
554 fn enqueue(
558 self: &Arc<Self>,
559 body: OutgoingBody,
560 operation: PendingOperation,
561 ) -> Result<(), Error> {
562 {
563 let mut state = self.state.lock().expect("session state not poisoned");
564 let (operations, outgoing, reserved_ids) = match &mut *state {
565 State::Open {
566 operations,
567 outgoing,
568 reserved_ids,
569 ..
570 } => (operations, outgoing, reserved_ids),
571 State::Closed(error) => return Err(error.clone()),
572 };
573 let now = self.now();
574 if now >= operation.deadline {
575 if let OutgoingBody::Reply { id, .. } = body {
576 reserved_ids.remove(&id);
577 }
578 operation.fail(Error::Timeout, now);
579 return Ok(());
580 }
581 let key = OperationKey::new();
582 outgoing.push_back(OutgoingMessage {
583 body,
584 operation: OperationHandle {
585 session: Arc::downgrade(self),
586 key: key.clone(),
587 },
588 #[cfg(any(test, feature = "fuzz"))]
589 deadline: operation.deadline,
590 });
591 operations.insert(key, operation);
592 }
593 self.changed.notify_all();
594 Ok(())
595 }
596
597 pub(super) fn expire(&self) {
601 let mut state = self.state.lock().expect("session state not poisoned");
602 state.expire(self.now());
603 }
604
605 #[cfg(any(test, feature = "fuzz"))]
607 pub(super) fn next_deadline(&self) -> Option<Instant> {
608 let state = self.state.lock().expect("session state not poisoned");
609 match &*state {
610 State::Open { operations, .. } => operations
611 .values()
612 .map(|operation| operation.deadline)
613 .min(),
614 State::Closed(_) => None,
615 }
616 }
617
618 #[cfg(any(test, feature = "fuzz"))]
620 pub(super) fn take_outgoing(&self) -> Option<OutgoingMessage> {
621 let mut state = self.state.lock().expect("session state not poisoned");
622 state.expire(self.now());
623 match &mut *state {
624 State::Open {
625 outgoing,
626 reserved_ids,
627 ..
628 } => {
629 let message = outgoing.pop_front()?;
630 if let OutgoingBody::Reply { id, .. } = &message.body {
631 reserved_ids.remove(id);
632 }
633 Some(message)
634 }
635 State::Closed(_) => None,
636 }
637 }
638
639 pub(super) fn record_write(&self, key: &OperationKey, result: Result<(), Error>) {
642 let mut state = self.state.lock().expect("session state not poisoned");
643 let State::Open { operations, .. } = &mut *state else {
644 return;
645 };
646 let Some(operation) = operations.get(key) else {
647 return;
648 };
649 let now = self.now();
650 if now >= operation.deadline || result.is_err() {
651 let operation = operations.remove(key).expect("operation held under lock");
652 operation.fail(result.err().unwrap_or(Error::Timeout), now);
653 } else if !operation.sender.response {
654 let operation = operations.remove(key).expect("operation held under lock");
655 let _ = operation.sender.send(Ok(PromiseResult::Written));
656 }
657 }
658
659 #[cfg(any(test, feature = "fuzz"))]
662 pub(super) fn record_response(
663 self: &Arc<Self>,
664 key: &OperationKey,
665 result: Result<Message, Error>,
666 ) {
667 let mut state = self.state.lock().expect("session state not poisoned");
668 let State::Open {
669 operations,
670 max_inbound_bytes,
671 ..
672 } = &mut *state
673 else {
674 return;
675 };
676 let Some(operation) = operations.remove(key) else {
677 return;
678 };
679 let result = match result {
680 Ok(message) => Ok(message),
681 Err(Error::Remote(error)) => Err(error),
682 Err(error) => {
683 operation.fail(error, self.now());
684 return;
685 }
686 };
687 let peer = match self.side {
688 Side::Client => Side::Server,
689 Side::Server => Side::Client,
690 };
691 let bytes = peer
692 .encode(0, result)
693 .expect("fixture response belongs to the peer");
694 let bytes = Bytes::from(bytes.into_boxed_slice());
695 let header = self
696 .side
697 .decode_header(bytes.clone())
698 .expect("fixture response has a valid envelope");
699 let result = operation.complete_response(self.now(), || {
700 self.retain_incoming(bytes, header, *max_inbound_bytes)
701 });
702 drop(state);
703 if let Err(error) = result {
704 self.close(error);
705 }
706 }
707
708 fn now(&self) -> Instant {
711 #[cfg(any(test, feature = "fuzz"))]
712 if let Some(now) = *self.time.lock().expect("scenario clock not poisoned") {
713 return now;
714 }
715 Instant::now()
716 }
717
718 pub(super) fn handle_message(self: &Arc<Self>, bytes: Vec<u8>) -> Result<(), Error> {
722 let bytes = Bytes::from(bytes.into_boxed_slice());
723 let header = self.side.decode_header(bytes.clone())?;
724 let mut unknown = None;
725 {
726 let mut state = self.state.lock().expect("session state not poisoned");
727 match MessageKind::from_id(header.id, self.side.into()) {
728 MessageKind::Request => {
729 if header.failed {
730 return Err(self.side.malformed(
731 Some(header),
732 bytes.len(),
733 "envelope",
734 "request contains an error",
735 ));
736 }
737 self.admit_request(&mut state, header, bytes)?;
740 if header.unknown {
741 unknown = Some(header.id);
742 }
743 }
744 MessageKind::Response => {
745 let State::Open {
746 operations,
747 outstanding,
748 max_inbound_bytes,
749 ..
750 } = &mut *state
751 else {
752 let State::Closed(error) = &*state else {
753 unreachable!()
754 };
755 return Err(error.clone());
756 };
757 if let Some(key) = outstanding.remove(&header.id) {
760 if let Some(operation) = operations.remove(&key) {
761 tracing::trace!(
762 "received response {} ({})",
763 header.id,
764 header.payload.unwrap_or("none")
765 );
766 operation.complete_response(self.now(), || {
767 self.retain_incoming(bytes, header, *max_inbound_bytes)
768 })?;
769 } else {
770 tracing::debug!("discarding late response {}", header.id);
771 }
772 } else {
773 tracing::warn!("discarding unmatched response {}", header.id);
774 }
775 }
776 }
777 }
778 if let Some(id) = unknown {
779 self.reply_unknown(id);
780 }
781 self.changed.notify_all();
782 Ok(())
783 }
784
785 fn admit_request(
789 self: &Arc<Self>,
790 state: &mut State,
791 header: Header,
792 bytes: Bytes,
793 ) -> Result<(), Error> {
794 let State::Open {
795 incoming,
796 reserved_ids,
797 max_inbound_requests,
798 max_inbound_bytes,
799 ..
800 } = state
801 else {
802 let State::Closed(error) = state else {
803 unreachable!()
804 };
805 return Err(error.clone());
806 };
807 if reserved_ids.contains(&header.id) {
808 return Err(self.side.malformed(
809 Some(header),
810 bytes.len(),
811 "envelope",
812 "duplicate request ID",
813 ));
814 }
815 let id = header.id;
816 if reserved_ids.len() >= *max_inbound_requests {
817 tracing::warn!(
818 "inbound request limit exceeded (id: {}, used: {}, limit: {})",
819 id,
820 reserved_ids.len(),
821 max_inbound_requests
822 );
823 return Err(Error::InboundRequestLimitExceeded(*max_inbound_requests));
824 }
825 let payload = header.payload.unwrap_or("none");
826 let message = if header.unknown {
827 None
828 } else {
829 Some(self.retain_incoming(bytes, header, *max_inbound_bytes)?)
830 };
831 reserved_ids.insert(id);
832 if let Some(message) = message {
833 incoming.push_back((id, message));
834 }
835 tracing::trace!("received request {} ({})", id, payload);
836 Ok(())
837 }
838
839 pub(super) fn next_outgoing(&self) -> Option<(u64, OutgoingMessage)> {
844 let mut state = self.state.lock().expect("session state not poisoned");
845 loop {
846 state.expire(self.now());
848 let State::Open {
849 outgoing,
850 next_id,
851 outstanding,
852 operations,
853 reserved_ids,
854 ..
855 } = &mut *state
856 else {
857 return None;
858 };
859 if let Some(outgoing) = outgoing.pop_front() {
860 let id = match &outgoing.body {
861 OutgoingBody::Request(_) => {
862 let id = next_id.expect("wire request IDs exhausted");
863 *next_id = id.checked_add(2);
864 outstanding.insert(id, outgoing.operation.key.clone());
867 if let Some(operation) = operations.get_mut(&outgoing.operation.key) {
868 operation.log_id = Some(LogId::from(id));
869 }
870 id
871 }
872 OutgoingBody::Reply { id, .. } => {
873 reserved_ids.remove(id);
877 *id
878 }
879 };
880 return Some((id, outgoing));
881 }
882 state = self
883 .changed
884 .wait(state)
885 .expect("session state not poisoned");
886 }
887 }
888
889 fn run_writer(&self, sender: transport::Sender<impl Write>) {
892 while let Some((id, outgoing)) = self.next_outgoing() {
893 let request = matches!(outgoing.body, OutgoingBody::Request(_));
896 let kind = if request { "request" } else { "reply" };
897 let body = match outgoing.body {
898 OutgoingBody::Request(body) => Ok(body),
899 OutgoingBody::Reply { result, .. } => result,
900 };
901 let payload = match &body {
902 Ok(message) => message.field_name(),
903 Err(_) => "err",
904 };
905 let result = self.side.encode(id, body).and_then(|bytes| {
906 tracing::trace!("sending {} {} ({})", kind, id, payload);
909 sender.send(&bytes).map_err(Error::from)
910 });
911 match &result {
912 Ok(()) => {}
913 Err(Error::Transport(error)) => {
914 self.close(Error::Transport(error.clone()));
917 break;
918 }
919 Err(error) => tracing::debug!("not sending {} {}: {}", kind, id, error),
920 }
921 {
922 let mut state = self.state.lock().expect("session state not poisoned");
925 if let State::Open { outstanding, .. } = &mut *state
926 && request
927 && result.is_err()
928 {
929 outstanding.remove(&id);
930 }
931 }
932 outgoing.operation.record_write(result);
935 }
936 #[cfg(any(test, feature = "fuzz"))]
937 if let Some((entered, released)) = self.disconnect_hook.lock().unwrap().take() {
938 let _ = entered.send(());
939 let _ = released.recv();
940 }
941 if let Err(error) = sender.disconnect() {
944 tracing::debug!(
945 "failed to signal dropped session {}: {}",
946 sender.log_id(),
947 error
948 );
949 }
950 }
951
952 fn run_deadlines(&self) {
955 let mut state = self.state.lock().expect("session state not poisoned");
956 loop {
957 state.expire(self.now());
958 let State::Open { operations, .. } = &*state else {
959 return;
960 };
961 state = match operations
964 .values()
965 .map(|operation| operation.deadline)
966 .min()
967 {
968 Some(deadline) => {
969 self.changed
970 .wait_timeout(state, deadline.saturating_duration_since(self.now()))
971 .expect("session state not poisoned")
972 .0
973 }
974 None => self
975 .changed
976 .wait(state)
977 .expect("session state not poisoned"),
978 };
979 }
980 }
981}
982
983impl Session {
984 pub(super) fn start<W: Write + Send + 'static>(
987 side: Side,
988 sender: transport::Sender<W>,
989 stream_closer: Option<transport::Closer>,
990 #[cfg(any(test, feature = "fuzz"))] workers: Arc<worker::Tracker>,
991 ) -> Self {
992 let session = Self {
993 inner: Arc::new(SessionInner::new(
994 side,
995 sender.log_id(),
996 stream_closer,
997 #[cfg(any(test, feature = "fuzz"))]
998 workers.clone(),
999 )),
1000 };
1001 let inner = session.inner.clone();
1002 worker::spawn(
1003 "wire-writer",
1004 #[cfg(any(test, feature = "fuzz"))]
1005 &workers,
1006 move || inner.run_writer(sender),
1007 );
1008 let inner = session.inner.clone();
1009 worker::spawn(
1010 "wire-deadlines",
1011 #[cfg(any(test, feature = "fuzz"))]
1012 &workers,
1013 move || inner.run_deadlines(),
1014 );
1015 session
1016 }
1017}
1018
1019impl State {
1020 fn workload(&self) -> (usize, usize) {
1022 match self {
1023 Self::Open {
1024 operations,
1025 outgoing,
1026 incoming,
1027 ..
1028 } => (operations.len(), outgoing.len() + incoming.len()),
1029 Self::Closed(_) => (0, 0),
1030 }
1031 }
1032
1033 fn close(&mut self, error: Error, now: Instant) -> Option<Self> {
1036 if let Self::Closed(_) = self {
1037 return None;
1038 }
1039 let mut removed = std::mem::replace(self, Self::Closed(error.clone()));
1040 if let Self::Open { operations, .. } = &mut removed {
1041 for (_, operation) in operations.drain() {
1042 operation.fail(error.clone(), now);
1043 }
1044 }
1045 Some(removed)
1046 }
1047
1048 fn expire(&mut self, now: Instant) {
1051 if let Self::Open {
1052 operations,
1053 outgoing,
1054 reserved_ids,
1055 ..
1056 } = self
1057 {
1058 let expired: Vec<_> = operations
1059 .iter()
1060 .filter(|(_, operation)| now >= operation.deadline)
1061 .map(|(key, _)| key.clone())
1062 .collect();
1063 for key in expired {
1064 operations
1065 .remove(&key)
1066 .expect("expired operation held under lock")
1067 .fail(Error::Timeout, now);
1068 }
1069 outgoing.retain(|outgoing| {
1072 let retained = operations.contains_key(&outgoing.operation.key);
1073 if !retained && let OutgoingBody::Reply { id, .. } = outgoing.body {
1074 reserved_ids.remove(&id);
1075 }
1076 retained
1077 });
1078 }
1079 }
1080}
1081
1082#[cfg(any(test, feature = "fuzz"))]
1084impl Session {
1085 pub(super) fn fixture() -> Self {
1087 Self::fixture_for(Side::Server)
1088 }
1089
1090 pub(super) fn fixture_for(side: Side) -> Self {
1092 Self {
1093 inner: Arc::new(SessionInner::new(
1094 side,
1095 LogId::default(),
1096 None,
1097 Arc::new(worker::Tracker::default()),
1098 )),
1099 }
1100 }
1101}
1102
1103#[cfg(any(test, feature = "fuzz"))]
1104impl SessionInner {
1105 pub(super) fn pause_disconnect(
1108 &self,
1109 ) -> (std::sync::mpsc::Receiver<()>, std::sync::mpsc::Sender<()>) {
1110 let (entered, observed) = std::sync::mpsc::channel();
1111 let (release, released) = std::sync::mpsc::channel();
1112 *self.disconnect_hook.lock().unwrap() = Some((entered, released));
1113 (observed, release)
1114 }
1115
1116 pub(super) fn watch_drop(&self) -> std::sync::mpsc::Receiver<()> {
1118 let (sender, receiver) = std::sync::mpsc::channel();
1119 *self.drop_hook.lock().unwrap() = Some(sender);
1120 receiver
1121 }
1122
1123 pub(super) fn use_last_request_id(&self) {
1125 let mut state = self.state.lock().unwrap();
1126 let State::Open {
1127 next_id,
1128 outstanding,
1129 ..
1130 } = &mut *state
1131 else {
1132 panic!("open session required")
1133 };
1134 assert!(outstanding.is_empty());
1135 *next_id = Some(if self.side == Side::Client {
1136 u64::MAX
1137 } else {
1138 u64::MAX - 1
1139 });
1140 }
1141
1142 pub(super) fn wait_response(&self, id: u64) {
1145 let state = self.state.lock().unwrap();
1146 let (state, _) = self.changed.wait_timeout_while(state, Duration::from_secs(3), |state| {
1147 matches!(state, State::Open { outstanding, .. } if outstanding.contains_key(&id))
1148 }).unwrap();
1149 let State::Open { outstanding, .. } = &*state else {
1150 panic!("session closed before response fence");
1151 };
1152 assert!(
1153 !outstanding.contains_key(&id),
1154 "reader did not process response"
1155 );
1156 }
1157
1158 pub(super) fn outstanding_ids(&self) -> Vec<u64> {
1160 let state = self.state.lock().unwrap();
1161 let State::Open { outstanding, .. } = &*state else {
1162 panic!("open session required")
1163 };
1164 let mut ids: Vec<_> = outstanding.keys().copied().collect();
1165 ids.sort_unstable();
1166 ids
1167 }
1168
1169 pub(super) fn inject_request(self: &Arc<Self>, id: u64, message: Message) -> Result<(), Error> {
1172 let peer = match self.side {
1173 Side::Client => Side::Server,
1174 Side::Server => Side::Client,
1175 };
1176 let bytes = peer
1177 .encode(id, Ok(message))
1178 .expect("fixture request belongs to peer");
1179 let bytes = Bytes::from(bytes.into_boxed_slice());
1180 let header = self.side.decode_header(bytes.clone())?;
1181 let result = {
1182 let mut state = self.state.lock().expect("session state not poisoned");
1183 self.admit_request(&mut state, header, bytes)
1184 };
1185 self.changed.notify_all();
1186 if let Err(error) = &result {
1187 self.close(error.clone());
1188 }
1189 result
1190 }
1191
1192 pub(super) fn inbound_usage(&self) -> (usize, usize) {
1194 let state = self.state.lock().expect("session state not poisoned");
1195 let requests = match &*state {
1196 State::Open { reserved_ids, .. } => reserved_ids.len(),
1197 State::Closed(_) => 0,
1198 };
1199 (requests, self.retained_bytes.load(Ordering::Relaxed))
1200 }
1201
1202 pub(super) fn set_time(&self, now: Instant) {
1205 let _state = self.state.lock().expect("session state not poisoned");
1206 let mut time = self.time.lock().expect("scenario clock not poisoned");
1207 assert!(
1208 time.is_none_or(|previous| now >= previous),
1209 "clock cannot go backwards"
1210 );
1211 *time = Some(now);
1212 }
1213
1214 pub(super) fn use_realtime(&self) {
1216 let _state = self.state.lock().expect("session state not poisoned");
1217 *self.time.lock().expect("scenario clock not poisoned") = None;
1218 }
1219
1220 pub(super) fn watch_recv_wait(&self) -> std::sync::mpsc::Receiver<()> {
1227 let (sender, receiver) = std::sync::mpsc::channel();
1228 let mut state = self.state.lock().expect("session state not poisoned");
1229 let State::Open {
1230 incoming,
1231 wait_hook,
1232 ..
1233 } = &mut *state
1234 else {
1235 panic!("only watch an open session receive");
1236 };
1237 assert!(incoming.is_empty());
1238 *wait_hook = Some(sender);
1239 receiver
1240 }
1241}
1242
1243#[cfg(any(test, feature = "fuzz"))]
1244impl Drop for SessionInner {
1245 fn drop(&mut self) {
1247 if let Some(sender) = self.drop_hook.get_mut().unwrap().take() {
1248 let _ = sender.send(());
1249 }
1250 }
1251}
1252
1253#[cfg(test)]
1255#[cfg_attr(coverage_nightly, coverage(off))]
1256mod tests {
1257 use crate::protocol::{self, Error, Session};
1258 use crate::transport::{Read, Stream, Verifier, Write};
1259 use std::fmt::Debug;
1260
1261 #[allow(dead_code)]
1263 fn connect<R, W, V>(stream: Stream<R, W>, verifier: &V) -> Result<(Session, V::Info), Error>
1264 where
1265 R: Read + Send + 'static,
1266 W: Write + Send + 'static,
1267 V: Verifier,
1268 {
1269 protocol::connect(stream, verifier)
1270 }
1271
1272 #[test]
1275 fn test_thread_capabilities() {
1276 fn movable<T: Debug + Send + 'static>() {}
1278 movable::<Session>();
1279 }
1280}