1use super::envelope::{Header, IncomingEnvelope, MessageKind, Parity, Side};
7use super::operation::{
8 OperationHandle, OperationKey, OutgoingBody, OutgoingMessage, PendingOperation,
9};
10use super::promise::PromiseResult;
11use super::worker;
12use super::{
13 Closer, DEFAULT_ABANDONMENT_TIMEOUT, DEFAULT_MAX_INBOUND_BYTES, DEFAULT_MAX_INBOUND_REQUESTS,
14 Error, Message, Promise, RemoteError, Requester, ReservedErrors, Responder,
15};
16use crate::LogId;
17use crate::transport::{self, Read, Stream, Verifier, Write};
18use prost::bytes::Bytes;
19use std::collections::{HashMap, HashSet, VecDeque};
20use std::sync::atomic::{AtomicUsize, Ordering};
21use std::sync::{Arc, Condvar, Mutex};
22use std::time::{Duration, Instant};
23
24pub fn connect<R, W, V>(stream: Stream<R, W>, verifier: &V) -> Result<(Session, V::Info), Error>
30where
31 R: Read + Send + 'static,
32 W: Write + Send + 'static,
33 V: Verifier,
34{
35 let mut client = transport::Client::new(stream);
36
37 let (sender, info) = client.connect(verifier)?;
38 #[cfg(any(test, feature = "fuzz"))]
39 let workers = Arc::new(worker::Tracker::default());
40 let session = Session::start(
41 Side::Client,
42 sender,
43 Some(client.closer()),
44 #[cfg(any(test, feature = "fuzz"))]
45 workers.clone(),
46 );
47 let inner = session.inner.clone();
48
49 worker::spawn(
50 "wire-client-reader",
51 #[cfg(any(test, feature = "fuzz"))]
52 &workers,
53 move || run_reader(client, inner),
54 );
55 Ok((session, info))
56}
57
58fn run_reader<R: Read, W: Write>(mut client: transport::Client<R, W>, session: Arc<SessionInner>) {
62 loop {
63 let result = client
64 .recv()
65 .map_err(Error::from)
66 .and_then(|bytes| session.handle_message(bytes));
67 if let Err(error) = result {
68 session.close(error);
71 break;
72 }
73 }
74}
75
76pub struct Session {
96 pub(super) inner: Arc<SessionInner>,
98}
99
100impl Session {
101 pub fn set_abandonment_timeout(self, timeout: Duration) -> Self {
112 self.inner.set_abandonment_timeout(timeout);
113 self
114 }
115
116 pub fn set_inbound_limits(self, requests: usize, bytes: usize) -> Self {
134 self.inner.set_inbound_limits(requests, bytes);
135 self
136 }
137
138 pub fn requester(&self) -> Requester {
140 Requester::new(Arc::downgrade(&self.inner))
141 }
142
143 pub fn recv(&mut self) -> Result<(Message, Responder), Error> {
155 self.inner.recv()
156 }
157
158 pub fn closer(&self) -> Closer {
161 Closer::session(Arc::downgrade(&self.inner))
162 }
163
164 pub fn close(&self) {
170 self.inner.close(Error::Closed);
171 }
172}
173
174impl Drop for Session {
175 fn drop(&mut self) {
177 self.close();
178 }
179}
180
181pub(super) struct SessionInner {
184 state: Mutex<State>,
186 changed: Condvar,
188 retained_bytes: Arc<AtomicUsize>,
192 side: Side,
194 pub(super) log_id: LogId,
196 stream_closer: Option<transport::Closer>,
199 #[cfg(any(test, feature = "fuzz"))]
201 pub(super) workers: Arc<worker::Tracker>,
202 #[cfg(any(test, feature = "fuzz"))]
204 time: Mutex<Option<Instant>>,
205 #[cfg(any(test, feature = "fuzz"))]
207 drop_hook: Mutex<Option<std::sync::mpsc::Sender<()>>>,
208 #[cfg(any(test, feature = "fuzz"))]
210 disconnect_hook: Mutex<Option<(std::sync::mpsc::Sender<()>, std::sync::mpsc::Receiver<()>)>>,
211}
212
213#[cfg_attr(any(test, feature = "fuzz"), allow(clippy::large_enum_variant))]
217enum State {
218 Open {
221 max_inbound_requests: usize,
223 max_inbound_bytes: usize,
225 abandonment: Duration,
227
228 incoming: VecDeque<(u64, IncomingEnvelope)>,
230 reserved_ids: HashSet<u64>,
236
237 outgoing: VecDeque<OutgoingMessage>,
239 next_id: Option<u64>,
241 outstanding: HashMap<u64, OperationKey>,
244 operations: HashMap<OperationKey, PendingOperation>,
247
248 #[cfg(any(test, feature = "fuzz"))]
250 wait_hook: Option<std::sync::mpsc::Sender<()>>,
251 },
252 Closed(Error),
254}
255
256impl SessionInner {
257 fn new(
259 side: Side,
260 log_id: LogId,
261 stream_closer: Option<transport::Closer>,
262 #[cfg(any(test, feature = "fuzz"))] workers: Arc<worker::Tracker>,
263 ) -> Self {
264 Self {
265 state: Mutex::new(State::Open {
266 max_inbound_requests: DEFAULT_MAX_INBOUND_REQUESTS,
267 max_inbound_bytes: DEFAULT_MAX_INBOUND_BYTES,
268 abandonment: DEFAULT_ABANDONMENT_TIMEOUT,
269
270 incoming: VecDeque::new(),
271 reserved_ids: HashSet::new(),
272
273 outgoing: VecDeque::new(),
274 next_id: Some(Parity::from(side).first()),
275 outstanding: HashMap::new(),
276 operations: HashMap::new(),
277
278 #[cfg(any(test, feature = "fuzz"))]
279 wait_hook: None,
280 }),
281 changed: Condvar::new(),
282 retained_bytes: Arc::new(AtomicUsize::new(0)),
283 side,
284 log_id,
285 stream_closer,
286 #[cfg(any(test, feature = "fuzz"))]
287 workers,
288 #[cfg(any(test, feature = "fuzz"))]
289 time: Mutex::new(None),
290 #[cfg(any(test, feature = "fuzz"))]
291 drop_hook: Mutex::new(None),
292 #[cfg(any(test, feature = "fuzz"))]
293 disconnect_hook: Mutex::new(None),
294 }
295 }
296
297 pub(super) fn set_abandonment_timeout(&self, timeout: Duration) {
300 let mut state = self.state.lock().expect("session state not poisoned");
301 if let State::Open { abandonment, .. } = &mut *state {
302 *abandonment = timeout;
303 }
304 }
305
306 pub(super) fn set_inbound_limits(&self, requests: usize, bytes: usize) {
309 let removed = {
310 let mut state = self.state.lock().expect("session state not poisoned");
311 let State::Open {
312 max_inbound_requests,
313 max_inbound_bytes,
314 reserved_ids,
315 ..
316 } = &mut *state
317 else {
318 return;
319 };
320 *max_inbound_requests = requests;
321 *max_inbound_bytes = bytes;
322 let error = if reserved_ids.len() > *max_inbound_requests {
323 Some(Error::InboundRequestLimitExceeded(*max_inbound_requests))
324 } else if self.retained_bytes.load(Ordering::Relaxed) > *max_inbound_bytes {
325 Some(Error::InboundByteLimitExceeded(*max_inbound_bytes))
326 } else {
327 None
328 };
329 error.and_then(|error| {
330 let (pending, queued) = state.workload();
331 let removed = state.close(error.clone(), self.now());
332 if removed.is_some() {
333 self.log_closed(&error, pending, queued);
334 }
335 removed
336 })
337 };
338 if removed.is_some() {
339 self.finish_close(removed);
340 }
341 }
342
343 fn retain_incoming(
346 self: &Arc<Self>,
347 bytes: Bytes,
348 header: Header,
349 limit: usize,
350 ) -> Result<IncomingEnvelope, Error> {
351 IncomingEnvelope::new(
352 bytes,
353 header,
354 &self.retained_bytes,
355 limit,
356 self.side,
357 Arc::downgrade(self),
358 )
359 }
360
361 fn recv(self: &Arc<Self>) -> Result<(Message, Responder), Error> {
366 let mut state = self.state.lock().expect("session state not poisoned");
367 let (message, responder) = loop {
368 match &mut *state {
369 State::Closed(error) => return Err(error.clone()),
370 State::Open {
371 incoming,
372 #[cfg(any(test, feature = "fuzz"))]
373 wait_hook,
374 ..
375 } => {
376 if let Some((id, message)) = incoming.pop_front() {
377 break (message, Responder::new(Arc::downgrade(self), id));
378 }
379 #[cfg(any(test, feature = "fuzz"))]
380 if let Some(wait_hook) = wait_hook.take() {
381 let _ = wait_hook.send(());
382 }
383 state = self
384 .changed
385 .wait(state)
386 .expect("session state not poisoned");
387 }
388 }
389 };
390 drop(state);
391 Ok((message.decode()?, responder))
392 }
393
394 pub(super) fn close(&self, error: Error) {
398 let (removed, pending, queued) = {
399 let mut state = self.state.lock().expect("session state not poisoned");
400 let (pending, queued) = state.workload();
401 (state.close(error.clone(), self.now()), pending, queued)
402 };
403 if removed.is_some() {
404 self.log_closed(&error, pending, queued);
405 }
406 self.finish_close(removed);
407 }
408
409 fn log_closed(&self, error: &Error, pending: usize, queued: usize) {
412 match error {
413 Error::Closed => tracing::info!(
414 "wire session {} closed locally (pending {}, queued {})",
415 self.log_id,
416 pending,
417 queued
418 ),
419 _ if error.orderly() => tracing::info!(
420 "wire session {} closed: {} (pending {}, queued {})",
421 self.log_id,
422 error.reason(),
423 pending,
424 queued
425 ),
426 _ => tracing::warn!(
427 "wire session {} failed: {} (pending {}, queued {})",
428 self.log_id,
429 error.reason(),
430 pending,
431 queued
432 ),
433 }
434 }
435
436 fn finish_close(&self, removed: Option<State>) {
438 self.changed.notify_all();
441 drop(removed);
442 if let Some(stream_closer) = &self.stream_closer {
443 stream_closer.close();
444 }
445 }
446
447 pub(super) fn reply_unanswered(self: &Arc<Self>, id: u64) {
452 let now = self.now();
453 let timeout = {
454 let state = self.state.lock().expect("session state not poisoned");
455 match &*state {
456 State::Open {
457 abandonment: abandonment_timeout,
458 ..
459 } => *abandonment_timeout,
460 State::Closed(_) => return,
461 }
462 };
463 tracing::debug!("answering request {} as unanswered", id);
464
465 let deadline = now.checked_add(timeout).unwrap_or(now);
468 let _ = self.reply(
469 id,
470 Err(RemoteError::reserved(
471 ReservedErrors::Unanswered,
472 "request left unanswered",
473 )),
474 deadline,
475 );
476 }
477
478 pub(super) fn request(
481 self: &Arc<Self>,
482 request: Message,
483 deadline: Instant,
484 ) -> Result<Promise<Message>, Error> {
485 let (sender, promise) = Promise::pair(Arc::downgrade(self), deadline, true);
486 self.enqueue(
487 OutgoingBody::Request(request),
488 PendingOperation {
489 deadline,
490 sender,
491 log_id: None,
492 },
493 )?;
494 Ok(promise)
495 }
496
497 pub(super) fn reply(
500 self: &Arc<Self>,
501 id: u64,
502 result: Result<Message, RemoteError>,
503 deadline: Instant,
504 ) -> Result<Promise<()>, Error> {
505 let (sender, promise) = Promise::pair(Arc::downgrade(self), deadline, false);
506 self.enqueue(
507 OutgoingBody::Reply { id, result },
508 PendingOperation {
509 deadline,
510 sender,
511 log_id: Some(LogId::from(id)),
512 },
513 )?;
514 Ok(promise)
515 }
516
517 fn enqueue(
521 self: &Arc<Self>,
522 body: OutgoingBody,
523 operation: PendingOperation,
524 ) -> Result<(), Error> {
525 {
526 let mut state = self.state.lock().expect("session state not poisoned");
527 let (operations, outgoing, reserved_ids) = match &mut *state {
528 State::Open {
529 operations,
530 outgoing,
531 reserved_ids,
532 ..
533 } => (operations, outgoing, reserved_ids),
534 State::Closed(error) => return Err(error.clone()),
535 };
536 let now = self.now();
537 if now >= operation.deadline {
538 if let OutgoingBody::Reply { id, .. } = body {
539 reserved_ids.remove(&id);
540 }
541 operation.fail(Error::Timeout, now);
542 return Ok(());
543 }
544 let key = OperationKey::new();
545 outgoing.push_back(OutgoingMessage {
546 body,
547 operation: OperationHandle {
548 session: Arc::downgrade(self),
549 key: key.clone(),
550 },
551 #[cfg(any(test, feature = "fuzz"))]
552 deadline: operation.deadline,
553 });
554 operations.insert(key, operation);
555 }
556 self.changed.notify_all();
557 Ok(())
558 }
559
560 pub(super) fn expire(&self) {
564 let mut state = self.state.lock().expect("session state not poisoned");
565 state.expire(self.now());
566 }
567
568 #[cfg(any(test, feature = "fuzz"))]
570 pub(super) fn next_deadline(&self) -> Option<Instant> {
571 let state = self.state.lock().expect("session state not poisoned");
572 match &*state {
573 State::Open { operations, .. } => operations
574 .values()
575 .map(|operation| operation.deadline)
576 .min(),
577 State::Closed(_) => None,
578 }
579 }
580
581 #[cfg(any(test, feature = "fuzz"))]
583 pub(super) fn take_outgoing(&self) -> Option<OutgoingMessage> {
584 let mut state = self.state.lock().expect("session state not poisoned");
585 state.expire(self.now());
586 match &mut *state {
587 State::Open {
588 outgoing,
589 reserved_ids,
590 ..
591 } => {
592 let message = outgoing.pop_front()?;
593 if let OutgoingBody::Reply { id, .. } = &message.body {
594 reserved_ids.remove(id);
595 }
596 Some(message)
597 }
598 State::Closed(_) => None,
599 }
600 }
601
602 pub(super) fn record_write(&self, key: &OperationKey, result: Result<(), Error>) {
605 let mut state = self.state.lock().expect("session state not poisoned");
606 let State::Open { operations, .. } = &mut *state else {
607 return;
608 };
609 let Some(operation) = operations.get(key) else {
610 return;
611 };
612 let now = self.now();
613 if now >= operation.deadline || result.is_err() {
614 let operation = operations.remove(key).expect("operation held under lock");
615 operation.fail(result.err().unwrap_or(Error::Timeout), now);
616 } else if !operation.sender.response {
617 let operation = operations.remove(key).expect("operation held under lock");
618 let _ = operation.sender.send(Ok(PromiseResult::Written));
619 }
620 }
621
622 #[cfg(any(test, feature = "fuzz"))]
625 pub(super) fn record_response(
626 self: &Arc<Self>,
627 key: &OperationKey,
628 result: Result<Message, Error>,
629 ) {
630 let mut state = self.state.lock().expect("session state not poisoned");
631 let State::Open {
632 operations,
633 max_inbound_bytes,
634 ..
635 } = &mut *state
636 else {
637 return;
638 };
639 let Some(operation) = operations.remove(key) else {
640 return;
641 };
642 let result = match result {
643 Ok(message) => Ok(message),
644 Err(Error::Remote(error)) => Err(error),
645 Err(error) => {
646 operation.fail(error, self.now());
647 return;
648 }
649 };
650 let peer = match self.side {
651 Side::Client => Side::Server,
652 Side::Server => Side::Client,
653 };
654 let bytes = peer
655 .encode(0, result)
656 .expect("fixture response belongs to the peer");
657 let bytes = Bytes::from(bytes.into_boxed_slice());
658 let header = self
659 .side
660 .decode_header(bytes.clone())
661 .expect("fixture response has a valid envelope");
662 let result = operation.complete_response(self.now(), || {
663 self.retain_incoming(bytes, header, *max_inbound_bytes)
664 });
665 drop(state);
666 if let Err(error) = result {
667 self.close(error);
668 }
669 }
670
671 fn now(&self) -> Instant {
674 #[cfg(any(test, feature = "fuzz"))]
675 if let Some(now) = *self.time.lock().expect("scenario clock not poisoned") {
676 return now;
677 }
678 Instant::now()
679 }
680
681 pub(super) fn handle_message(self: &Arc<Self>, bytes: Vec<u8>) -> Result<(), Error> {
685 let bytes = Bytes::from(bytes.into_boxed_slice());
686 let header = self.side.decode_header(bytes.clone())?;
687 {
688 let mut state = self.state.lock().expect("session state not poisoned");
689 match MessageKind::from_id(header.id, self.side.into()) {
690 MessageKind::Request => {
691 if header.failed {
692 return Err(self.side.malformed(
693 Some(header),
694 bytes.len(),
695 "envelope",
696 "request contains an error",
697 ));
698 }
699 self.queue_request(&mut state, header, bytes)?;
700 }
701 MessageKind::Response => {
702 let State::Open {
703 operations,
704 outstanding,
705 max_inbound_bytes,
706 ..
707 } = &mut *state
708 else {
709 let State::Closed(error) = &*state else {
710 unreachable!()
711 };
712 return Err(error.clone());
713 };
714 if let Some(key) = outstanding.remove(&header.id) {
717 if let Some(operation) = operations.remove(&key) {
718 tracing::trace!(
719 "received response {} ({})",
720 header.id,
721 header.payload.unwrap_or("none")
722 );
723 operation.complete_response(self.now(), || {
724 self.retain_incoming(bytes, header, *max_inbound_bytes)
725 })?;
726 } else {
727 tracing::debug!("discarding late response {}", header.id);
728 }
729 } else {
730 tracing::warn!("discarding unmatched response {}", header.id);
731 }
732 }
733 }
734 }
735 self.changed.notify_all();
736 Ok(())
737 }
738
739 fn queue_request(
742 self: &Arc<Self>,
743 state: &mut State,
744 header: Header,
745 bytes: Bytes,
746 ) -> Result<(), Error> {
747 let State::Open {
748 incoming,
749 reserved_ids,
750 max_inbound_requests,
751 max_inbound_bytes,
752 ..
753 } = state
754 else {
755 let State::Closed(error) = state else {
756 unreachable!()
757 };
758 return Err(error.clone());
759 };
760 let id = header.id;
761 if reserved_ids.contains(&id) {
762 return Err(self.side.malformed(
763 Some(header),
764 bytes.len(),
765 "envelope",
766 "duplicate request ID",
767 ));
768 }
769 if reserved_ids.len() >= *max_inbound_requests {
770 tracing::warn!(
771 "inbound request limit exceeded (id: {}, used: {}, limit: {})",
772 id,
773 reserved_ids.len(),
774 max_inbound_requests
775 );
776 return Err(Error::InboundRequestLimitExceeded(*max_inbound_requests));
777 }
778 let payload = header.payload.unwrap_or("none");
779 let message = self.retain_incoming(bytes, header, *max_inbound_bytes)?;
780 reserved_ids.insert(id);
781 incoming.push_back((id, message));
782 tracing::trace!("received request {} ({})", id, payload);
783 Ok(())
784 }
785
786 pub(super) fn next_outgoing(&self) -> Option<(u64, OutgoingMessage)> {
791 let mut state = self.state.lock().expect("session state not poisoned");
792 loop {
793 state.expire(self.now());
795 let State::Open {
796 outgoing,
797 next_id,
798 outstanding,
799 operations,
800 reserved_ids,
801 ..
802 } = &mut *state
803 else {
804 return None;
805 };
806 if let Some(outgoing) = outgoing.pop_front() {
807 let id = match &outgoing.body {
808 OutgoingBody::Request(_) => {
809 let id = next_id.expect("wire request IDs exhausted");
810 *next_id = id.checked_add(2);
811 outstanding.insert(id, outgoing.operation.key.clone());
814 if let Some(operation) = operations.get_mut(&outgoing.operation.key) {
815 operation.log_id = Some(LogId::from(id));
816 }
817 id
818 }
819 OutgoingBody::Reply { id, .. } => {
820 reserved_ids.remove(id);
824 *id
825 }
826 };
827 return Some((id, outgoing));
828 }
829 state = self
830 .changed
831 .wait(state)
832 .expect("session state not poisoned");
833 }
834 }
835
836 fn run_writer(&self, sender: transport::Sender<impl Write>) {
839 while let Some((id, outgoing)) = self.next_outgoing() {
840 let request = matches!(outgoing.body, OutgoingBody::Request(_));
843 let kind = if request { "request" } else { "reply" };
844 let body = match outgoing.body {
845 OutgoingBody::Request(body) => Ok(body),
846 OutgoingBody::Reply { result, .. } => result,
847 };
848 let payload = match &body {
849 Ok(message) => message.field_name(),
850 Err(_) => "err",
851 };
852 let result = self.side.encode(id, body).and_then(|bytes| {
853 tracing::trace!("sending {} {} ({})", kind, id, payload);
856 sender.send(&bytes).map_err(Error::from)
857 });
858 match &result {
859 Ok(()) => {}
860 Err(Error::Transport(error)) => {
861 self.close(Error::Transport(error.clone()));
864 break;
865 }
866 Err(error) => tracing::debug!("not sending {} {}: {}", kind, id, error),
867 }
868 {
869 let mut state = self.state.lock().expect("session state not poisoned");
872 if let State::Open { outstanding, .. } = &mut *state
873 && request
874 && result.is_err()
875 {
876 outstanding.remove(&id);
877 }
878 }
879 outgoing.operation.record_write(result);
882 }
883 #[cfg(any(test, feature = "fuzz"))]
884 if let Some((entered, released)) = self.disconnect_hook.lock().unwrap().take() {
885 let _ = entered.send(());
886 let _ = released.recv();
887 }
888 if let Err(error) = sender.disconnect() {
891 tracing::debug!(
892 "failed to signal dropped session {}: {}",
893 sender.log_id(),
894 error
895 );
896 }
897 }
898
899 fn run_deadlines(&self) {
902 let mut state = self.state.lock().expect("session state not poisoned");
903 loop {
904 state.expire(self.now());
905 let State::Open { operations, .. } = &*state else {
906 return;
907 };
908 state = match operations
911 .values()
912 .map(|operation| operation.deadline)
913 .min()
914 {
915 Some(deadline) => {
916 self.changed
917 .wait_timeout(state, deadline.saturating_duration_since(self.now()))
918 .expect("session state not poisoned")
919 .0
920 }
921 None => self
922 .changed
923 .wait(state)
924 .expect("session state not poisoned"),
925 };
926 }
927 }
928}
929
930impl Session {
931 pub(super) fn start<W: Write + Send + 'static>(
934 side: Side,
935 sender: transport::Sender<W>,
936 stream_closer: Option<transport::Closer>,
937 #[cfg(any(test, feature = "fuzz"))] workers: Arc<worker::Tracker>,
938 ) -> Self {
939 let session = Self {
940 inner: Arc::new(SessionInner::new(
941 side,
942 sender.log_id(),
943 stream_closer,
944 #[cfg(any(test, feature = "fuzz"))]
945 workers.clone(),
946 )),
947 };
948 let inner = session.inner.clone();
949 worker::spawn(
950 "wire-writer",
951 #[cfg(any(test, feature = "fuzz"))]
952 &workers,
953 move || inner.run_writer(sender),
954 );
955 let inner = session.inner.clone();
956 worker::spawn(
957 "wire-deadlines",
958 #[cfg(any(test, feature = "fuzz"))]
959 &workers,
960 move || inner.run_deadlines(),
961 );
962 session
963 }
964}
965
966impl State {
967 fn workload(&self) -> (usize, usize) {
969 match self {
970 Self::Open {
971 operations,
972 outgoing,
973 incoming,
974 ..
975 } => (operations.len(), outgoing.len() + incoming.len()),
976 Self::Closed(_) => (0, 0),
977 }
978 }
979
980 fn close(&mut self, error: Error, now: Instant) -> Option<Self> {
983 if let Self::Closed(_) = self {
984 return None;
985 }
986 let mut removed = std::mem::replace(self, Self::Closed(error.clone()));
987 if let Self::Open { operations, .. } = &mut removed {
988 for (_, operation) in operations.drain() {
989 operation.fail(error.clone(), now);
990 }
991 }
992 Some(removed)
993 }
994
995 fn expire(&mut self, now: Instant) {
998 if let Self::Open {
999 operations,
1000 outgoing,
1001 reserved_ids,
1002 ..
1003 } = self
1004 {
1005 let expired: Vec<_> = operations
1006 .iter()
1007 .filter(|(_, operation)| now >= operation.deadline)
1008 .map(|(key, _)| key.clone())
1009 .collect();
1010 for key in expired {
1011 operations
1012 .remove(&key)
1013 .expect("expired operation held under lock")
1014 .fail(Error::Timeout, now);
1015 }
1016 outgoing.retain(|outgoing| {
1019 let retained = operations.contains_key(&outgoing.operation.key);
1020 if !retained && let OutgoingBody::Reply { id, .. } = outgoing.body {
1021 reserved_ids.remove(&id);
1022 }
1023 retained
1024 });
1025 }
1026 }
1027}
1028
1029#[cfg(any(test, feature = "fuzz"))]
1031impl Session {
1032 pub(super) fn fixture() -> Self {
1034 Self::fixture_for(Side::Server)
1035 }
1036
1037 pub(super) fn fixture_for(side: Side) -> Self {
1039 Self {
1040 inner: Arc::new(SessionInner::new(
1041 side,
1042 LogId::default(),
1043 None,
1044 Arc::new(worker::Tracker::default()),
1045 )),
1046 }
1047 }
1048}
1049
1050#[cfg(any(test, feature = "fuzz"))]
1051impl SessionInner {
1052 pub(super) fn pause_disconnect(
1055 &self,
1056 ) -> (std::sync::mpsc::Receiver<()>, std::sync::mpsc::Sender<()>) {
1057 let (entered, observed) = std::sync::mpsc::channel();
1058 let (release, released) = std::sync::mpsc::channel();
1059 *self.disconnect_hook.lock().unwrap() = Some((entered, released));
1060 (observed, release)
1061 }
1062
1063 pub(super) fn watch_drop(&self) -> std::sync::mpsc::Receiver<()> {
1065 let (sender, receiver) = std::sync::mpsc::channel();
1066 *self.drop_hook.lock().unwrap() = Some(sender);
1067 receiver
1068 }
1069
1070 pub(super) fn use_last_request_id(&self) {
1072 let mut state = self.state.lock().unwrap();
1073 let State::Open {
1074 next_id,
1075 outstanding,
1076 ..
1077 } = &mut *state
1078 else {
1079 panic!("open session required")
1080 };
1081 assert!(outstanding.is_empty());
1082 *next_id = Some(if self.side == Side::Client {
1083 u64::MAX
1084 } else {
1085 u64::MAX - 1
1086 });
1087 }
1088
1089 pub(super) fn wait_response(&self, id: u64) {
1092 let state = self.state.lock().unwrap();
1093 let (state, _) = self.changed.wait_timeout_while(state, Duration::from_secs(3), |state| {
1094 matches!(state, State::Open { outstanding, .. } if outstanding.contains_key(&id))
1095 }).unwrap();
1096 let State::Open { outstanding, .. } = &*state else {
1097 panic!("session closed before response fence");
1098 };
1099 assert!(
1100 !outstanding.contains_key(&id),
1101 "reader did not process response"
1102 );
1103 }
1104
1105 pub(super) fn outstanding_ids(&self) -> Vec<u64> {
1107 let state = self.state.lock().unwrap();
1108 let State::Open { outstanding, .. } = &*state else {
1109 panic!("open session required")
1110 };
1111 let mut ids: Vec<_> = outstanding.keys().copied().collect();
1112 ids.sort_unstable();
1113 ids
1114 }
1115
1116 pub(super) fn inject_request(self: &Arc<Self>, id: u64, message: Message) -> Result<(), Error> {
1119 let peer = match self.side {
1120 Side::Client => Side::Server,
1121 Side::Server => Side::Client,
1122 };
1123 let bytes = peer
1124 .encode(id, Ok(message))
1125 .expect("fixture request belongs to peer");
1126 let bytes = Bytes::from(bytes.into_boxed_slice());
1127 let header = self.side.decode_header(bytes.clone())?;
1128 let result = {
1129 let mut state = self.state.lock().expect("session state not poisoned");
1130 self.queue_request(&mut state, header, bytes)
1131 };
1132 self.changed.notify_all();
1133 if let Err(error) = &result {
1134 self.close(error.clone());
1135 }
1136 result
1137 }
1138
1139 pub(super) fn inbound_usage(&self) -> (usize, usize) {
1141 let state = self.state.lock().expect("session state not poisoned");
1142 let requests = match &*state {
1143 State::Open { reserved_ids, .. } => reserved_ids.len(),
1144 State::Closed(_) => 0,
1145 };
1146 (requests, self.retained_bytes.load(Ordering::Relaxed))
1147 }
1148
1149 pub(super) fn set_time(&self, now: Instant) {
1152 let _state = self.state.lock().expect("session state not poisoned");
1153 let mut time = self.time.lock().expect("scenario clock not poisoned");
1154 assert!(
1155 time.is_none_or(|previous| now >= previous),
1156 "clock cannot go backwards"
1157 );
1158 *time = Some(now);
1159 }
1160
1161 pub(super) fn use_realtime(&self) {
1163 let _state = self.state.lock().expect("session state not poisoned");
1164 *self.time.lock().expect("scenario clock not poisoned") = None;
1165 }
1166
1167 pub(super) fn watch_recv_wait(&self) -> std::sync::mpsc::Receiver<()> {
1174 let (sender, receiver) = std::sync::mpsc::channel();
1175 let mut state = self.state.lock().expect("session state not poisoned");
1176 let State::Open {
1177 incoming,
1178 wait_hook,
1179 ..
1180 } = &mut *state
1181 else {
1182 panic!("only watch an open session receive");
1183 };
1184 assert!(incoming.is_empty());
1185 *wait_hook = Some(sender);
1186 receiver
1187 }
1188}
1189
1190#[cfg(any(test, feature = "fuzz"))]
1191impl Drop for SessionInner {
1192 fn drop(&mut self) {
1194 if let Some(sender) = self.drop_hook.get_mut().unwrap().take() {
1195 let _ = sender.send(());
1196 }
1197 }
1198}
1199
1200#[cfg(test)]
1202#[cfg_attr(coverage_nightly, coverage(off))]
1203mod tests {
1204 use crate::protocol::{self, Error, Session};
1205 use crate::transport::{Read, Stream, Verifier, Write};
1206
1207 #[allow(dead_code)]
1209 fn connect<R, W, V>(stream: Stream<R, W>, verifier: &V) -> Result<(Session, V::Info), Error>
1210 where
1211 R: Read + Send + 'static,
1212 W: Write + Send + 'static,
1213 V: Verifier,
1214 {
1215 protocol::connect(stream, verifier)
1216 }
1217
1218 #[test]
1220 fn test_thread_capabilities() {
1221 fn movable<T: Send + 'static>() {}
1223 movable::<Session>();
1224 }
1225}