dolang_rpc/client.rs
1//! The calling side of a bound RPC session.
2//!
3//! [`Client::call`] sends a request and returns a [`Call`] future that
4//! resolves to the peer's response. Multiple calls may be outstanding
5//! concurrently on one [`Client`]; each is dispatched and matched to its
6//! response independently.
7
8use std::{
9 collections::{HashMap, HashSet, VecDeque},
10 future::Future,
11 mem,
12 pin::Pin,
13 sync::{Arc, Mutex, Weak},
14 task::{Context, Poll},
15};
16
17#[cfg(windows)]
18use std::{any::TypeId, io};
19
20use futures::{FutureExt, future::BoxFuture};
21use tokio::sync::{mpsc, oneshot};
22
23#[cfg(windows)]
24use std::os::windows::io::{AsRawHandle, FromRawHandle, OwnedHandle};
25
26#[cfg(windows)]
27use windows_sys::Win32::System::Threading::GetProcessId;
28
29#[cfg(unix)]
30use crate::session::SessionHandles;
31use crate::{
32 Error, Limits, Protocol,
33 fragment::{
34 self, AbortOutcome, Event, Flags, FragmentHeader, Kind, Message, Reassembler, Scheduler,
35 Trailer,
36 },
37 serde::{decode_payload, encode_payload},
38 session::{Ledger, ReleaseSink, Session, SessionFrame},
39 trailer::{RecvShared, SendShared, TrailerRecv, TrailerSend},
40 transport::{self, EncodeHandles, Receiver, Sender},
41 window::{ControlSink, PayloadBudget, PayloadCharge, SessionWindow},
42};
43#[cfg(windows)]
44use crate::{handle::TakeHandle, session::Inner as OpaqueInner};
45
46/// A negotiated client endpoint that has not yet been bound to a [`Protocol`].
47///
48/// Inspect its negotiated application protocol, then consume it with
49/// [`bind`](Unbound::bind) to obtain a [`Client`].
50pub use crate::unbound::UnboundClient as Unbound;
51
52type Responder<R> = oneshot::Sender<Result<CallResult<R>, Error>>;
53
54/// The registry of calls awaiting a response, which is poisoned once the
55/// session has authoritatively failed.
56///
57/// The reader task is the only thing that can ever deliver a response, so a
58/// call registered after it has gone would wait forever. Poisoning is what
59/// makes that impossible: the same lock that fails the calls already waiting
60/// records why, and every later registration is refused with that error
61/// instead of being parked on a response nobody will send.
62enum Pending<R> {
63 Live(HashMap<u64, Responder<R>>),
64 Failed(Error),
65}
66
67impl<R> Pending<R> {
68 fn new() -> Self {
69 Self::Live(HashMap::new())
70 }
71
72 /// Registers a call, or settles it immediately with the error the session
73 /// failed with. Returns whether it was registered.
74 #[must_use]
75 fn register(&mut self, id: u64, tx: Responder<R>) -> bool {
76 match self {
77 Self::Live(calls) => {
78 calls.insert(id, tx);
79 true
80 }
81 Self::Failed(error) => {
82 let _ = tx.send(Err(error.copy()));
83 false
84 }
85 }
86 }
87
88 fn remove(&mut self, id: u64) -> Option<Responder<R>> {
89 match self {
90 Self::Live(calls) => calls.remove(&id),
91 Self::Failed(_) => None,
92 }
93 }
94
95 fn contains(&self, id: u64) -> bool {
96 match self {
97 Self::Live(calls) => calls.contains_key(&id),
98 Self::Failed(_) => false,
99 }
100 }
101}
102
103/// State the reader and writer tasks share.
104///
105/// Both hold this strongly, which is safe precisely because nothing in it can
106/// keep an API handle alive: everything a [`Client`] handle owns — pending
107/// calls, the id counter, the task handles, and the only strong sender into
108/// the writer — stays in [`Inner`], so dropping the last handle still closes
109/// the writer's channel and still shuts the connection down.
110struct Shared {
111 session: Arc<Session>,
112 /// Send-side trailer credit shared by every outgoing trailer on this
113 /// connection. Bounds what the peer must buffer for us in aggregate.
114 trailer_session: Arc<SessionWindow>,
115 /// Send-side payload quota shared by every outgoing request. Bounds the
116 /// postcard bytes the peer must hold for us across all live calls, and is
117 /// charged in full when a request is admitted to the scheduler.
118 ///
119 /// Kept apart from `trailer_session` on purpose; see [`crate::window`].
120 payload_budget: Arc<PayloadBudget>,
121 /// Calls whose request reached the scheduler and whose terminal message
122 /// has not yet arrived.
123 ///
124 /// Each task asks it a different question: the writer bounds admissions
125 /// by its size, and the reader treats it as the set of ids the peer is
126 /// entitled to respond to. Membership starts in `SendDriver::admit_request`
127 /// and ends wherever the terminal message is observed, which for
128 /// everything but a locally cancelled request is the reader.
129 active_calls: Mutex<HashSet<u64>>,
130 /// The route trailers and payload charges use to credit and discard
131 /// themselves. Weak, since a `TrailerRecv` or a held payload credit can
132 /// outlive the session and must not keep the writer alive just to say it
133 /// is going away.
134 sink: Arc<dyn ControlSink>,
135 #[cfg(windows)]
136 handle_escrow: Mutex<HashMap<u64, Vec<OwnedHandle>>>,
137 #[cfg(target_os = "macos")]
138 fd_escrow: Mutex<crate::escrow::FdEscrow>,
139 limits: Limits,
140}
141
142impl Shared {
143 /// Maximum handle attachments one message may carry.
144 fn max_handles(&self) -> usize {
145 // A transport configured to attach no handles to a fragment can
146 // carry none at all.
147 #[cfg(unix)]
148 if self.limits.max_handles_per_fragment == 0 {
149 return 0;
150 }
151 self.limits.max_handles_per_message
152 }
153
154 /// Finishes handle encoding for message `id`, taking custody of whatever
155 /// this platform must keep alive once the message is on the wire.
156 ///
157 /// On macOS that is the file descriptors themselves, escrowed until the
158 /// peer acknowledges receipt. Every other unix passes them with the
159 /// fragment and is done with them.
160 #[cfg(unix)]
161 fn finish_handles(&self, id: u64, handles: EncodeHandles) -> transport::OutgoingHandles {
162 let handles = handles.finish();
163 #[cfg(target_os = "macos")]
164 if handles.needs_ack() {
165 self.fd_escrow.lock().unwrap().register(id);
166 }
167 #[cfg(not(target_os = "macos"))]
168 let _ = id;
169 handles
170 }
171
172 /// Finishes handle encoding for message `id`, escrowing the originals of
173 /// the handles duplicated into the peer until the call is settled.
174 #[cfg(windows)]
175 fn finish_handles(&self, id: u64, handles: EncodeHandles) -> transport::OutgoingHandles {
176 let (handles, escrow) = handles.finish();
177 if !escrow.is_empty() {
178 self.handle_escrow.lock().unwrap().insert(id, escrow);
179 }
180 handles
181 }
182
183 /// Records the file descriptors for `id` that just reached the wire.
184 #[cfg(target_os = "macos")]
185 fn escrow_sent(&self, id: u64, fds: Vec<std::os::fd::OwnedFd>, done: bool) {
186 self.fd_escrow.lock().unwrap().sent(id, fds, done);
187 }
188
189 /// Forgets the escrow for a request that will never reach the wire.
190 fn discard_unsent_escrow(&self, id: u64) {
191 #[cfg(target_os = "macos")]
192 self.fd_escrow.lock().unwrap().discard_unsent(id);
193 #[cfg(not(target_os = "macos"))]
194 let _ = id;
195 }
196
197 /// Releases the escrow an `Ack` names, returning false when there is
198 /// none — which is every `Ack` on a platform that escrows nothing.
199 fn release_escrow(&self, id: u64) -> bool {
200 #[cfg(target_os = "macos")]
201 return self.fd_escrow.lock().unwrap().release(id);
202 #[cfg(not(target_os = "macos"))]
203 {
204 let _ = id;
205 false
206 }
207 }
208
209 /// Drops the handles escrowed for `id`. A no-op where the platform keeps
210 /// nothing back for the peer to duplicate.
211 fn drop_escrowed_handles(&self, id: u64) {
212 #[cfg(windows)]
213 self.handle_escrow.lock().unwrap().remove(&id);
214 #[cfg(not(windows))]
215 let _ = id;
216 }
217
218 /// Decodes a message payload, taking custody of every handle and opaque
219 /// reference it carries.
220 #[cfg(unix)]
221 fn decode<T: ::serde::de::DeserializeOwned>(
222 &self,
223 payload: &[u8],
224 handles: transport::ReceivedHandles,
225 ) -> Result<T, Error> {
226 decode_payload(
227 payload,
228 &mut SessionHandles {
229 inner: handles,
230 session: &self.session,
231 },
232 )
233 }
234
235 /// Decodes a message payload, taking custody of every handle and opaque
236 /// reference it carries. Windows handles arrive already duplicated into
237 /// this process, named by value in the payload rather than attached to
238 /// the fragment, so `handles` is empty.
239 #[cfg(windows)]
240 fn decode<T: ::serde::de::DeserializeOwned>(
241 &self,
242 payload: &[u8],
243 _handles: transport::ReceivedHandles,
244 ) -> Result<T, Error> {
245 decode_payload(
246 payload,
247 &mut DecodeHandles::new(self.max_handles(), &self.session),
248 )
249 }
250
251 /// Retires call `id` now that its terminal message has settled it: the
252 /// call slot goes back, and so does anything escrowed for its request.
253 fn finish_call(&self, id: u64) {
254 self.drop_escrowed_handles(id);
255 self.active_calls.lock().unwrap().remove(&id);
256 }
257}
258
259#[cfg(windows)]
260struct DecodeHandles<'a> {
261 consumed: HashSet<usize>,
262 count: usize,
263 max_handles: usize,
264 session: &'a Arc<Session>,
265}
266
267#[cfg(windows)]
268impl<'a> DecodeHandles<'a> {
269 fn new(max_handles: usize, session: &'a Arc<Session>) -> Self {
270 Self {
271 consumed: HashSet::new(),
272 count: 0,
273 max_handles,
274 session,
275 }
276 }
277}
278
279#[cfg(windows)]
280impl TakeHandle for DecodeHandles<'_> {
281 fn take_handle(&mut self, value: usize) -> io::Result<OwnedHandle> {
282 if !self.consumed.insert(value) {
283 return Err(io::Error::new(
284 io::ErrorKind::InvalidData,
285 "handle value was already consumed",
286 ));
287 }
288 self.count += 1;
289 // SAFETY: the trusted server created this value in our process with
290 // DuplicateHandle before transmitting it.
291 Ok(unsafe { OwnedHandle::from_raw_handle(value as _) })
292 }
293
294 fn finish(&mut self) -> io::Result<()> {
295 if self.count > self.max_handles {
296 return Err(io::Error::new(
297 io::ErrorKind::InvalidData,
298 "message contains too many handle attachments",
299 ));
300 }
301 Ok(())
302 }
303
304 fn take_gift(&mut self, owner: u8, id: u64) -> io::Result<OpaqueInner> {
305 self.session
306 .take_gift(owner, id)
307 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid opaque reference"))
308 }
309
310 fn take_cite(&mut self, owner: u8, id: u64, marker: TypeId) -> io::Result<OpaqueInner> {
311 self.session
312 .take_cite(owner, id, marker)
313 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid opaque reference"))
314 }
315}
316
317/// `(id, response receiver, cancel_sent)`, returned by `Client::begin`.
318type BeginResult<P> = (
319 u64,
320 oneshot::Receiver<Result<CallResult<<P as Protocol>::Response>, Error>>,
321 bool,
322);
323
324enum Outgoing<Q> {
325 Request {
326 id: u64,
327 value: Q,
328 trailer: Trailer,
329 },
330 Cancel {
331 id: u64,
332 },
333 /// We stopped reading a response trailer (it arrived unwanted) and want
334 /// to tell the peer to stop sending it. Always results in a wire
335 /// `Kind::Discard` fragment — this connection never has an active
336 /// outgoing send under a response id to abort locally instead.
337 DiscardTrailer {
338 id: u64,
339 },
340 /// A wire `Kind::Discard` fragment arrived, telling us the peer no
341 /// longer wants our request trailer. Applied to our own active send;
342 /// never re-sent to the peer.
343 PeerDiscarded {
344 id: u64,
345 },
346 Ack {
347 id: u64,
348 },
349 /// Drops `count` of this endpoint's references to the peer's opaque `id`.
350 Release {
351 id: u64,
352 count: u32,
353 },
354 /// We retired `count` bytes of the response trailer on `id` and are
355 /// returning that much credit. Always results in a wire `Kind::Credit`.
356 Credit {
357 id: u64,
358 count: u32,
359 },
360 /// A call released its request payload and is returning `count` bytes of
361 /// quota. Always results in a wire `Kind::PayloadCredit`, which names no
362 /// message: several of these coalesce into one fragment.
363 PayloadCredit {
364 count: u32,
365 },
366 /// A call ended, freeing a slot. The reader has already dropped the id
367 /// from [`ActiveCalls`] itself — it has to, so that a second `Response`
368 /// for the same id is rejected — so this carries nothing and exists only
369 /// to wake the writer to promote whatever was waiting on that slot.
370 Terminal,
371}
372
373impl<Q: Send + 'static> ReleaseSink for mpsc::WeakUnboundedSender<Outgoing<Q>> {
374 fn release(&self, id: u64, count: u32) {
375 // Called from `Drop`, so a departed channel is not an error: the
376 // writer is already gone and the peer's table dies with the session.
377 if let Some(outgoing) = self.upgrade() {
378 let _ = outgoing.send(Outgoing::Release { id, count });
379 }
380 }
381}
382
383impl<Q: Send + 'static> ControlSink for mpsc::WeakUnboundedSender<Outgoing<Q>> {
384 fn credit(&self, id: u64, count: u32) {
385 if let Some(outgoing) = self.upgrade() {
386 let _ = outgoing.send(Outgoing::Credit { id, count });
387 }
388 }
389
390 fn payload_credit(&self, count: u32) {
391 // Reached from `PayloadCharge::drop`, which runs on every path a call
392 // can end on — including ones where the session is already tearing
393 // down, where there is no one left to credit.
394 if let Some(outgoing) = self.upgrade() {
395 let _ = outgoing.send(Outgoing::PayloadCredit { count });
396 }
397 }
398
399 fn discard(&self, id: u64) {
400 // Reached from `TrailerRecv::drop`, so a departed channel just means
401 // the connection is already gone and the peer has nothing to stop.
402 if let Some(outgoing) = self.upgrade() {
403 let _ = outgoing.send(Outgoing::DiscardTrailer { id });
404 }
405 }
406}
407
408struct Inner<P: Protocol> {
409 // Holding a clone of this sender represents the ability to still get a
410 // message into the writer, so closing the channel — clearing this to
411 // `None` — is itself the writer's shutdown signal (see `SendDriver::run`):
412 // no separate oneshot needed. This is the only strong sender; the
413 // session's release sink holds a weak one so that it cannot keep the
414 // channel open past this point.
415 outgoing: Mutex<Option<mpsc::UnboundedSender<Outgoing<P::Request>>>>,
416 pending: Mutex<Pending<P::Response>>,
417 next_id: Mutex<u64>,
418 reader_shutdown: Mutex<Option<oneshot::Sender<()>>>,
419 tasks: Mutex<Option<Tasks>>,
420 requires_full_close: bool,
421 shared: Arc<Shared>,
422 #[cfg(windows)]
423 _peer_process: Option<OwnedHandle>,
424}
425
426/// Drives the send half of the connection: admits queued items into the
427/// fragment scheduler and advances the scheduler onto the transport.
428struct SendDriver<P: Protocol> {
429 transport: transport::AnySender,
430 outgoing: mpsc::UnboundedReceiver<Outgoing<P::Request>>,
431 /// Weak: the API handles decide when the session ends, and a writer that
432 /// held them alive would wait forever on a channel it kept open itself.
433 inner: Weak<Inner<P>>,
434 shared: Arc<Shared>,
435 scheduler: Scheduler,
436 /// Requests held back because `Shared::active_calls` is at the limit,
437 /// promoted in arrival order as slots free up.
438 waiting: VecDeque<Outgoing<P::Request>>,
439}
440
441/// Drives the receive half of the connection: reassembles inbound fragments
442/// and settles the calls they answer.
443struct RecvDriver<P: Protocol> {
444 transport: transport::AnyReceiver,
445 /// Weak, as on the writer.
446 inner: Weak<Inner<P>>,
447 shared: Arc<Shared>,
448}
449
450/// A driver task's completion, awaitable any number of times.
451///
452/// A `JoinHandle` can only be awaited once, and it is not `Clone`: `close`,
453/// `abort` and `Drop` all need to observe the same task ending, possibly from
454/// several client clones at once. `Shared` gives every awaiter its own handle
455/// on one join, and hands the polling on to another awaiter if the one
456/// currently driving it is dropped — which is exactly what happens when
457/// `abort` on one clone interrupts a `close` waiting on another.
458type Done = futures::future::Shared<BoxFuture<'static, ()>>;
459
460fn done(handle: tokio::task::JoinHandle<impl Send + 'static>) -> Done {
461 handle.map(|_| ()).boxed().shared()
462}
463
464#[derive(Clone)]
465struct Tasks {
466 writer_done: Done,
467 reader_done: Done,
468}
469
470impl Tasks {
471 async fn writer(&self) {
472 self.writer_done.clone().await;
473 }
474
475 async fn reader(&self) {
476 self.reader_done.clone().await;
477 }
478
479 async fn join(&self) {
480 tokio::join!(self.writer(), self.reader());
481 }
482}
483
484impl<P: Protocol> Drop for Inner<P> {
485 fn drop(&mut self) {
486 // Close the writer's channel first — see the comment on `outgoing`.
487 self.outgoing.lock().unwrap().take();
488 if let Some(shutdown) = self.reader_shutdown.get_mut().unwrap().take() {
489 let _ = shutdown.send(());
490 }
491 self.fail(Error::ConnectionClosed);
492 }
493}
494
495impl<P: Protocol> Inner<P> {
496 /// Best-effort send: silently dropped if the writer's channel has
497 /// already been closed.
498 fn send(&self, message: Outgoing<P::Request>) {
499 if let Some(sender) = self.outgoing.lock().unwrap().as_ref() {
500 let _ = sender.send(message);
501 }
502 }
503
504 fn complete(&self, id: u64, result: Result<CallResult<P::Response>, Error>) {
505 if let Some(tx) = self.pending.lock().unwrap().remove(id) {
506 let _ = tx.send(result);
507 }
508 }
509
510 /// Fails every waiting call and poisons the registry against later ones.
511 ///
512 /// The first error wins: it is the one that says why the session actually
513 /// ended, where anything after it is the teardown that followed.
514 fn fail(&self, error: Error) {
515 let waiting = {
516 let mut pending = self.pending.lock().unwrap();
517 match mem::replace(&mut *pending, Pending::Failed(error.copy())) {
518 Pending::Live(calls) => calls,
519 already @ Pending::Failed(_) => {
520 *pending = already;
521 return;
522 }
523 }
524 };
525 for (_, tx) in waiting {
526 let _ = tx.send(Err(error.copy()));
527 }
528 }
529}
530
531/// RPC client handle.
532pub struct Client<P: Protocol> {
533 inner: Arc<Inner<P>>,
534}
535
536impl<P: Protocol> Clone for Client<P> {
537 fn clone(&self) -> Self {
538 Self {
539 inner: self.inner.clone(),
540 }
541 }
542}
543
544impl<P: Protocol> Client<P> {
545 /// Returns whether both clients refer to the same RPC session.
546 pub fn is_same_session(&self, other: &Self) -> bool {
547 Arc::ptr_eq(&self.inner, &other.inner)
548 }
549
550 /// Builds a `Client` from an already-negotiated transport. Only reachable
551 /// via [`Unbound::bind`] — `Client` has
552 /// no public constructors of its own, so every `Client<P>` has already
553 /// completed `fragment::negotiate` by the time it exists.
554 pub(crate) fn from_transport(
555 sender: transport::AnySender,
556 receiver: transport::AnyReceiver,
557 limits: Limits,
558 #[cfg(windows)] peer_process: Option<OwnedHandle>,
559 ) -> Self {
560 let requires_full_close = sender.requires_full_close();
561 let (outgoing, outgoing_rx) = mpsc::unbounded_channel();
562 let session = Session::new(Box::new(outgoing.downgrade()));
563 let shared = Arc::new(Shared {
564 session,
565 trailer_session: Arc::new(SessionWindow::new(limits.trailer_session_window)),
566 payload_budget: Arc::new(PayloadBudget::new(limits.max_outstanding_payload)),
567 active_calls: Default::default(),
568 sink: Arc::new(outgoing.downgrade()),
569 #[cfg(windows)]
570 handle_escrow: Mutex::new(HashMap::new()),
571 #[cfg(target_os = "macos")]
572 fd_escrow: Mutex::new(Default::default()),
573 limits,
574 });
575 let inner = Arc::new(Inner {
576 outgoing: Mutex::new(Some(outgoing)),
577 pending: Mutex::new(Pending::new()),
578 next_id: Mutex::new(0),
579 reader_shutdown: Mutex::new(None),
580 tasks: Mutex::new(None),
581 requires_full_close,
582 shared: shared.clone(),
583 #[cfg(windows)]
584 _peer_process: peer_process,
585 });
586 let (reader_shutdown, reader_stop) = oneshot::channel();
587 let writer = tokio::spawn(
588 SendDriver {
589 transport: sender,
590 outgoing: outgoing_rx,
591 inner: Arc::downgrade(&inner),
592 shared: shared.clone(),
593 scheduler: Scheduler::new(&limits, shared.payload_budget.clone()),
594 waiting: VecDeque::new(),
595 }
596 .run(),
597 );
598 let reader = tokio::spawn(
599 RecvDriver {
600 transport: receiver,
601 inner: Arc::downgrade(&inner),
602 shared,
603 }
604 .run(reader_stop),
605 );
606 *inner.reader_shutdown.lock().unwrap() = Some(reader_shutdown);
607 *inner.tasks.lock().unwrap() = Some(Tasks {
608 writer_done: done(writer),
609 reader_done: done(reader),
610 });
611 Self { inner }
612 }
613
614 /// Gracefully closes the session.
615 ///
616 /// This prevents new calls from being sent and completes all pending calls
617 /// with [`Error::ConnectionClosed`]. It then drains committed writes,
618 /// closes the outgoing transport, and waits for the peer to close its
619 /// outgoing transport. It affects every clone of this client handle.
620 ///
621 /// This operation has no timeout. A peer that does not close its transport
622 /// can make it wait indefinitely; callers that need a bound should apply a
623 /// timeout and use [`Client::abort`] if the peer does not cooperate.
624 /// Windows named pipes do not support half-close, so after draining the
625 /// writer this closes the shared pipe instead of waiting for peer EOF.
626 pub async fn close(self) {
627 let tasks = self.inner.tasks.lock().unwrap().clone();
628 // Close the writer's channel first — see the comment on `outgoing`.
629 self.inner.outgoing.lock().unwrap().take();
630 self.inner.fail(Error::ConnectionClosed);
631 if let Some(tasks) = tasks {
632 // The reader must remain alive while the writer drains because it
633 // may still deliver the credit needed by a committed write. Once
634 // the writer exits, dropping its transport tells the peer that no
635 // more input is coming; natural EOF from the peer then ends the
636 // reader when the transport supports half-close.
637 tasks.writer().await;
638 if self.inner.requires_full_close
639 && let Some(shutdown) = self.inner.reader_shutdown.lock().unwrap().take()
640 {
641 // Windows named pipes have no write-side half-close. Both
642 // drivers share one pipe handle, so after the writer drains
643 // the reader must release its half to make the peer observe
644 // EOF. Keeping it alive through the drain still preserves
645 // flow-control progress and committed writes.
646 let _ = shutdown.send(());
647 }
648 tasks.reader().await;
649 }
650 // Keep the sender alive until after natural EOF. Besides documenting
651 // that intent in the ownership, this lets `abort` on another clone
652 // interrupt a `close` that is waiting for an uncooperative peer.
653 self.inner.reader_shutdown.lock().unwrap().take();
654 }
655
656 /// Abruptly closes the session.
657 ///
658 /// This prevents new calls from being sent, completes all pending calls
659 /// with [`Error::ConnectionClosed`], and stops the reader without waiting
660 /// for the peer to close its transport. Committed writes are allowed to
661 /// finish their current fragments before the writer exits. It affects
662 /// every clone of this client handle.
663 pub async fn abort(self) {
664 let tasks = self.inner.tasks.lock().unwrap().clone();
665 // Close the writer's channel before the reader, matching `Drop` and
666 // preserving the writer's committed-fragment drain.
667 self.inner.outgoing.lock().unwrap().take();
668 self.inner.fail(Error::ConnectionClosed);
669 if let Some(shutdown) = self.inner.reader_shutdown.lock().unwrap().take() {
670 let _ = shutdown.send(());
671 }
672 if let Some(tasks) = tasks {
673 tasks.join().await;
674 }
675 }
676
677 /// Issue a call request.
678 pub fn call(&self, request: P::Request) -> Call<P> {
679 let ((id, rx, cancel_sent), ()) = self.begin(|id| {
680 (
681 Outgoing::Request {
682 id,
683 value: request,
684 trailer: Trailer::None,
685 },
686 (),
687 )
688 });
689 Call {
690 id,
691 rx,
692 inner: self.inner.clone(),
693 cancel_sent,
694 }
695 }
696
697 /// Issue a call request with a byte trailer.
698 ///
699 /// Write the trailer through the returned [`TrailerSend`], then call
700 /// [`TrailerSend::finish`] to obtain the
701 /// [`Call`]. Dropping it sender without finishing aborts the trailer and
702 /// cancels the partially sent request.
703 pub fn call_with_trailer(&self, request: P::Request) -> TrailerSend<Call<P>> {
704 let ((id, rx, cancel_sent), shared) = self.begin(|id| {
705 let shared = SendShared::new(
706 Kind::Request,
707 id,
708 &self.inner.shared.limits,
709 self.inner.shared.trailer_session.clone(),
710 );
711 (
712 Outgoing::Request {
713 id,
714 value: request,
715 trailer: Trailer::Stream(shared.clone()),
716 },
717 shared,
718 )
719 });
720 if cancel_sent {
721 SendShared::discard(&shared);
722 }
723 TrailerSend::new(
724 shared,
725 Call {
726 id,
727 rx,
728 inner: self.inner.clone(),
729 cancel_sent,
730 },
731 )
732 }
733
734 /// Shared id-allocation/pending-registration logic for `call` and
735 /// `call_with_trailer`. `build` constructs the outgoing message once the
736 /// id is known. Returns the id, the response receiver, and whether a
737 /// cancel has effectively already been sent (nothing left to cancel).
738 fn begin<T>(
739 &self,
740 build: impl FnOnce(u64) -> (Outgoing<P::Request>, T),
741 ) -> (BeginResult<P>, T) {
742 let (tx, rx) = oneshot::channel();
743 let id = {
744 let mut next = self.inner.next_id.lock().unwrap();
745 let id = *next;
746 *next = id.checked_add(1).expect("request identifiers exhausted");
747 id
748 };
749 let (message, value) = build(id);
750 // Registration first, and under the lock that poisoning takes: a
751 // session that has already failed refuses the call here rather than
752 // letting it queue a request that no surviving reader will ever
753 // answer.
754 let registered = self.inner.pending.lock().unwrap().register(id, tx);
755 let queued = registered
756 && self
757 .inner
758 .outgoing
759 .lock()
760 .unwrap()
761 .as_ref()
762 .is_some_and(|sender| sender.send(message).is_ok());
763 if registered && !queued {
764 self.inner.complete(id, Err(Error::ConnectionClosed));
765 }
766 ((id, rx, !queued), value)
767 }
768}
769
770#[cfg(windows)]
771pub(crate) fn validate_peer_process(
772 peer_process: &OwnedHandle,
773 pipe_peer_pid: u32,
774) -> io::Result<()> {
775 let process_pid = unsafe { GetProcessId(peer_process.as_raw_handle() as _) };
776 if process_pid == 0 {
777 return Err(io::Error::last_os_error());
778 }
779 if process_pid != pipe_peer_pid {
780 return Err(io::Error::new(
781 io::ErrorKind::PermissionDenied,
782 "named-pipe peer does not match the expected process",
783 ));
784 }
785 Ok(())
786}
787
788/// A completed call's response and possible trailer.
789///
790/// Use [`into_response`](Self::into_response) when the trailer is not needed,
791/// or [`into_response_trailer`](Self::into_response_trailer) to retain it —
792/// or [`into_response_trailer_manual_credit`](Self::into_response_trailer_manual_credit)
793/// to retain it and take charge of returning its credit.
794pub struct CallResult<R> {
795 response: R,
796 trailer: Option<TrailerRecv>,
797 /// This response's share of the payload quota, released when this value
798 /// is decomposed or dropped — unless the caller took it out with
799 /// [`take_payload_credit`](Self::take_payload_credit) first.
800 charge: Option<PayloadCharge>,
801}
802
803/// A held share of the session payload quota.
804///
805/// Obtained from [`CallResult::take_payload_credit`] by a caller that wants
806/// the quota released later than the `CallResult` it came from — typically
807/// because the deserialized response is potentially large. Holding it
808/// prevents it being returned to the server's pool, limited by
809/// [`Builder::max_outstanding_payload()`](crate::Builder::max_outstanding_payload())
810pub struct PayloadCredit(Option<PayloadCharge>);
811
812impl PayloadCredit {
813 /// Returns the quota now. Identical to dropping this value, and offered
814 /// only so the intent can be stated where it happens.
815 pub fn release(self) {
816 drop(self.0);
817 }
818}
819
820impl<R> CallResult<R> {
821 /// Takes charge of releasing this call response's payload quota.
822 ///
823 /// Calling this more than once yields a token that releases nothing.
824 pub fn take_payload_credit(&mut self) -> PayloadCredit {
825 PayloadCredit(self.charge.take())
826 }
827
828 /// Discards any response trailer and returns just the response.
829 pub fn into_response(self) -> R {
830 self.response
831 }
832
833 /// Decomposes into the response and its readable trailer, if present.
834 pub fn into_response_trailer(self) -> (R, Option<TrailerRecv>) {
835 (self.response, self.trailer)
836 }
837
838 /// Decomposes into the response and its trailer in manual-credit mode.
839 ///
840 /// The consumer then owes the server an explicit
841 /// [`TrailerRecv::release`](crate::trailer::TrailerRecv::release) for
842 /// every chunk it finishes using, instead of credit being returned on
843 /// read.
844 pub fn into_response_trailer_manual_credit(self) -> (R, Option<TrailerRecv>) {
845 let mut trailer = self.trailer;
846 if let Some(trailer) = trailer.as_mut() {
847 trailer.set_manual_credit();
848 }
849 (self.response, trailer)
850 }
851}
852
853/// An in-progress RPC call.
854///
855/// Await this future to receive the response and its optional trailer, or an
856/// [`Error`]. Dropping it before completion sends best-effort cancellation to
857/// the peer.
858pub struct Call<P: Protocol> {
859 id: u64,
860 rx: oneshot::Receiver<Result<CallResult<P::Response>, Error>>,
861 inner: Arc<Inner<P>>,
862 cancel_sent: bool,
863}
864
865impl<P: Protocol> Call<P> {
866 /// Requests best-effort cancellation and leaves the call awaitable.
867 ///
868 /// Idempotent. A call that races with cancellation may still
869 /// complete successfully.
870 pub fn cancel(&mut self) {
871 if !self.cancel_sent {
872 self.cancel_sent = true;
873 self.inner.send(Outgoing::Cancel { id: self.id });
874 }
875 }
876}
877
878impl<P: Protocol> Future for Call<P> {
879 type Output = Result<CallResult<P::Response>, Error>;
880
881 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
882 match Pin::new(&mut self.rx).poll(cx) {
883 Poll::Ready(Ok(Ok(result))) => Poll::Ready(Ok(result)),
884 Poll::Ready(Ok(Err(e))) => Poll::Ready(Err(e)),
885 Poll::Ready(Err(_)) => Poll::Ready(Err(Error::ConnectionClosed)),
886 Poll::Pending => Poll::Pending,
887 }
888 }
889}
890
891impl<P: Protocol> Drop for Call<P> {
892 fn drop(&mut self) {
893 if self.inner.pending.lock().unwrap().remove(self.id).is_some() {
894 self.cancel();
895 }
896 }
897}
898
899impl<P: Protocol> SendDriver<P> {
900 /// Best-effort completion of a pending call with an error; a no-op if
901 /// the session is already gone. Also drops any handles escrowed until
902 /// the peer has had an opportunity to duplicate them.
903 fn complete_err(&self, id: u64, error: Error) {
904 // The escrow lives in `Shared`, which outlives the handles, so it is
905 // cleaned up whether or not anyone is still waiting for the result.
906 self.shared.drop_escrowed_handles(id);
907 if let Some(inner) = self.inner.upgrade() {
908 inner.complete(id, Err(error));
909 }
910 }
911
912 /// Admits one queued item into the scheduler. Returns `Err` on a fatal
913 /// transport/protocol error, which the caller must treat as fatal for
914 /// the whole session, not just this one message.
915 async fn admit(&mut self, message: Outgoing<P::Request>) -> Result<(), Error> {
916 match message {
917 Outgoing::Request { id, value, trailer } => {
918 self.admit_request(id, value, trailer).await
919 }
920 Outgoing::Cancel { id } => {
921 self.admit_cancel(id);
922 Ok(())
923 }
924 Outgoing::DiscardTrailer { id } => {
925 self.scheduler.admit_empty(Kind::Discard, id);
926 Ok(())
927 }
928 Outgoing::PeerDiscarded { id } => {
929 // The peer will never credit what it just threw away, so
930 // settle by id rather than through the send, which may
931 // already have finished and left the scheduler.
932 self.shared.trailer_session.settle(id);
933 self.scheduler.discard_active_trailer(id);
934 Ok(())
935 }
936 Outgoing::Ack { id } => {
937 self.scheduler.admit_empty(Kind::Ack, id);
938 Ok(())
939 }
940 Outgoing::Release { id, count } => {
941 self.scheduler.admit_release(id, count);
942 Ok(())
943 }
944 Outgoing::Credit { id, count } => {
945 self.scheduler.admit_credit(id, count);
946 Ok(())
947 }
948 Outgoing::PayloadCredit { count } => {
949 self.scheduler.admit_payload_credit(count);
950 Ok(())
951 }
952 Outgoing::Terminal => unreachable!("handled by the writer loop"),
953 }
954 }
955
956 async fn admit_request(
957 &mut self,
958 id: u64,
959 value: P::Request,
960 trailer: Trailer,
961 ) -> Result<(), Error> {
962 // Every handle is gone, so nothing is waiting on this call's result
963 // and its pending entry went with them: drop it rather than encode it.
964 if self.inner.upgrade().is_none() {
965 return Ok(());
966 }
967 let mut ledger = Ledger::default();
968 let mut put_handles = SessionFrame {
969 inner: EncodeHandles::new(&self.transport, self.shared.max_handles()),
970 session: &self.shared.session,
971 ledger: &mut ledger,
972 };
973 let payload = match encode_payload(&value, &mut put_handles) {
974 Ok(payload) => payload,
975 Err(err) => {
976 // The ledger drops here without committing. Nothing of this
977 // message reached the wire, so any gift it named is rescinded
978 // by that drop path, not by a commit.
979 drop(put_handles);
980 ledger.rescind();
981 self.complete_err(id, err);
982 return Ok(());
983 }
984 };
985 let handles = self.shared.finish_handles(id, put_handles.inner);
986 self.scheduler
987 .admit_message(Kind::Request, id, payload, handles, trailer, ledger);
988 // The call takes its slot here, where its request actually reaches
989 // the scheduler — never on the way in. A request that fails to encode
990 // is completed locally above and never goes out, so no response and
991 // no `Terminal` will ever come back to release a slot taken earlier.
992 self.shared.active_calls.lock().unwrap().insert(id);
993 Ok(())
994 }
995
996 fn admit_cancel(&mut self, id: u64) -> bool {
997 match self.scheduler.try_cancel_active(id) {
998 AbortOutcome::NotActive => {
999 self.scheduler.admit_empty(Kind::Cancel, id);
1000 false
1001 }
1002 AbortOutcome::Discarded {
1003 started,
1004 dispatched,
1005 } => {
1006 if started {
1007 self.scheduler.admit_abort(id);
1008 }
1009 if !started {
1010 self.shared.discard_unsent_escrow(id);
1011 }
1012 if dispatched {
1013 self.scheduler.admit_empty(Kind::Cancel, id);
1014 false
1015 } else {
1016 self.complete_err(id, Error::Cancelled);
1017 true
1018 }
1019 }
1020 }
1021 }
1022
1023 async fn promote_waiting(&mut self) -> Result<(), Error> {
1024 while self.shared.active_calls.lock().unwrap().len()
1025 < self.shared.limits.max_concurrent_calls
1026 {
1027 let Some(message) = self.waiting.pop_front() else {
1028 break;
1029 };
1030 let Outgoing::Request { id, .. } = &message else {
1031 unreachable!("only requests wait for call admission")
1032 };
1033 let id = *id;
1034 let Some(inner) = self.inner.upgrade() else {
1035 break;
1036 };
1037 if !inner.pending.lock().unwrap().contains(id) {
1038 continue;
1039 }
1040 self.admit(message).await?;
1041 }
1042 Ok(())
1043 }
1044
1045 async fn run(mut self) -> Result<(), Error> {
1046 // Holding a clone of `Inner::outgoing` is what represents the
1047 // ability to still get a message in (see its doc comment), so the
1048 // channel closing — every clone gone — doubles as the shutdown
1049 // signal: once `recv()` reports no more messages will ever arrive,
1050 // admission of new work stops, and the loop keeps advancing the
1051 // scheduler until it's fully drained before exiting, never
1052 // abandoning a write already committed to it.
1053 //
1054 // "Fully drained" here means `has_work`, not `has_pending`: shutdown
1055 // finishes writes already committed to the wire, but abandons work
1056 // still parked on quota or concurrency admission. `Client::close`
1057 // keeps the reader alive while this happens, so credit can still
1058 // arrive for committed writes; `Client::abort` and dropping the last
1059 // handle stop it. Widening this condition would make abrupt shutdown
1060 // wait for credit that can no longer arrive.
1061 let mut closed = false;
1062 while !closed || self.scheduler.has_work() {
1063 tokio::select! {
1064 message = self.outgoing.recv(), if !closed => {
1065 let Some(message) = message else {
1066 closed = true;
1067 self.waiting.clear();
1068 continue;
1069 };
1070 // No blanket `fail_all` here: `admit` already fails just
1071 // the one call whose request it couldn't get onto the
1072 // transport (see `admit_request`). Every other pending
1073 // call's request either already made it out, or is still
1074 // queued for a later turn of this same loop — a write
1075 // failure on one message doesn't mean every other one is
1076 // doomed, only that this connection is. The reader is
1077 // what authoritatively decides that (see the comment
1078 // after this loop).
1079 match message {
1080 request @ Outgoing::Request { .. } => self.waiting.push_back(request),
1081 Outgoing::Cancel { id } => {
1082 if let Some(pos) = self.waiting.iter().position(
1083 |message| matches!(message, Outgoing::Request { id: waiting_id, .. } if *waiting_id == id),
1084 ) {
1085 self.waiting.remove(pos);
1086 self.complete_err(id, Error::Cancelled);
1087 } else if self.shared.active_calls.lock().unwrap().contains(&id)
1088 && self.admit_cancel(id)
1089 {
1090 // Cancelled before its request ever reached
1091 // the wire, so no terminal message is coming
1092 // and the reader will never free this slot.
1093 self.shared.active_calls.lock().unwrap().remove(&id);
1094 }
1095 }
1096 // The slot is already free; this only prompts the
1097 // promotion below.
1098 Outgoing::Terminal => {}
1099 message => self.admit(message).await?,
1100 }
1101 self.promote_waiting().await?;
1102 }
1103 // Not raced against anything: once ready, a fragment write
1104 // is committed to the scheduler and must run to completion.
1105 // A dropped send future could otherwise leave a committed
1106 // partial fragment on the transport, or — on transports
1107 // whose writes are dispatched to a detached background task
1108 // (e.g. the blocking-pool-backed Windows pipe transport) —
1109 // let an abandoned write complete arbitrarily later,
1110 // potentially after the peer has already torn down its end.
1111 _ = self.scheduler.ready(), if self.scheduler.has_pending() => {
1112 let result = self.scheduler.advance(&mut self.transport).await;
1113 // Flush anything sent by the scheduler
1114 let _ = self.transport.flush().await;
1115 match result {
1116 // A streaming trailer producer was dropped mid-message.
1117 Ok(fragment::AdvanceOutcome::Aborted(id)) => {
1118 // The postcard payload has already reached the peer
1119 // before a streaming trailer can abort. Cancel the
1120 // dispatched handler and retain its call slot until
1121 // the resulting terminal message arrives.
1122 self.scheduler.admit_empty(Kind::Cancel, id);
1123 }
1124 Ok(fragment::AdvanceOutcome::None) => {}
1125 #[cfg(target_os = "macos")]
1126 Ok(fragment::AdvanceOutcome::Escrow { id, fds, handles_done }) => {
1127 self.shared.escrow_sent(id, fds, handles_done);
1128 }
1129 // No blanket `fail_all`: a write failure here means
1130 // this connection is broken, not that every pending
1131 // call's already-sent request was never delivered.
1132 // The reader observes the same broken connection
1133 // (see the comment after this loop) and is what
1134 // authoritatively fails pending calls.
1135 Err(err) => {
1136 return Err(err);
1137 }
1138 }
1139 }
1140 }
1141 }
1142 self.transport.shutdown().await?;
1143 Ok(())
1144 }
1145}
1146
1147impl<P: Protocol> RecvDriver<P> {
1148 /// Applies a reassembled message.
1149 ///
1150 /// Everything that belongs to `Shared` — the call slot, the handle
1151 /// escrows — is settled unconditionally; only handing the result to a
1152 /// waiting caller needs the handle side to still be alive, so the
1153 /// upgrade is taken per arm rather than up front.
1154 fn dispatch(&self, message: Message) -> Result<(), Error> {
1155 let Message {
1156 kind,
1157 id,
1158 payload,
1159 handles,
1160 trailer,
1161 charge,
1162 } = message;
1163 match kind {
1164 Kind::Response => {
1165 // Decoding happens here, in the reader task, *before*
1166 // `complete` discovers whether anyone still wants this
1167 // response. Decoding is what mirrors any opaque the payload
1168 // carries; the resulting `CallResult` is then dropped
1169 // normally when the call was cancelled, which releases those
1170 // references back to the peer. Skipping the decode for a
1171 // response nobody is waiting on would look like an
1172 // optimization and would silently leak every handle in it.
1173 let response = self.shared.decode(&payload, handles)?;
1174 let trailer = trailer.map(TrailerRecv::new);
1175 self.shared.finish_call(id);
1176 if let Some(inner) = self.inner.upgrade() {
1177 inner.complete(
1178 id,
1179 Ok(CallResult {
1180 response,
1181 trailer,
1182 charge: Some(charge),
1183 }),
1184 );
1185 inner.send(Outgoing::Terminal);
1186 }
1187 }
1188 Kind::Error => {
1189 self.shared.finish_call(id);
1190 if let Some(inner) = self.inner.upgrade() {
1191 inner.complete(id, Err(Error::Cancelled));
1192 inner.send(Outgoing::Terminal);
1193 }
1194 }
1195 Kind::Ack => {
1196 if !self.shared.release_escrow(id) {
1197 return Err(Error::Protocol(format!(
1198 "Ack for request {id} with no active escrow"
1199 )));
1200 }
1201 }
1202 Kind::Discard => {
1203 if let Some(inner) = self.inner.upgrade() {
1204 inner.send(Outgoing::PeerDiscarded { id });
1205 }
1206 }
1207 kind => return Err(Error::Protocol(format!("unexpected {kind:?} frame"))),
1208 }
1209 Ok(())
1210 }
1211
1212 /// Refuses a fragment the peer had no business sending, before the
1213 /// reassembler can allocate anything for it.
1214 ///
1215 /// Only the first fragment of a message is checked: a later one names a
1216 /// message this end already accepted, and the reassembler rejects an id it
1217 /// has no entry for.
1218 fn check_header(
1219 active_calls: &Mutex<HashSet<u64>>,
1220 header: &FragmentHeader,
1221 ) -> Result<(), Error> {
1222 match header.kind {
1223 // A server cannot originate a call, so this is never admissible in
1224 // this direction, whatever id it names.
1225 Kind::Request => Err(Error::Protocol("client received a Request fragment".into())),
1226 // Ids are minted by this end, so an id that names no live call is
1227 // fabricated. Tolerating it would let a peer open unbounded
1228 // reassembly buffers for calls that were never made.
1229 Kind::Response
1230 if header.flags.contains(Flags::FIRST)
1231 && !active_calls.lock().unwrap().contains(&header.id) =>
1232 {
1233 Err(Error::Protocol(format!(
1234 "Response for message {} with no call outstanding",
1235 header.id
1236 )))
1237 }
1238 _ => Ok(()),
1239 }
1240 }
1241
1242 async fn run(mut self, mut shutdown: oneshot::Receiver<()>) {
1243 let mut reassembler = Reassembler::new(self.shared.limits, self.shared.sink.clone());
1244 loop {
1245 let mut frame = self.transport.recv();
1246 let header = tokio::select! {
1247 header = fragment::read_fragment_header(&mut frame) => header,
1248 _ = &mut shutdown => return,
1249 };
1250 let header = match header {
1251 Ok(header) => header,
1252 Err(error) => {
1253 fail(&self.inner, error);
1254 return;
1255 }
1256 };
1257 if let Err(error) = Self::check_header(&self.shared.active_calls, &header) {
1258 fail(&self.inner, error);
1259 return;
1260 }
1261 let accepted = tokio::select! {
1262 accepted = reassembler.accept(header, &mut frame) => accepted,
1263 _ = &mut shutdown => return,
1264 };
1265 let complete = match accepted {
1266 Ok(complete) => complete,
1267 Err(error) => {
1268 fail(&self.inner, error);
1269 return;
1270 }
1271 };
1272 match complete {
1273 Event::None => {}
1274 // Nothing to admit: a client answers no calls, and the header
1275 // gate already refused every fragment that does not belong to
1276 // one this end made. So the reassembler holds at most one
1277 // entry per live call, and `max_concurrent_calls` bounds
1278 // those where they are issued.
1279 Event::PayloadIncomplete { .. } => {}
1280 Event::Aborted {
1281 kind,
1282 id,
1283 dispatched,
1284 } => {
1285 // The header gate already refused every kind a client
1286 // may not receive, and the reassembler only ever aborts a
1287 // message it opened.
1288 debug_assert_eq!(kind, Kind::Response, "aborted a non-response message");
1289 if !dispatched {
1290 self.shared.finish_call(id);
1291 if let Some(inner) = self.inner.upgrade() {
1292 inner.complete(id, Err(Error::Cancelled));
1293 inner.send(Outgoing::Terminal);
1294 }
1295 }
1296 }
1297 Event::Message(message) => {
1298 if let Err(error) = self.dispatch(message) {
1299 fail(&self.inner, error);
1300 return;
1301 }
1302 }
1303 Event::Ack { id, message } => {
1304 if let Some(inner) = self.inner.upgrade() {
1305 inner.send(Outgoing::Ack { id });
1306 }
1307 if let Some(message) = message
1308 && let Err(error) = self.dispatch(message)
1309 {
1310 fail(&self.inner, error);
1311 return;
1312 }
1313 }
1314 Event::Trailer { shared, len, .. } => {
1315 let frame = self.transport.recv();
1316 // SAFETY: the lease retains the receiver borrow and
1317 // clears the erased token before it ends.
1318 let lease = unsafe { RecvShared::grant(&shared, frame, len) };
1319 if let Err(error) = RecvShared::wait_fragment(&shared).await {
1320 fail(&self.inner, error.into());
1321 return;
1322 }
1323 lease.complete();
1324 }
1325 Event::Release { id, count } => self.shared.session.release(id, count),
1326 Event::Credit { id, count } => {
1327 // Applied here rather than routed through the writer:
1328 // the pool is shared state on `Shared`, and the refund
1329 // needs nothing the writer owns. It is keyed by id and
1330 // lands whether or not the send still exists, since a
1331 // trailer's last credits routinely arrive after it has
1332 // finished and left the scheduler, and dropping those
1333 // would shrink the pool on every transfer.
1334 self.shared.trailer_session.refund(id, count as usize);
1335 }
1336 Event::PayloadCredit { count } => {
1337 // Applied here for the same reason as trailer credit, and
1338 // with the same indifference to whether the send it pays
1339 // for still exists: the pool lives on `Shared`, and
1340 // crediting it wakes the writer parked on it. Nothing is
1341 // keyed by id, so nothing has to still be around.
1342 self.shared.payload_budget.credit(count as usize);
1343 }
1344 }
1345 }
1346 }
1347}
1348
1349/// Fails every pending call. Takes `inner` by reference (rather than a
1350/// `RecvDriver` method borrowing `&self`) so it can be called while another
1351/// field (e.g. a `RecvFrame` token borrowing `self.transport`) is still
1352/// mutably borrowed.
1353fn fail<P: Protocol>(inner: &Weak<Inner<P>>, error: Error) {
1354 if let Some(inner) = inner.upgrade() {
1355 inner.fail(error);
1356 }
1357}
1358
1359#[cfg(test)]
1360mod tests {
1361 use super::*;
1362
1363 struct Test;
1364
1365 impl Protocol for Test {
1366 type Request = u8;
1367 type Response = u8;
1368 }
1369
1370 fn header(kind: Kind, flags: Flags, id: u64) -> FragmentHeader {
1371 FragmentHeader {
1372 flags,
1373 kind,
1374 id,
1375 payload_len: 0,
1376 }
1377 }
1378
1379 /// The gate in front of the reassembler: a client answers calls it made
1380 /// and nothing else.
1381 #[test]
1382 fn header_gate_refuses_requests_and_responses_to_calls_never_made() {
1383 let active = Mutex::new(HashSet::from([7u64]));
1384 let check = |header| RecvDriver::<Test>::check_header(&active, &header);
1385
1386 assert!(matches!(
1387 check(header(Kind::Request, Flags::FIRST | Flags::LAST, 7)),
1388 Err(Error::Protocol(_)),
1389 ));
1390 assert!(matches!(
1391 check(header(Kind::Response, Flags::FIRST, 9)),
1392 Err(Error::Protocol(_)),
1393 ));
1394 assert!(check(header(Kind::Response, Flags::FIRST, 7)).is_ok());
1395 // A continuation names a message this end already accepted, so the
1396 // reassembler owns rejecting an id it has no entry for.
1397 assert!(check(header(Kind::Response, Flags::LAST, 9)).is_ok());
1398 // Trailer credit and opaque releases outlive the call they name, so
1399 // control frames are not gated on it — and payload credit names no
1400 // call at all.
1401 assert!(check(header(Kind::Credit, Flags::FIRST | Flags::LAST, 9)).is_ok());
1402 assert!(check(header(Kind::PayloadCredit, Flags::FIRST | Flags::LAST, 0)).is_ok());
1403 }
1404
1405 fn test_shared(outgoing: &mpsc::UnboundedSender<Outgoing<u8>>) -> Arc<Shared> {
1406 Arc::new(Shared {
1407 session: Session::new(Box::new(outgoing.downgrade())),
1408 trailer_session: Arc::new(SessionWindow::new(Limits::default().trailer_session_window)),
1409 payload_budget: Arc::new(PayloadBudget::new(
1410 Limits::default().max_outstanding_payload,
1411 )),
1412 active_calls: Default::default(),
1413 sink: Arc::new(outgoing.downgrade()),
1414 #[cfg(windows)]
1415 handle_escrow: Mutex::new(HashMap::new()),
1416 #[cfg(target_os = "macos")]
1417 fd_escrow: Mutex::new(Default::default()),
1418 limits: Limits::default(),
1419 })
1420 }
1421
1422 fn pending_call() -> (Call<Test>, mpsc::UnboundedReceiver<Outgoing<u8>>) {
1423 let (outgoing, outgoing_rx) = mpsc::unbounded_channel();
1424 let inner = Arc::new(Inner {
1425 outgoing: Mutex::new(Some(outgoing.clone())),
1426 pending: Mutex::new(Pending::new()),
1427 next_id: Mutex::new(1),
1428 reader_shutdown: Mutex::new(None),
1429 tasks: Mutex::new(None),
1430 requires_full_close: false,
1431 shared: test_shared(&outgoing),
1432 #[cfg(windows)]
1433 _peer_process: None,
1434 });
1435 let (tx, rx) = oneshot::channel();
1436 assert!(inner.pending.lock().unwrap().register(0, tx));
1437 (
1438 Call {
1439 id: 0,
1440 rx,
1441 inner,
1442 cancel_sent: false,
1443 },
1444 outgoing_rx,
1445 )
1446 }
1447
1448 #[tokio::test]
1449 async fn completed_call_does_not_send_cancel_when_dropped() {
1450 let (call, mut outgoing) = pending_call();
1451 let inner = call.inner.clone();
1452 call.inner.complete(
1453 call.id,
1454 Ok(CallResult {
1455 response: 7,
1456 trailer: None,
1457 charge: None,
1458 }),
1459 );
1460 assert_eq!(call.await.unwrap().into_response(), 7);
1461 assert!(matches!(
1462 outgoing.try_recv(),
1463 Err(mpsc::error::TryRecvError::Empty)
1464 ));
1465 drop(inner);
1466 }
1467
1468 #[test]
1469 fn dropped_pending_call_sends_cancel() {
1470 let (call, mut outgoing) = pending_call();
1471 drop(call);
1472 assert!(matches!(
1473 outgoing.try_recv(),
1474 Ok(Outgoing::Cancel { id: 0 })
1475 ));
1476 }
1477
1478 #[cfg(windows)]
1479 #[tokio::test]
1480 async fn complete_err_clears_handle_escrow() {
1481 let (outgoing, _outgoing_rx) = mpsc::unbounded_channel();
1482 let shared = test_shared(&outgoing);
1483 let inner = Arc::new(Inner {
1484 outgoing: Mutex::new(Some(outgoing.clone())),
1485 pending: Mutex::new(Pending::new()),
1486 next_id: Mutex::new(1),
1487 reader_shutdown: Mutex::new(None),
1488 tasks: Mutex::new(None),
1489 requires_full_close: true,
1490 shared: shared.clone(),
1491 _peer_process: None,
1492 });
1493 let (tx, _rx) = oneshot::channel();
1494 assert!(inner.pending.lock().unwrap().register(0, tx));
1495 shared.handle_escrow.lock().unwrap().insert(0, Vec::new());
1496 shared.handle_escrow.lock().unwrap().insert(1, Vec::new());
1497
1498 let (dummy_write, _unused) = tokio::io::duplex(64);
1499 let (sender, _unused) = transport::generic_duplex(dummy_write);
1500 let (_unused_tx, outgoing_rx) = mpsc::unbounded_channel();
1501 let writer = SendDriver::<Test> {
1502 transport: transport::AnySender::Generic(sender),
1503 outgoing: outgoing_rx,
1504 inner: Arc::downgrade(&inner),
1505 shared: shared.clone(),
1506 scheduler: Scheduler::new(
1507 &Limits::default(),
1508 Arc::new(PayloadBudget::new(
1509 Limits::default().max_outstanding_payload,
1510 )),
1511 ),
1512 waiting: VecDeque::new(),
1513 };
1514
1515 writer.complete_err(0, Error::Cancelled);
1516 assert!(!shared.handle_escrow.lock().unwrap().contains_key(&0));
1517
1518 // The escrow outlives the handle side, so it is still cleaned up
1519 // once nothing is left to deliver the error to.
1520 drop(inner);
1521 writer.complete_err(1, Error::Cancelled);
1522 assert!(shared.handle_escrow.lock().unwrap().is_empty());
1523 }
1524}
1525
1526#[cfg(all(test, windows))]
1527mod windows_tests {
1528 use std::os::windows::io::FromRawHandle;
1529
1530 use windows_sys::Win32::System::Threading::{
1531 GetCurrentProcessId, OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE,
1532 };
1533
1534 use super::*;
1535
1536 fn current_process_handle() -> OwnedHandle {
1537 let handle = unsafe {
1538 OpenProcess(
1539 PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_SYNCHRONIZE,
1540 0,
1541 GetCurrentProcessId(),
1542 )
1543 };
1544 assert!(!handle.is_null());
1545 unsafe { OwnedHandle::from_raw_handle(handle as _) }
1546 }
1547
1548 #[test]
1549 fn validates_named_pipe_peer_process() {
1550 let process = current_process_handle();
1551 let pid = unsafe { GetCurrentProcessId() };
1552 validate_peer_process(&process, pid).unwrap();
1553 assert_eq!(
1554 validate_peer_process(&process, !pid).unwrap_err().kind(),
1555 io::ErrorKind::PermissionDenied
1556 );
1557 }
1558}