aboutsummaryrefslogtreecommitdiff
path: root/crates/arti-rpc-client-core/src/conn/connimpl.rs
blob: a92d735bae94f51ab966f3c30a96836a1bc1e6e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
//! Implementation logic for RpcConn.
//!
//! Except for [`RpcConn`] itself, nothing in this module is a public API.
//! This module exists so that we can more easily audit the code that
//! touches the members of `RpcConn`.
//!
//! NOTE that many of the types and fields here have documented invariants.
//! Except if noted otherwise, these invariants only hold when nobody
//! is holding the lock on [`RequestState`].
//!
//! # Overview
//!
//! Each connection supports both:
//!  - requests that the caller will block on (a Waitable request)
//!  - requests that the caller will poll for (a Pollable request).
//!
//! ## Identifying requests
//!
//! Each request has a corresponding value of a type that implements QueueId
//! to identify which queue responses for the request should go into.
//!
//! - Waitable requests have [`AnyRequestId`]. which implements [`QueueId`]
//! - Pollable requests have [`PolledRequests`], a ZST that implements `QueueId``
//!
//! (Requests themselves all have an [`AnyRequestId`] --
//! the actual ID that we send out in the request,
//! which the RPC server sends back in all responses.
//! Additionally, Pollable requests are created with a client-defined [`UserTag`],
//! which the client can use to identify their particular requests.
//! `UserTag is a separate type to help FFI-style programs
//! that want to put things like pointers in it.)
//!
//! # Data structure
//!
//! The connection has
//!   - an outbound queue for outbound messages, in its [`BlockingConnection`].
//!   - [`RequestMap`], a data structure containing outstanding requests,
//!     which is used for knowing what to do with inbound messages
//!
//! If the request is Waitable,
//! its `RequestMap.map` entry is [`RequestState::Waiting`],
//! and contains its own [`ResponseQueue`].
//!
//! If the request is Pollable,
//! its `RequestMap` entry is [`RequestState::Pollable`],
//! and contains the Tag that the application will use
//! to distinguish responses ot that request.
//! All responses to _all_ Pollable requests
//! are queued within `RequestMap::polled_response_queue`.
//!
//! # Operation
//!
//! When we make a request, we add an entry to the `RequestMap::map`.
//! The entry stays there until we receive a final response to the request.
//!
//! At any given time,
//! multiple threads can be waiting for responses on the same RpcConn object.
//! If [`RpcPoll`] is not in use,
//! exactly of these threads will actually be holding the [`BlockingConnection`]
//! and trying to read from the network.
//! (Otherwise (if [`RpcPoll`] is in use), the `RpcPoll` object will be holding the
//! [`NonblockingConnection`] and will be responsible for all reads and writes.)
//!
//! If it finds a response for itself, it returns that response.
//! Otherwise, it puts the response in the appropriate queue,
//! and signal's the condvar associated with that queue.
//!
//! There are two kinds of queue:
//! A per-request queue used by Waitable requests,
//! and a single queue shared by all Polled requests.
//! Every queue has its  own associated condvar.
//!
//! The two kinds of queue are slightly different.
//! (We represent their differences with the QueueId trait):
//!     - Pollable responses need to carry a `UserTag`;
//!       Waitable responses don't. This is [`QueueId::UserTag`].
//!     - We need to treat final responses a bit differently
//!       in terms of how we find what to remove.
//!       This is [`QueueId::remove_entry`].
//!     - If we're holding the connection and waiting for responses on a given queue,
//!       we need to answer the "is this for us?" question a little differently.
//!       This is [`QueueId::response_disposition`]`.

use std::{
    collections::{HashMap, VecDeque},
    sync::{Arc, Condvar, Mutex, MutexGuard},
};

use crate::{
    UserTag,
    conn::AnyResponse,
    ll_conn::{BlockingConnection, NonblockingConnection},
    msgs::{
        AnyRequestId, ObjectId,
        request::{IdGenerator, ValidatedRequest},
        response::ValidatedResponse,
    },
};

use super::{ProtoError, ShutdownError};

/// An identifier for a [`ResponseQueue`] within a [`RequestMap`].
trait QueueId {
    /// A tag type associated with responses in the identified queue.
    ///
    /// ("Polling" requests use [`UserTag`]s to tell the user
    /// which response goes with which request.)
    type UserTag: Sized;

