///|
/// Ownership state for a serial line, TCP endpoint, or device command lane.
pub(all) enum LeaseState {
LeaseAvailable
LeaseHeld
LeaseExpired
LeaseRevoked
} derive(Debug, Eq)
///|
/// A time-bounded exclusive lease with renewal history.
pub(all) struct ResourceLease {
id : String
resource : String
owner : String
acquired_at : Int
mut expires_at : Int
mut state : LeaseState
mut renewals : Int
priority : Int
}
///|
pub fn ResourceLease::new(
id : String,
resource : String,
owner : String,
acquired_at : Int,
duration : Int,
priority? : Int = 0,
) -> Result[ResourceLease, ModbusError] {
if id == "" ||
resource == "" ||
owner == "" ||
acquired_at < 0 ||
duration < 1 ||
priority < 0 {
return Err(InvalidData)
}
Ok({
id,
resource,
owner,
acquired_at,
expires_at: acquired_at + duration,
state: LeaseHeld,
renewals: 0,
priority,
})
}
///|
pub fn ResourceLease::id(self : ResourceLease) -> String {
self.id
}
///|
pub fn ResourceLease::resource(self : ResourceLease) -> String {
self.resource
}
///|
pub fn ResourceLease::owner(self : ResourceLease) -> String {
self.owner
}
///|
pub fn ResourceLease::acquired_at(self : ResourceLease) -> Int {
self.acquired_at
}
///|
pub fn ResourceLease::expires_at(self : ResourceLease) -> Int {
self.expires_at
}
///|
pub fn ResourceLease::state(self : ResourceLease) -> LeaseState {
self.state
}
///|
pub fn ResourceLease::renewals(self : ResourceLease) -> Int {
self.renewals
}
///|
pub fn ResourceLease::priority(self : ResourceLease) -> Int {
self.priority
}
///|
pub fn ResourceLease::active(self : ResourceLease, now : Int) -> Bool {
self.state == LeaseHeld && now >= self.acquired_at && now < self.expires_at
}
///|
pub fn ResourceLease::renew(
self : ResourceLease,
now : Int,
duration : Int,
) -> Result[Unit, ModbusError] {
if now < self.acquired_at ||
duration < 1 ||
self.state != LeaseHeld ||
now >= self.expires_at {
return Err(Busy)
}
self.expires_at = now + duration
self.renewals += 1
Ok(())
}
///|
pub fn ResourceLease::expire(
self : ResourceLease,
now : Int,
) -> Result[Unit, ModbusError] {
if now < 0 {
return Err(InvalidData)
}
if self.state == LeaseHeld && now >= self.expires_at {
self.state = LeaseExpired
}
Ok(())
}
///|
pub fn ResourceLease::revoke(self : ResourceLease) -> Result[Unit, ModbusError] {
if self.state != LeaseHeld {
Err(Busy)
} else {
self.state = LeaseRevoked
Ok(())
}
}
///|
pub fn lease_state_name(state : LeaseState) -> String {
match state {
LeaseAvailable => "available"
LeaseHeld => "held"
LeaseExpired => "expired"
LeaseRevoked => "revoked"
}
}
///|
/// Manages exclusive resource ownership across client workers.
pub(all) struct LeaseManager {
leases : Array[ResourceLease]
max_leases : Int
default_duration : Int
mut sequence : Int
}
///|
pub fn LeaseManager::new(
max_leases? : Int = 1024,
default_duration? : Int = 5000,
) -> Result[LeaseManager, ModbusError] {
if max_leases < 1 || default_duration < 1 {
Err(InvalidData)
} else {
Ok({ leases: [], max_leases, default_duration, sequence: 0 })
}
}
///|
pub fn LeaseManager::count(self : LeaseManager) -> Int {
self.leases.length()
}
///|
pub fn LeaseManager::default_duration(self : LeaseManager) -> Int {
self.default_duration
}
///|
pub fn LeaseManager::find(
self : LeaseManager,
resource : String,
) -> ResourceLease? {
for lease in self.leases {
if lease.resource() == resource && lease.state() == LeaseHeld {
return Some(lease)
}
}
None
}
///|
/// Acquire a resource if no active lease has a higher or equal priority.
pub fn LeaseManager::acquire(
self : LeaseManager,
resource : String,
owner : String,
now : Int,
priority? : Int = 0,
) -> Result[ResourceLease, ModbusError] {
if resource == "" || owner == "" || now < 0 || priority < 0 {
return Err(InvalidData)
}
for existing in self.leases {
ignore(existing.expire(now))
if existing.resource() == resource && existing.active(now) {
if existing.priority() >= priority {
return Err(Busy)
}
ignore(existing.revoke())
}
}
if self.leases.length() >= self.max_leases {
return Err(CapacityExceeded)
}
self.sequence += 1
let id = "lease-" + self.sequence.to_string()
let lease = match
ResourceLease::new(
id,
resource,
owner,
now,
self.default_duration,
priority~,
) {
Ok(value) => value
Err(error) => return Err(error)
}
self.leases.push(lease)
Ok(lease)
}
///|
/// Renew a lease only for its current owner.
pub fn LeaseManager::renew(
self : LeaseManager,
lease_id : String,
owner : String,
now : Int,
duration? : Int = 0,
) -> Result[Unit, ModbusError] {
if lease_id == "" || owner == "" {
return Err(InvalidData)
}
let selected_duration = if duration == 0 {
self.default_duration
} else {
duration
}
for lease in self.leases {
if lease.id() == lease_id {
if lease.owner() != owner {
return Err(Unsupported)
}
return lease.renew(now, selected_duration)
}
}
Err(InvalidData)
}
///|
/// Release a lease at the end of a request or connection.
pub fn LeaseManager::release(
self : LeaseManager,
lease_id : String,
owner : String,
) -> Result[Unit, ModbusError] {
for lease in self.leases {
if lease.id() == lease_id {
if lease.owner() != owner {
return Err(Unsupported)
}
return lease.revoke()
}
}
Err(InvalidData)
}
///|
/// Expire inactive leases and return their count.
pub fn LeaseManager::expire(
self : LeaseManager,
now : Int,
) -> Result[Int, ModbusError] {
if now < 0 {
return Err(InvalidData)
}
let mut count = 0
for lease in self.leases {
let before = lease.state()
ignore(lease.expire(now))
if before == LeaseHeld && lease.state() == LeaseExpired {
count += 1
}
}
Ok(count)
}
///|
/// Return active leases for a given owner.
pub fn LeaseManager::owned_by(
self : LeaseManager,
owner : String,
now : Int,
) -> Result[Array[String], ModbusError] {
if owner == "" || now < 0 {
return Err(InvalidData)
}
let result : Array[String] = []
for lease in self.leases {
if lease.owner() == owner && lease.active(now) {
result.push(lease.resource())
}
}
Ok(result)
}
///|
/// Clean up old terminal leases while retaining recent history.
pub fn LeaseManager::compact(
self : LeaseManager,
before : Int,
) -> Result[Int, ModbusError] {
if before < 0 {
return Err(InvalidData)
}
let mut removed = 0
let mut index = 0
while index < self.leases.length() {
let lease = self.leases[index]
if lease.state() != LeaseHeld && lease.expires_at() < before {
ignore(self.leases.remove(index))
removed += 1
} else {
index += 1
}
}
Ok(removed)
}
///|
/// Return resource names with an active owner.
pub fn LeaseManager::active_resources(
self : LeaseManager,
now : Int,
) -> Result[Array[String], ModbusError] {
if now < 0 {
return Err(InvalidData)
}
let result : Array[String] = []
for lease in self.leases {
if lease.active(now) {
result.push(lease.resource())
}
}
Ok(result)
}