use std::collections::HashMap;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use bacnet_types::primitives::ObjectIdentifier;
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
pub(crate) const RUN_REQUESTS: usize = 32;
pub(crate) const PER_DEVICE: usize = 1;
pub(crate) struct RemoteSlots {
server: Arc<Semaphore>,
devices: Mutex<HashMap<ObjectIdentifier, DeviceQueue>>,
}
struct DeviceQueue {
users: usize,
slots: Arc<Semaphore>,
}
impl Default for RemoteSlots {
fn default() -> Self {
Self {
server: Arc::new(Semaphore::new(RUN_REQUESTS)),
devices: Mutex::default(),
}
}
}
impl RemoteSlots {
pub(crate) async fn acquire(&self, device: ObjectIdentifier) -> RemoteSlot<'_> {
let (user, slots) = self.join(device);
let device_slot = slots
.acquire_owned()
.await
.expect("a device's slots are never closed");
let server_slot = Arc::clone(&self.server)
.acquire_owned()
.await
.expect("the server's slots are never closed");
RemoteSlot {
_server: server_slot,
_device: device_slot,
_user: user,
}
}
#[cfg(test)]
pub(crate) fn outstanding(&self) -> usize {
RUN_REQUESTS - self.server.available_permits()
}
#[cfg(test)]
pub(crate) fn devices(&self) -> usize {
self.lock().len()
}
fn join(&self, device: ObjectIdentifier) -> (DeviceUser<'_>, Arc<Semaphore>) {
let mut devices = self.lock();
let queue = devices.entry(device).or_insert_with(|| DeviceQueue {
users: 0,
slots: Arc::new(Semaphore::new(PER_DEVICE)),
});
queue.users += 1;
let slots = Arc::clone(&queue.slots);
(
DeviceUser {
slots: self,
device,
},
slots,
)
}
fn lock(&self) -> MutexGuard<'_, HashMap<ObjectIdentifier, DeviceQueue>> {
self.devices.lock().unwrap_or_else(PoisonError::into_inner)
}
}
pub(crate) struct RemoteSlot<'s> {
_server: OwnedSemaphorePermit,
_device: OwnedSemaphorePermit,
_user: DeviceUser<'s>,
}
struct DeviceUser<'s> {
slots: &'s RemoteSlots,
device: ObjectIdentifier,
}
impl Drop for DeviceUser<'_> {
fn drop(&mut self) {
let mut devices = self.slots.lock();
if let Some(queue) = devices.get_mut(&self.device) {
queue.users -= 1;
if queue.users == 0 {
devices.remove(&self.device);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bacnet_types::enums::ObjectType;
use std::future::Future;
use std::task::Poll;
fn device(instance: u32) -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::DEVICE, instance).unwrap()
}
#[tokio::test]
async fn a_device_takes_one_request_and_the_server_thirty_two() {
let slots = RemoteSlots::default();
let held: Vec<_> = futures_util::future::join_all(
(0..RUN_REQUESTS as u32).map(|instance| slots.acquire(device(instance))),
)
.await;
assert_eq!(slots.outstanding(), RUN_REQUESTS);
let mut cx = std::task::Context::from_waker(std::task::Waker::noop());
let mut more = Box::pin(slots.acquire(device(99)));
let mut again = Box::pin(slots.acquire(device(0)));
assert!(more.as_mut().poll(&mut cx).is_pending());
assert!(again.as_mut().poll(&mut cx).is_pending());
drop(held);
let (Poll::Ready(more), Poll::Ready(again)) =
(more.as_mut().poll(&mut cx), again.as_mut().poll(&mut cx))
else {
panic!("both get through once the others let go");
};
assert_eq!(slots.outstanding(), 2);
drop((more, again));
assert_eq!((slots.outstanding(), slots.devices()), (0, 0));
}
#[tokio::test]
async fn a_device_leaves_once_nothing_holds_or_waits_for_it() {
let slots = RemoteSlots::default();
let held = slots.acquire(device(1)).await;
let mut cx = std::task::Context::from_waker(std::task::Waker::noop());
let mut waiting = Box::pin(slots.acquire(device(1)));
assert!(waiting.as_mut().poll(&mut cx).is_pending());
assert_eq!(slots.devices(), 1);
drop(waiting);
assert_eq!(slots.devices(), 1);
drop(held);
assert_eq!((slots.devices(), slots.outstanding()), (0, 0));
}
}