    /// Find the queue identified by this `QueueId` within `map`,
    /// in order to wait for messages on it.
    fn get_queue_mut<'a>(
        &self,
        map: &'a mut RequestMap,
    ) -> Result<&'a mut ResponseQueue<Self>, ProtoError>;

    /// Given that we are polling on the queue identified by `self`,
    /// determine what we should do with `msg`.
    ///
    /// (Should we return it, drop it, or forward it to somebody else?)
    ///
    /// This is used by the core waiting code in `read_until_message_for`,
    /// which needs to be able to handle any incoming response,
    /// even one which is for a different context / different caller,
    /// and reroute the message to the appropriate place.
    fn response_disposition<'a>(
        &self,
        map: &'a mut RequestMap,
        msg: &ValidatedResponse,
    ) -> ResponseDisposition<'a, Self>;

    /// Remove any state from `map` associated with `msg_id`.
    ///
    /// (If `msg_id` is absent, an error occurred that was not associated with any message ID.)
    fn remove_entry<'a>(&self, map: &'a mut RequestMap, msg_id: Option<&AnyRequestId>);

    /// Create and return a new RequestState to track a request associated with this kind of ID.
    fn new_entry(tag: Self::UserTag) -> RequestState;
}

impl QueueId for AnyRequestId {
    type UserTag = ();

    fn get_queue_mut<'a>(
        &self,
        map: &'a mut RequestMap,
    ) -> Result<&'a mut ResponseQueue<Self>, ProtoError> {
        match map.map.get_mut(self) {
            Some(RequestState::Waiting(s)) => Ok(s),
            Some(RequestState::Pollable(_)) => Err(ProtoError::RequestNotWaitable),
            None => Err(ProtoError::RequestCompleted),
        }
    }

    fn response_disposition<'a>(
        &self,
        map: &'a mut RequestMap,
        msg: &ValidatedResponse,
    ) -> ResponseDisposition<'a, Self> {
        if self == msg.id() {
            // This message is for us; no reason to look anything up.
            return ResponseDisposition::Return(());
        }

        match map.map.get_mut(msg.id()) {
            Some(RequestState::Waiting(q)) => ResponseDisposition::ForwardWaiting(q),
            Some(RequestState::Pollable(tag)) => {
                ResponseDisposition::ForwardPollable(*tag, &mut map.polled_response_queue)
            }
            None => ResponseDisposition::Ignore,
        }
    }

    fn remove_entry<'a>(&self, map: &'a mut RequestMap, _: Option<&AnyRequestId>) {
        map.map.remove(self);
    }

    /// Create and return a new RequestState to track a request associated with this kind of ID.
    fn new_entry(_: Self::UserTag) -> RequestState {
        RequestState::Waiting(ResponseQueue::default())
    }
}

/// Identifier for the set of "Pollable" requests.
///
/// As distinct from "Waitable" requests, which are created with "execute*" methods and
/// whose APIs expect the user to block while waiting for responses,
/// polled requests are created with "submit*" methods,
/// and their replies are returned, along with [`UserTag`] instances,
/// from the RpcConn directly.
struct PolledRequests;

impl QueueId for PolledRequests {
    type UserTag = UserTag;

    fn get_queue_mut<'a>(
        &self,
        map: &'a mut RequestMap,
    ) -> Result<&'a mut ResponseQueue<Self>, ProtoError> {
        Ok(&mut map.polled_response_queue)
    }

    fn response_disposition<'a>(
        &self,
        map: &'a mut RequestMap,
        msg: &ValidatedResponse,
    ) -> ResponseDisposition<'a, Self> {
        match map.map.get_mut(msg.id()) {
            Some(RequestState::Waiting(s)) => ResponseDisposition::ForwardWaiting(s),
            Some(RequestState::Pollable(tag)) => ResponseDisposition::Return(*tag),
            None => ResponseDisposition::Ignore,
        }
    }

    fn remove_entry<'a>(&self, map: &'a mut RequestMap, msg_id: Option<&AnyRequestId>) {
        let Some(msg_id) = msg_id else {
            // This can only happen when we have an error that wasn't associated with a message ID.
            // We can't actually remove the appropriate thing.
            return;
        };

        map.map.remove(msg_id);
    }

    fn new_entry(tag: Self::UserTag) -> RequestState {
        RequestState::Pollable(tag)
    }
}

