use std::{
collections::VecDeque,
sync::{Arc, Mutex},
time::Instant,
};
use futures::{FutureExt, channel::oneshot, select};
use tracing::trace;
use super::{
AbandonedReply, ChannelError, HidppMessage, RequestOutcome, STALE_REPLY_GRACE,
observation::RequestObservation,
};
use crate::sync::lock;
type CorrelationKey = (u8, u8, u8);
#[derive(Default)]
pub(super) struct PendingQueue {
pub(super) messages: VecDeque<PendingMessage>,
pub(super) stale: Vec<StaleKey>,
key_waiters: Vec<oneshot::Sender<()>>,
}
pub(super) struct StaleKey {
pub(super) key: CorrelationKey,
pub(super) request: HidppMessage,
pub(super) response_predicate: Box<dyn Fn(&HidppMessage) -> bool + Send>,
pub(super) outstanding: usize,
pub(super) expires: Instant,
}
enum Wait {
InFlight,
Stale(Instant),
}
impl PendingQueue {
fn try_register(
&mut self,
mut message: PendingMessage,
now: Instant,
) -> Result<(), (PendingMessage, Wait)> {
self.stale.retain(|stale| stale.expires > now);
if self
.messages
.iter()
.any(|pending| pending.key == message.key)
{
return Err((message, Wait::InFlight));
}
let adopts = |stale: &StaleKey| {
message.abandoned == AbandonedReply::AdoptIdentical && stale.request == message.request
};
if let Some(until) = self
.stale
.iter()
.filter(|stale| stale.key == message.key && !adopts(stale))
.map(|stale| stale.expires)
.max()
{
return Err((message, Wait::Stale(until)));
}
message.extra_replies = self
.stale
.iter()
.filter(|stale| stale.key == message.key)
.map(|stale| stale.outstanding)
.sum();
self.stale.retain(|stale| stale.key != message.key);
self.messages.push_back(message);
Ok(())
}
fn abandon(&mut self, id: u64, now: Instant) {
let Some(pos) = self.messages.iter().position(|message| message.id == id) else {
return;
};
let Some(abandoned) = self.messages.remove(pos) else {
return;
};
let outstanding = abandoned.extra_replies + 1;
self.owe_replies(abandoned, outstanding, now);
self.wake_key_waiters();
}
fn owe_replies(&mut self, message: PendingMessage, outstanding: usize, now: Instant) {
let PendingMessage {
key,
request,
response_predicate,
..
} = message;
self.stale.push(StaleKey {
key,
request,
response_predicate,
outstanding,
expires: now + STALE_REPLY_GRACE,
});
}
pub(super) fn discard_stale_reply(&mut self, msg: &HidppMessage) -> bool {
let Some(pos) = self
.stale
.iter()
.position(|stale| (stale.response_predicate)(msg))
else {
return false;
};
self.stale[pos].outstanding -= 1;
if self.stale[pos].outstanding == 0 {
self.stale.remove(pos);
self.wake_key_waiters();
}
true
}
pub(super) fn wake_key_waiters(&mut self) {
for waiter in self.key_waiters.drain(..) {
let _ = waiter.send(());
}
}
}
pub(super) struct PendingMessage {
pub(super) id: u64,
pub(super) key: CorrelationKey,
pub(super) request: HidppMessage,
abandoned: AbandonedReply,
pub(super) response_predicate: Box<dyn Fn(&HidppMessage) -> bool + Send>,
pub(super) sender: oneshot::Sender<HidppMessage>,
pub(super) extra_replies: usize,
}
pub(super) struct PendingRequest {
id: u64,
pending_messages: Arc<Mutex<PendingQueue>>,
receiver: oneshot::Receiver<HidppMessage>,
}
impl PendingRequest {
pub(super) async fn register_when_key_free(
id: u64,
pending_messages: Arc<Mutex<PendingQueue>>,
request: HidppMessage,
abandoned: AbandonedReply,
response_predicate: impl Fn(&HidppMessage) -> bool + Send + 'static,
) -> Self {
let (sender, receiver) = oneshot::channel();
let key = request.header();
let mut message = PendingMessage {
id,
key,
request,
abandoned,
response_predicate: Box::new(response_predicate),
sender,
extra_replies: 0,
};
loop {
let now = Instant::now();
let (parked, stale_until) = {
let mut queue = lock(&pending_messages);
let stale_until = match queue.try_register(message, now) {
Ok(()) => break,
Err((returned, Wait::InFlight)) => {
message = returned;
None
}
Err((returned, Wait::Stale(expires))) => {
message = returned;
Some(expires)
}
};
let (wake, parked) = oneshot::channel();
queue.key_waiters.push(wake);
(parked, stale_until)
};
let (dev, feat, func) = key;
match stale_until {
None => {
trace!(
dev,
feat, func, "hidpp request parked — same header in flight"
);
let _ = parked.await;
}
Some(expires) => {
trace!(
dev,
feat,
func,
"hidpp request parked — replies with its header are still outstanding"
);
let mut parked = parked.fuse();
let mut grace =
futures_timer::Delay::new(expires.saturating_duration_since(now)).fuse();
select! {
_ = parked => {}
() = grace => {}
}
}
}
}
Self {
id,
pending_messages,
receiver,
}
}
pub(super) async fn receive(mut self) -> Option<HidppMessage> {
(&mut self.receiver).await.ok()
}
}
impl Drop for PendingRequest {
fn drop(&mut self) {
lock(&self.pending_messages).abandon(self.id, Instant::now());
}
}
pub(super) enum PendingRequestCompletion {
Response(HidppMessage),
WriteFailed(ChannelError),
NoResponse,
TimedOut,
}
pub(super) fn finish_request(
completion: PendingRequestCompletion,
mut observation: RequestObservation<'_>,
dev: u8,
feat: u8,
) -> Result<HidppMessage, ChannelError> {
match completion {
PendingRequestCompletion::Response(response) => {
observation.complete(RequestOutcome::Succeeded);
trace!(dev, feat, "hidpp response");
Ok(response)
}
PendingRequestCompletion::WriteFailed(error) => {
observation.complete(RequestOutcome::WriteFailed);
trace!(dev, feat, error = ?error, "hidpp no response");
Err(error)
}
PendingRequestCompletion::NoResponse => {
observation.complete(RequestOutcome::NoResponse);
trace!(dev, feat, error = ?ChannelError::NoResponse, "hidpp no response");
Err(ChannelError::NoResponse)
}
PendingRequestCompletion::TimedOut => {
observation.complete(RequestOutcome::TimedOut);
trace!(dev, feat, error = ?ChannelError::Timeout, "hidpp no response");
Err(ChannelError::Timeout)
}
}
}