use crate::{FirstHopId, daemon};
use educe::Educe;
use futures::{Future, channel::mpsc::UnboundedSender};
use oneshot_fused_workaround as oneshot;
use pin_project::pin_project;
use std::fmt::Debug;
use std::pin::Pin;
use std::sync::atomic::{AtomicU64, Ordering};
use std::task::{Context, Poll};
use tor_proto::ClockSkew;
use web_time_compat::Instant;
use tor_basic_utils::skip_fmt;
#[pin_project]
pub struct GuardUsable {
#[pin]
u: Option<oneshot::Receiver<bool>>,
}
impl Future for GuardUsable {
type Output = Result<bool, oneshot::Canceled>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project().u.as_pin_mut() {
None => Poll::Ready(Ok(true)),
Some(u) => u.poll(cx),
}
}
}
impl GuardUsable {
pub(crate) fn new_usable_immediately() -> Self {
GuardUsable { u: None }
}
pub(crate) fn new_uncertain() -> (Self, oneshot::Sender<bool>) {
let (snd, rcv) = oneshot::channel();
(GuardUsable { u: Some(rcv) }, snd)
}
}
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub enum GuardStatus {
Success,
Failure,
Indeterminate,
AttemptAbandoned,
}
#[must_use = "You need to report the status of any guard that you asked for"]
#[derive(Educe)]
#[educe(Debug)]
pub struct GuardMonitor {
id: RequestId,
pending_status: GuardStatus,
ignore_indeterminate: bool,
pending_skew: Option<ClockSkew>,
#[educe(Debug(method = "skip_fmt"))]
snd: Option<UnboundedSender<daemon::Msg>>,
}
impl GuardMonitor {
pub(crate) fn new(id: RequestId, snd: UnboundedSender<daemon::Msg>) -> Self {
GuardMonitor {
id,
pending_status: GuardStatus::AttemptAbandoned,
ignore_indeterminate: false,
pending_skew: None,
snd: Some(snd),
}
}
pub fn succeeded(self) {
self.report(GuardStatus::Success);
}
pub fn failed(self) {
self.report(GuardStatus::Failure);
}
pub fn attempt_abandoned(self) {
self.report(GuardStatus::AttemptAbandoned);
}
pub fn pending_status(&mut self, status: GuardStatus) {
self.pending_status = status;
}
pub fn skew(&mut self, skew: ClockSkew) {
self.pending_skew = Some(skew);
}
#[cfg(feature = "testing")]
pub fn inspect_pending_status(&self) -> (GuardStatus, bool) {
(self.pending_status, self.ignore_indeterminate)
}
pub fn ignore_indeterminate_status(&mut self) {
self.ignore_indeterminate = true;
}
pub fn report(mut self, msg: GuardStatus) {
self.report_impl(msg);
}
fn report_impl(&mut self, msg: GuardStatus) {
let msg = match (msg, self.ignore_indeterminate) {
(GuardStatus::Indeterminate, true) => GuardStatus::AttemptAbandoned,
(m, _) => m,
};
let _ignore = self
.snd
.take()
.expect("GuardMonitor initialized with no sender")
.unbounded_send(daemon::Msg::Status(self.id, msg, self.pending_skew));
}
pub fn commit(self) {
let status = self.pending_status;
self.report(status);
}
}
impl Drop for GuardMonitor {
fn drop(&mut self) {
if self.snd.is_some() {
self.report_impl(self.pending_status);
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub(crate) struct RequestId {
id: u64,
}
impl RequestId {
pub(crate) fn next() -> RequestId {
static NEXT_VAL: AtomicU64 = AtomicU64::new(1);
let id = NEXT_VAL.fetch_add(1, Ordering::Relaxed);
assert!(id != 0, "Exhausted guard request Id space.");
RequestId { id }
}
}
#[derive(Debug)]
pub(crate) struct PendingRequest {
guard_id: FirstHopId,
usage: crate::GuardUsage,
usable: Option<oneshot::Sender<bool>>,
waiting_since: Option<Instant>,
net_has_been_down: bool,
}
impl PendingRequest {
pub(crate) fn new(
guard_id: FirstHopId,
usage: crate::GuardUsage,
usable: Option<oneshot::Sender<bool>>,
net_has_been_down: bool,
) -> Self {
PendingRequest {
guard_id,
usage,
usable,
waiting_since: None,
net_has_been_down,
}
}
pub(crate) fn guard_id(&self) -> &FirstHopId {
&self.guard_id
}
pub(crate) fn usage(&self) -> &crate::GuardUsage {
&self.usage
}
pub(crate) fn waiting_since(&self) -> Option<Instant> {
self.waiting_since
}
pub(crate) fn net_has_been_down(&self) -> bool {
self.net_has_been_down
}
pub(crate) fn reply(&mut self, usable: bool) {
if let Some(sender) = self.usable.take() {
let _ignore = sender.send(usable);
}
}
pub(crate) fn mark_waiting(&mut self, now: Instant) {
debug_assert!(self.waiting_since.is_none());
self.waiting_since = Some(now);
}
}