/// A queue of responses used to alert a polling function about replies to
/// one or more requests.
#[derive(educe::Educe)]
#[educe(Default)]
struct ResponseQueue<Q: QueueId + ?Sized> {
    /// A queue of replies received with this request's identity.
    queue: VecDeque<(Q::UserTag, ValidatedResponse)>,
    /// A condition variable used to wake a thread waiting for this request
    /// to have messages.
    ///
    /// We `notify` this condvar thread under one of three circumstances:
    ///
    /// * When we queue a response for this request.
    /// * When we store a fatal error affecting all requests in the RpcConn.
    /// * When the thread currently interacting with he [`BlockingConnection`] for this
    ///   RpcConn stops doing so, and the request waiting
    ///   on this thread has been chosen to take responsibility for interacting.
    ///
    /// Invariants:
    /// * The condvar is Some if (and only if) some thread is waiting
    ///   on it.
    waiter: Option<Arc<Condvar>>,
}

/// State held by the [`RpcConn`] for a single request ID.
enum RequestState {
    /// A request submitted by one of the `execute_*` functions:
    /// The user must call a "wait" function for this request specifically in order to get
    /// responses. This request has its own queue.
    Waiting(ResponseQueue<AnyRequestId>),

    /// A request submitted by one of the `submit_*` functions:
    /// the user must provide an associated [`UserTag`],
    /// and call [`RpcConn::wait`] to find responses.
    Pollable(UserTag),
}

impl<Q: QueueId + ?Sized> ResponseQueue<Q> {
    /// Helper: Pop and return the next message for this request.
    ///
    /// If there are no queued messages, but a fatal error has occurred on the connection,
    /// return that.
    ///
    /// If there are no queued messages and no fatal error, return None.
    fn pop_next_msg(
        &mut self,
        fatal: &Option<ShutdownError>,
    ) -> Option<Result<(Q::UserTag, ValidatedResponse), ShutdownError>> {
        if let Some(m) = self.queue.pop_front() {
            Some(Ok(m))
        } else {
            fatal.as_ref().map(|f| Err(f.clone()))
        }
    }

    /// Queue `response` for this request, and alert the condvar (if any).
    fn push_back_and_alert(&mut self, tag: Q::UserTag, response: ValidatedResponse) {
        self.queue.push_back((tag, response));

        if let Some(cv) = &self.waiter {
            cv.notify_one();
        }
    }
}

/// A map from a [`QueueId`] to a request state.
#[derive(Default)]
struct RequestMap {
    /// A map from request ID to the state for that request ID.
    ///
    /// Entries are added to this map when a request is sent,
    /// and removed when the request encounters
    /// an error or a final response.
    map: HashMap<AnyRequestId, RequestState>,

    /// A response queue to hold the responses for pollable requests.
    polled_response_queue: ResponseQueue<PolledRequests>,
}

