///|
/// Resource tracked by a gateway or device service budget.
pub(all) enum CapacityResource {
InFlightRequests
PollRegisters
CaptureBytes
CommandQueue
HistorianSamples
AlarmEvents
} derive(Debug, Eq)
///|
/// A named resource limit with soft and hard thresholds.
pub(all) struct CapacityLimit {
resource : CapacityResource
name : String
soft_limit : Int
hard_limit : Int
mut used : Int
}
///|
pub fn CapacityLimit::new(
resource : CapacityResource,
name : String,
soft_limit : Int,
hard_limit : Int,
) -> Result[CapacityLimit, ModbusError] {
if name == "" || soft_limit < 0 || hard_limit < soft_limit {
return Err(InvalidData)
}
Ok({ resource, name, soft_limit, hard_limit, used: 0 })
}
///|
pub fn CapacityLimit::resource(self : CapacityLimit) -> CapacityResource {
self.resource
}
///|
pub fn CapacityLimit::name(self : CapacityLimit) -> String {
self.name
}
///|
pub fn CapacityLimit::soft_limit(self : CapacityLimit) -> Int {
self.soft_limit
}
///|
pub fn CapacityLimit::hard_limit(self : CapacityLimit) -> Int {
self.hard_limit
}
///|
pub fn CapacityLimit::used(self : CapacityLimit) -> Int {
self.used
}
///|
pub fn CapacityLimit::available(self : CapacityLimit) -> Int {
self.hard_limit - self.used
}
///|
pub fn CapacityLimit::over_soft(self : CapacityLimit) -> Bool {
self.used > self.soft_limit
}
///|
pub fn CapacityLimit::over_hard(self : CapacityLimit) -> Bool {
self.used > self.hard_limit
}
///|
pub fn CapacityLimit::reserve(
self : CapacityLimit,
amount : Int,
) -> Result[Unit, ModbusError] {
if amount < 1 {
return Err(InvalidQuantity)
}
if self.used + amount > self.hard_limit {
return Err(CapacityExceeded)
}
self.used += amount
Ok(())
}
///|
pub fn CapacityLimit::release(
self : CapacityLimit,
amount : Int,
) -> Result[Unit, ModbusError] {
if amount < 1 || amount > self.used {
return Err(InvalidQuantity)
}
self.used -= amount
Ok(())
}
///|
pub fn CapacityLimit::reset(self : CapacityLimit) -> Unit {
self.used = 0
}
///|
pub fn capacity_resource_name(resource : CapacityResource) -> String {
match resource {
InFlightRequests => "in-flight-requests"
PollRegisters => "poll-registers"
CaptureBytes => "capture-bytes"
CommandQueue => "command-queue"
HistorianSamples => "historian-samples"
AlarmEvents => "alarm-events"
}
}
///|
/// A reservation returned to a caller so releases can be audited.
pub(all) struct CapacityReservation {
id : String
resource : CapacityResource
amount : Int
created_at : Int
mut released : Bool
}
///|
pub fn CapacityReservation::new(
id : String,
resource : CapacityResource,
amount : Int,
created_at : Int,
) -> Result[CapacityReservation, ModbusError] {
if id == "" || amount < 1 || created_at < 0 {
Err(InvalidData)
} else {
Ok({ id, resource, amount, created_at, released: false })
}
}
///|
pub fn CapacityReservation::id(self : CapacityReservation) -> String {
self.id
}
///|
pub fn CapacityReservation::resource(
self : CapacityReservation,
) -> CapacityResource {
self.resource
}
///|
pub fn CapacityReservation::amount(self : CapacityReservation) -> Int {
self.amount
}
///|
pub fn CapacityReservation::created_at(self : CapacityReservation) -> Int {
self.created_at
}
///|
pub fn CapacityReservation::released(self : CapacityReservation) -> Bool {
self.released
}
///|
pub fn CapacityReservation::mark_released(
self : CapacityReservation,
) -> Result[Unit, ModbusError] {
if self.released {
Err(Busy)
} else {
self.released = true
Ok(())
}
}
///|
/// Multi-resource ledger for admission control.
pub(all) struct CapacityLedger {
limits : Array[CapacityLimit]
reservations : Array[CapacityReservation]
max_reservations : Int
mut sequence : Int
}
///|
pub fn CapacityLedger::new(
max_reservations? : Int = 8192,
) -> Result[CapacityLedger, ModbusError] {
if max_reservations < 1 {
Err(InvalidData)
} else {
Ok({ limits: [], reservations: [], max_reservations, sequence: 0 })
}
}
///|
pub fn CapacityLedger::limit_count(self : CapacityLedger) -> Int {
self.limits.length()
}
///|
pub fn CapacityLedger::reservation_count(self : CapacityLedger) -> Int {
self.reservations.length()
}
///|
pub fn CapacityLedger::add_limit(
self : CapacityLedger,
limit : CapacityLimit,
) -> Result[Unit, ModbusError] {
for existing in self.limits {
if existing.resource() == limit.resource() {
return Err(InvalidData)
}
}
self.limits.push(limit)
Ok(())
}
///|
pub fn CapacityLedger::limit(
self : CapacityLedger,
resource : CapacityResource,
) -> CapacityLimit? {
for limit in self.limits {
if limit.resource() == resource {
return Some(limit)
}
}
None
}
///|
/// Reserve one resource atomically and create an expirable token.
pub fn CapacityLedger::reserve(
self : CapacityLedger,
resource : CapacityResource,
amount : Int,
now : Int,
) -> Result[CapacityReservation, ModbusError] {
if now < 0 ||
amount < 1 ||
self.reservations.length() >= self.max_reservations {
return Err(CapacityExceeded)
}
let limit = match self.limit(resource) {
Some(value) => value
None => return Err(InvalidData)
}
match limit.reserve(amount) {
Err(error) => return Err(error)
Ok(_) => ()
}
self.sequence += 1
let id = "reservation-" + self.sequence.to_string()
let reservation = match CapacityReservation::new(id, resource, amount, now) {
Ok(value) => value
Err(error) => {
ignore(limit.release(amount))
return Err(error)
}
}
self.reservations.push(reservation)
Ok(reservation)
}
///|
/// Release a previously issued reservation exactly once.
pub fn CapacityLedger::release(
self : CapacityLedger,
reservation_id : String,
) -> Result[Unit, ModbusError] {
for reservation in self.reservations {
if reservation.id() == reservation_id {
if reservation.released() {
return Err(Busy)
}
let limit = match self.limit(reservation.resource()) {
Some(value) => value
None => return Err(InvalidData)
}
match limit.release(reservation.amount()) {
Err(error) => return Err(error)
Ok(_) => ()
}
return reservation.mark_released()
}
}
Err(InvalidData)
}
///|
/// Expire all reservations older than a caller-supplied age.
pub fn CapacityLedger::expire(
self : CapacityLedger,
now : Int,
age : Int,
) -> Result[Int, ModbusError] {
if now < 0 || age < 0 {
return Err(InvalidData)
}
let mut released = 0
for reservation in self.reservations {
if !reservation.released() && now - reservation.created_at() >= age {
let limit = match self.limit(reservation.resource()) {
Some(value) => value
None => continue
}
ignore(limit.release(reservation.amount()))
ignore(reservation.mark_released())
released += 1
}
}
Ok(released)
}
///|
/// Return resources currently above their soft limit.
pub fn CapacityLedger::pressure(
self : CapacityLedger,
) -> Array[CapacityResource] {
let result : Array[CapacityResource] = []
for limit in self.limits {
if limit.over_soft() {
result.push(limit.resource())
}
}
result
}
///|
/// Reset all limits and reservation tokens after a controlled restart.
pub fn CapacityLedger::reset(self : CapacityLedger) -> Unit {
for limit in self.limits {
limit.reset()
}
for reservation in self.reservations {
ignore(reservation.mark_released())
}
self.reservations.clear()
}
///|
/// Human-readable capacity table for health endpoints.
pub fn CapacityLedger::export_lines(self : CapacityLedger) -> Array[String] {
let lines : Array[String] = []
for limit in self.limits {
lines.push(
capacity_resource_name(limit.resource()) +
" name=" +
limit.name() +
" used=" +
limit.used().to_string() +
" soft=" +
limit.soft_limit().to_string() +
" hard=" +
limit.hard_limit().to_string(),
)
}
lines
}