use monoloop_contracts::ToolId;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub struct SharedToolCapacity {
max: usize,
active: AtomicUsize,
}
impl SharedToolCapacity {
pub fn new(max: usize) -> Arc<Self> {
Arc::new(Self {
max: max.max(1),
active: AtomicUsize::new(0),
})
}
pub fn unlimited() -> Arc<Self> {
Self::new(usize::MAX / 4)
}
fn try_acquire(&self) -> bool {
loop {
let cur = self.active.load(Ordering::SeqCst);
if cur >= self.max {
return false;
}
if self
.active
.compare_exchange(cur, cur + 1, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
return true;
}
}
}
fn release(&self) {
self.active.fetch_sub(1, Ordering::SeqCst);
}
pub fn active(&self) -> usize {
self.active.load(Ordering::SeqCst)
}
}
#[derive(Debug)]
pub struct TransactionToolCapacity {
shared: Arc<SharedToolCapacity>,
max_concurrent: usize,
max_queued: usize,
txn_active: AtomicUsize,
txn_queued: AtomicUsize,
per_tool: Mutex<HashMap<ToolId, ToolSlot>>,
}
#[derive(Debug, Default)]
struct ToolSlot {
max_concurrent: usize,
active: usize,
}
pub struct ToolPermit {
shared: Arc<SharedToolCapacity>,
txn: Arc<TransactionToolCapacity>,
tool_id: ToolId,
}
impl Drop for ToolPermit {
fn drop(&mut self) {
self.shared.release();
self.txn.txn_active.fetch_sub(1, Ordering::SeqCst);
if let Ok(mut map) = self.txn.per_tool.lock() {
if let Some(slot) = map.get_mut(&self.tool_id) {
slot.active = slot.active.saturating_sub(1);
}
}
}
}
impl TransactionToolCapacity {
pub fn new(
shared: Arc<SharedToolCapacity>,
max_concurrent: usize,
max_queued: usize,
) -> Arc<Self> {
Arc::new(Self {
shared,
max_concurrent: max_concurrent.max(1),
max_queued: max_queued.max(1),
txn_active: AtomicUsize::new(0),
txn_queued: AtomicUsize::new(0),
per_tool: Mutex::new(HashMap::new()),
})
}
pub fn configure_tool(self: &Arc<Self>, tool_id: ToolId, max_concurrent: usize) {
let mut map = self.per_tool.lock().unwrap_or_else(|e| e.into_inner());
map.insert(
tool_id,
ToolSlot {
max_concurrent: max_concurrent.max(1),
active: 0,
},
);
}
pub fn try_enqueue(self: &Arc<Self>) -> bool {
loop {
let q = self.txn_queued.load(Ordering::SeqCst);
if q >= self.max_queued {
return false;
}
if self
.txn_queued
.compare_exchange(q, q + 1, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
{
return true;
}
}
}
pub fn dequeue(self: &Arc<Self>) {
self.txn_queued.fetch_sub(1, Ordering::SeqCst);
}
pub fn try_acquire(self: &Arc<Self>, tool_id: &ToolId) -> Option<ToolPermit> {
{
let mut map = self.per_tool.lock().unwrap_or_else(|e| e.into_inner());
let slot = map.entry(tool_id.clone()).or_insert(ToolSlot {
max_concurrent: 1,
active: 0,
});
if slot.active >= slot.max_concurrent {
return None;
}
let txn = self.txn_active.load(Ordering::SeqCst);
if txn >= self.max_concurrent {
return None;
}
if !self.shared.try_acquire() {
return None;
}
slot.active += 1;
self.txn_active.fetch_add(1, Ordering::SeqCst);
self.txn_queued.fetch_sub(1, Ordering::SeqCst);
}
Some(ToolPermit {
shared: Arc::clone(&self.shared),
txn: Arc::clone(self),
tool_id: tool_id.clone(),
})
}
pub fn active(&self) -> usize {
self.txn_active.load(Ordering::SeqCst)
}
pub fn queued(&self) -> usize {
self.txn_queued.load(Ordering::SeqCst)
}
}