/// An action to take with a given message.
///
/// Returned by [`QueueId::response_disposition`]
enum ResponseDisposition<'a, Q: QueueId + ?Sized> {
    /// This message is for the queue that we are waiting for;
    /// we should return it to the caller.
    Return(Q::UserTag),

    /// This message if for a dead request that was probably cancelled;
    /// we should drop it.
    Ignore,

    /// This message is for some other request;
    /// we should instead forward it to that request's queue.
    ForwardWaiting(&'a mut ResponseQueue<AnyRequestId>),

    /// This message is for some other request;
    ///  we should instead forward it to the polled request queue.
    ForwardPollable(UserTag, &'a mut ResponseQueue<PolledRequests>),
}

/// Mutable state to implement receiving replies on an RpcConn.
struct ReceiverState {
    /// Helper to assign connection- unique IDs to any requests without them.
    id_gen: IdGenerator,
    /// A fatal error, if any has occurred.
    fatal: Option<ShutdownError>,
    /// A map from request ID to the corresponding state.
    ///
    /// There is an entry in this map for every request that we have sent,
    /// unless we have received a final response for that request,
    /// or we have cancelled that request.
    ///
    /// (TODO: We might handle cancelling differently.)
    pending: RequestMap,
    /// A steam that we use to send requests and receive replies from Arti.
    ///
    /// Invariants:
    ///
    /// * If this is None, a thread is polling and will take responsibility
    ///   for liveness.
    /// * If this is Some, no-one is polling and anyone who cares about liveness
    ///   must take on the interactor role.
    ///
    /// (Therefore, when it becomes Some, we must signal a cv, if any is set.)
    conn: Option<BlockingConnection>,
}

impl RequestMap {
    /// Notify an arbitrarily chosen request's condvar.
    fn alert_anybody(&self) {
        // TODO: This is O(n) in the worst case.
        //
        // But with luck, nobody will make a million requests and
        // then wait on them one at a time?
        for ent in self.map.values() {
            if let RequestState::Waiting(ResponseQueue {
                waiter: Some(cv), ..
            }) = ent
            {
                cv.notify_one();
                return;
            }
        }
    }

    /// Notify the condvar for every request.
    fn alert_everybody(&self) {
        for ent in self.map.values() {
            if let RequestState::Waiting(ResponseQueue {
                waiter: Some(cv), ..
            }) = ent
            {
                // By our rules, each condvar is waited on by precisely one thread.
                // So we call `notify_one` even though we are trying to wake up everyone.
                cv.notify_one();
            }
        }
    }
}

/// Object to receive messages on an RpcConn.
///
/// This is a crate-internal abstraction.
/// It's separate from RpcConn for a few reasons:
///
/// - So we can keep polling the channel while the RpcConn has
///   been dropped.
/// - So we can hold the lock on this part without being blocked on threads writing.
/// - Because this is the only part that for which
///   `RequestHandle` needs to keep a reference.
pub(super) struct Receiver {
    /// Mutable state.
    ///
    /// This lock should only be held briefly, and never while interacting with the
    /// `BlockingConnection`.
    state: Mutex<ReceiverState>,
}

/// An open RPC connection to Arti.
#[derive(educe::Educe)]
#[educe(Debug)]
pub struct RpcConn {
    /// The receiver object for this conn.
    ///
    /// It's in an `Arc<>` so that we can share it with the RequestHandles.
    #[educe(Debug(ignore))]
    pub(super) receiver: Arc<Receiver>,

    /// A writer that we use to queue requests to be sent back to Arti.
    writer: crate::ll_conn::WriteHandle,

    /// If set, we are authenticated and we have negotiated a session that has
    /// this ObjectID.
    pub(super) session: Option<ObjectId>,
}

/// A handle used to poll for RPC responses within an [event-driven IO] loop.
///
/// Only one handle of this type can exist per [`RpcConn`].
///
/// This type is _not_ intended to be used by multiple threads at once: Only one thread at a time
/// should ever invoke its [`poll`](RpcPoll::poll) method.
/// (In Rust, this is enforced by having RpcPoll::poll take `&mut self`.)
///
/// [event-driven IO]: https://man7.org/linux/man-pages/man2/select.2.html
pub struct RpcPoll {
    /// The message-receiver that we're using to track request state and report responses.
    receiver: Arc<Receiver>,

    /// The underling nonblocking connection that we're polling for readiness,
    /// and using to send and receive messages.
    nbconn: NonblockingConnection,
}

/// Instruction to alert some additional condvar(s) before releasing our lock and returning
///
/// Any code which receives one of these must pass the instruction on to someone else,
/// until, eventually, the instruction is acted on in [`Receiver::wait_on_message_for`].
#[must_use]
#[derive(Debug)]
enum AlertWhom {
    /// We don't need to alert anybody;
    /// we have not taken the connection, or registered our own condvar:
    /// therefore nobody expects us to take the connection.
    Nobody,
    /// We have taken the connection or been alerted via our condvar:
    /// therefore, we are responsible for making sure
    /// that _somebody_ takes the connection.
    ///
    /// We should therefore alert somebody if nobody currently has the connection.
    Anybody,
    /// We have been the first to encounter a fatal error.
    /// Therefore, we should inform _everybody_.
    Everybody,
}

impl RpcConn {
    /// Construct a new RpcConn with a given BlockingConnection.
    pub(super) fn new(conn: BlockingConnection) -> Self {
        let writer = conn.writer();
        Self {
            receiver: Arc::new(Receiver {
                state: Mutex::new(ReceiverState {
                    id_gen: IdGenerator::default(),
                    fatal: None,
                    pending: RequestMap::default(),
                    conn: Some(conn),
                }),
            }),
            writer,
            session: None,
        }
    }

    /// Return a new [`RpcPoll`] to use for managing an RpcConn using event-driven IO.
    ///
    /// Removes the `BlockingConnection` from this `RpcConn`
    /// and drops any mio resources associated with it.
    /// After this method is called is called, only `RpcPoll::poll()` can interact with it.
    ///
    /// See caveats on [`RpcConnBuilder::connect_polling`](crate::RpcConnBuilder::connect_polling).
    pub(crate) fn construct_rpc_poll(
        &mut self,
        event_loop: Box<dyn crate::ll_conn::EventLoop>,
    ) -> Option<RpcPoll> {
        let mut state = self.receiver.state.lock().expect("Lock poisoned");
        // TODO nb: enforce that nobody else is holding the state?  Return an error?
        let mut nbconn = state.conn.take()?.into_nonblocking();
        nbconn.replace_event_loop_handle(event_loop);
        Some(RpcPoll {
            receiver: Arc::clone(&self.receiver),
            nbconn,
        })
    }

    /// Send the request in `msg` on this connection, and return a RequestHandle
    /// to wait for a reply.
    ///
    /// We validate `msg` before sending it out, and reject it if it doesn't
    /// make sense. If `msg` has no `id` field, we allocate a new one
    /// according to the rules in [`IdGenerator`].
    ///
    /// Limitation: We don't preserved unrecognized fields in the framing and meta
    /// parts of `msg`.  See notes in `request.rs`.
    pub(super) fn send_waitable_request(
        &self,
        msg: &str,
    ) -> Result<super::RequestHandle, ProtoError> {
        let id = self.send_request_impl::<AnyRequestId>(msg, ())?;
        Ok(super::RequestHandle {
            conn: Mutex::new(Arc::clone(&self.receiver)),
            id,
        })
    }

    /// As a`send_waitable_request`, but send a Polled request -- one without a RequestHandle,
    /// where responses are returned via [`RpcConn::wait()`].
    pub(super) fn send_pollable_request(&self, tag: UserTag, msg: &str) -> Result<(), ProtoError> {
        let _id = self.send_request_impl::<PolledRequests>(msg, tag)?;
        Ok(())
    }

    /// Helper for send_request.
    ///
    /// We use the [`QueueId`] parameter to determine what kind of queue will
    fn send_request_impl<Q: QueueId>(
        &self,
        msg: &str,
        tag: Q::UserTag,
    ) -> Result<AnyRequestId, ProtoError> {
        use std::collections::hash_map::Entry::*;

        let mut state = self.receiver.state.lock().expect("poisoned");
        if let Some(f) = &state.fatal {
            // If there's been a fatal error we don't even try to send the request.
            return Err(f.clone().into());
        }

        // Convert this request into validated form (with an ID) and re-encode it.
        let valid: ValidatedRequest =
            ValidatedRequest::from_string_loose(msg, || state.id_gen.next_id())?;

        // Do the necessary housekeeping before we send the request, so that
        // we'll be able to understand the replies.
        let id = valid.id().clone();
        match state.pending.map.entry(id.clone()) {
            Occupied(_) => return Err(ProtoError::RequestIdInUse),
            Vacant(v) => {
                v.insert(Q::new_entry(tag));
            }
        }
        // Release the lock on the ReceiverState here; the two locks must not overlap.
        drop(state);

        // NOTE: This is the only block of code that holds the writer lock!
        let write_outcome = self.writer.send_valid(&valid);

        match write_outcome {
            Err(e) => {
                // A failed write is a fatal error for everybody.
                let e = ShutdownError::Write(Arc::new(e));
                let mut state = self.receiver.state.lock().expect("poisoned");
                if state.fatal.is_none() {
                    state.fatal = Some(e.clone());
                    state.pending.alert_everybody();
                }
                Err(e.into())
            }

            Ok(()) => Ok(id),
        }
    }
}

impl Receiver {
    /// Wait until there is either a fatal error on this connection,
    /// _or_ there is a new message for the queue with the provided waiting request `id`.
    /// Return that message, or a copy of the fatal error.
    pub(super) fn wait_on_message_for(
        &self,
        id: &AnyRequestId,
    ) -> Result<ValidatedResponse, ProtoError> {
        let ((), response) = self.wait_on_message_for_queue(id)?;
        Ok(response)
    }

    /// Wait until there is aeither a fatal error on this connection,
    /// _or_ there is a new message for some pollable request.
    pub(super) fn wait_on_pollable_response(
        &self,
    ) -> Result<(UserTag, ValidatedResponse), ProtoError> {
        self.wait_on_message_for_queue(&PolledRequests)
    }

    /// Wait until there is either a fatal error on this connection,
    /// _or_ there is a new message for the queue with the provided `queue_id`.
    /// Return that message, or a copy of the fatal error.
    fn wait_on_message_for_queue<Q: QueueId>(
        &self,
        queue_id: &Q,
    ) -> Result<(Q::UserTag, ValidatedResponse), ProtoError> {
        // Here in wait_on_message_for_impl, we do the actual work
        // of waiting for the message.
        let state = self.state.lock().expect("poisoned");
        let (result, mut state, should_alert) = self.wait_on_message_for_impl(state, queue_id);

        // Great; we have a message or a fatal error.  All we need to do now
        // is to restore our invariants before we drop state_lock.
        //
        // (It would be a bug to return early without restoring the invariants,
        // so we'll use an IEFE pattern to prevent "?" and "return Err".)
        #[allow(clippy::redundant_closure_call)]
        (|| {
            // "final" in this case means that we are not expecting any more
            // replies for this request.
            let (msg_id, is_final) = match &result {
                Err(_) => (None, true),
                Ok(r) => (Some(r.1.id()), r.1.is_final()),
            };

            if is_final {
                // Note 1: It might be cleaner to use Entry::remove(), but Entry is not
                // exactly the right shape for us; see note in
                // wait_on_message_for_impl.

                // Note 2: This remove isn't necessary if `result` is
                // RequestCancelled, but it won't hurt.

                // Note 3: On DuplicateWait, it is not totally clear whether we should
                // remove or not.  But that's an internal error that should never occur,
                // so it is probably okay if we let the _other_ waiter keep on trying.
                queue_id.remove_entry(&mut state.pending, msg_id);
            }

            match should_alert {
                AlertWhom::Nobody => {}
                AlertWhom::Anybody if state.conn.is_none() => {}
                AlertWhom::Anybody => state.pending.alert_anybody(),
                AlertWhom::Everybody => state.pending.alert_everybody(),
            }
        })();

        result
    }

    /// Helper to implement [`wait_on_message_for`](Self::wait_on_message_for).
    ///
    /// Takes a `MutexGuard` as one of its arguments, and returns an equivalent
    /// `MutexGuard` on completion.
    ///
    /// The caller is responsible for:
    ///
    /// - Removing the appropriate entry from `pending`, if the result
    ///   indicates that no more messages will be received for this request.
    /// - Possibly, notifying one or more condvars,
    ///   depending on the resulting `AlertWhom`.
    ///
    /// The caller must not drop the `MutexGuard` until it has done the above.
    #[allow(clippy::type_complexity)]
    fn wait_on_message_for_impl<'a, Q: QueueId>(
        &'a self,
        mut state_lock: MutexGuard<'a, ReceiverState>,
        queue_id: &Q,
    ) -> (
        Result<(Q::UserTag, ValidatedResponse), ProtoError>,
        MutexGuard<'a, ReceiverState>,
        AlertWhom,
    ) {
        // At this point, we have not registered on a condvar, and we have not
        // taken the BlockingConnection.
        // Therefore, we do not yet need to ensure that anybody else takes the BlockingConnection.
        //
        // TODO: It is possibly too easy to forget to set this,
        // or to set it to a less "alerty" value.  Refactoring might help;
        // see discussion at
        // https://gitlab.torproject.org/tpo/core/arti/-/merge_requests/2258#note_3047267
        let mut should_alert = AlertWhom::Nobody;

        let mut state: &mut ReceiverState = &mut state_lock;

        // Initialize `this_ent` to our own entry in the pending table.
        let mut this_ent = match queue_id.get_queue_mut(&mut state.pending) {
            Ok(ent) => ent,
            Err(err) => return (Err(err), state_lock, should_alert),
        };

        let mut conn = loop {
            // Note: It might be nice to use a hash_map::Entry here, but it
            // doesn't really work the way we want.  The `entry()` API is always
            // ready to insert, and requires that we clone `id`.  But what we
            // want in this case is something that would give us a .remove()able
            // Entry only if one is present.
            if this_ent.waiter.is_some() {
                // This is an internal error; nobody should be able to cause this.
                return (Err(ProtoError::DuplicateWait), state_lock, should_alert);
            }

            if let Some(ready) = this_ent.pop_next_msg(&state.fatal) {
                // There is a reply for us, or a fatal error.
                return (ready.map_err(ProtoError::from), state_lock, should_alert);
            }

            // If we reach this point, we are about to either take the connection or
            // register a cv.  This means that when we return, we need to make
            // sure that at least one other cv gets notified.
            should_alert = AlertWhom::Anybody;

            if let Some(r) = state.conn.take() {
                // Nobody else is polling; we have to do it.
                break r;
            }

            // Somebody else is polling; register a condvar.
            let cv = Arc::new(Condvar::new());
            this_ent.waiter = Some(Arc::clone(&cv));

            state_lock = cv.wait(state_lock).expect("poisoned lock");
            state = &mut state_lock;
            // Restore `this_ent`...
            let e = match queue_id.get_queue_mut(&mut state.pending) {
                Ok(ent) => ent,
                Err(err) => return (Err(err), state_lock, should_alert),
            };
            this_ent = e;
            // ... And un-register our condvar.
            this_ent.waiter = None;

            // We have been notified: either there is a reply or us,
            // or we are supposed to take the connection.  We'll find out on our
            // next time through the loop.
        };

        let (result, mut state_lock, should_alert) =
            self.read_until_message_for(state_lock, &mut conn, queue_id);
        // Put the connection back.
        state_lock.conn = Some(conn);

        (result.map_err(ProtoError::from), state_lock, should_alert)
    }

    /// Interact with `conn`, writing any queued messages,
    /// reading messages, and
    /// delivering them as appropriate, until we find one for the queue `queue_id`
    /// or a fatal error occurs.
    ///
    /// Return that message or error, along with a `MutexGuard`.
    ///
    /// The caller is responsible for restoring the following state before
    /// dropping the `MutexGuard`:
    ///
    /// - Putting `conn` back into the `conn` field.
    /// - Other invariants as discussed in wait_on_message_for_impl.
    #[allow(clippy::type_complexity)]
    fn read_until_message_for<'a, Q: QueueId>(
        &'a self,
        mut state_lock: MutexGuard<'a, ReceiverState>,
        conn: &mut BlockingConnection,
        queue_id: &Q,
    ) -> (
        Result<(Q::UserTag, ValidatedResponse), ShutdownError>,
        MutexGuard<'a, ReceiverState>,
        AlertWhom,
    ) {
        loop {
            // Importantly, we drop the state lock while we are polling.
            // This is okay, since all our invariants should hold at this point.
            drop(state_lock);

            let result = match conn.interact() {
                Err(e) => Err(ShutdownError::Read(Arc::new(e))),
                Ok(None) => Err(ShutdownError::ConnectionClosed),
                Ok(Some(m)) => m.try_validate().map_err(ShutdownError::from),
            };

            state_lock = self.state.lock().expect("poisoned lock");
            let state = &mut state_lock;

            let response = match result {
                Ok(m) => m,
                Err(e) => {
                    // This is a fatal error on the whole connection.
                    //
                    // If it's the first one encountered, queue the error.
                    // In any case, return it.
                    if state.fatal.is_none() {
                        state.fatal = Some(e.clone());
                    }
                    return (Err(e), state_lock, AlertWhom::Everybody);
                }
            };

            match queue_id.response_disposition(&mut state.pending, &response) {
                ResponseDisposition::Return(tag) => {
                    // This only is for us, so there's no need to alert anybody specific
                    // or queue it.
                    return (Ok((tag, response)), state_lock, AlertWhom::Anybody);
                }
                ResponseDisposition::ForwardWaiting(queue) => {
                    queue.push_back_and_alert((), response);
                }
                ResponseDisposition::ForwardPollable(tag, queue) => {
                    queue.push_back_and_alert(tag, response);
                }
                ResponseDisposition::Ignore => {
                    // Nothing wanted this response any longer.
                    // _Probably_ this means that we decided to cancel the
                    // request but Arti sent this response before it handled
                    // our cancellation.
                }
            }
        }
    }
}

/// Type returned by [`RpcPoll::poll`] when no progress can be made until the underlying
/// connection has more data to read or write.
#[derive(Copy, Clone, Debug, Default)]
#[non_exhaustive]
pub struct WouldBlock;

impl RpcPoll {
    #[cfg(unix)]
    /// If possible, return a fd to use with an underlying event-driven IO code.
    ///
    /// This implementation fails if the underlying connection to the Arti RPC server
    /// is _not_ implemented via an fd.
    /// This is not possible in the current implementation,
    /// but may become possible in the future.
    /// Applications should consider this a fatal error.
    pub fn try_as_fd(&self) -> std::io::Result<std::os::fd::BorrowedFd<'_>> {
        self.nbconn.try_as_handle()
    }

    #[cfg(windows)]
    /// If possible, return a SOCKET to use with an underlying event-driven IO code.
    ///
    /// This implementation fails if the underlying connection to the Arti RPC server
    /// is _not_ implemented via a SOCKET.
    /// This is not possible in the current implementation,
    /// but may become possible in the future.
    /// Applications should consider this a fatal error.
    pub fn try_as_socket(&self) -> std::io::Result<std::os::windows::io::BorrowedSocket<'_>> {
        self.nbconn.try_as_handle()
    }

    /// Return true iff this [`RpcPoll`] currently wants to write
    ///
    /// If this returns true, the RPC library user should invoke [`RpcPoll::poll`]
    /// when the underlying connection is ready to write.
    ///
    /// See [`Eventloop`] for full usage information.
    ///
    /// Changes to the return value of this function correspond to calls
    /// to the methods on [`EventLoop`].
    ///
    /// A returned `false` value can be invalidated by calls to [`RpcConn::submit`].
    ///
    /// A returned `true` value can be invalidated by calls to [`RpcPoll::poll`].
    ///
    /// [`EventLoop`]: crate::EventLoop
    pub fn wants_to_write(&self) -> bool {
        self.nbconn.wants_to_write()
    }

    /// Handle IO for the associated RPC connection, without blocking.
    ///
    /// This method reads and writes data from the RPC server,
    /// until either:
    ///
    ///   * A response is available to a request created with [`RpcConn::submit`];
    ///     in which case, `RpcPoll::poll` returns that response.
    ///
    ///   * No further progress can be made without blocking;
    ///     in which case `RpcPoll::poll` returns [`WouldBlock`].
    ///
    /// This is used in conjunction with `EventLoop` and/or `wants_to_write`;
    /// see [the `EventLoop` documentation] for details.
    pub fn poll(&mut self) -> Result<Result<(UserTag, AnyResponse), WouldBlock>, ProtoError> {
        use crate::ll_conn::PollStatus;
        // We try reading _and_ writing regardless; it won't hurt anything.
        loop {
            let r = self.nbconn.interact_once();
            let response = match r {
                Ok(PollStatus::Msg(m)) => m.try_validate().map_err(ShutdownError::from),
                Ok(PollStatus::Closed) => return Err(ShutdownError::ConnectionClosed.into()),
                Ok(PollStatus::WouldBlock) => return Ok(Err(WouldBlock)),
                Err(io_error) => return Err(ShutdownError::Read(Arc::new(io_error)).into()),
            };

            let mut state = self.receiver.state.lock().expect("Poisoned lock");

            let response = match response {
                Ok(m) => m,
                Err(e) => {
                    if state.fatal.is_none() {
                        state.fatal = Some(e.clone());
                        state.pending.alert_everybody();
                    }
                    return Err(e.into());
                }
            };

            match PolledRequests.response_disposition(&mut state.pending, &response) {
                ResponseDisposition::Return(tag) => {
                    return Ok(Ok((tag, AnyResponse::from_validated(response))));
                }
                ResponseDisposition::Ignore => {}
                ResponseDisposition::ForwardWaiting(response_queue) => {
                    response_queue.push_back_and_alert((), response);
                }
                ResponseDisposition::ForwardPollable(_, _) => panic!("This should be unreachable"),
            };
            drop(state);
        }
    }
}