///|
pub fn Snapshot::entries(self : Snapshot) -> Array[Entry] {
self.records.copy()
}
///|
pub fn Snapshot::length(self : Snapshot) -> Int32 {
self.records.length()
}
///|
fn Snapshot::index(self : Snapshot, line : Int32) -> Int32 raise LockError {
for i, e in self.records {
if e.line == line {
return i
}
}
invalid(line, "no lock record at this source line")
-1
}
///|
pub fn Snapshot::entry(self : Snapshot, line : Int32) -> Entry raise LockError {
self.records[self.index(line)]
}
///|
/// Requested record first, then immediate blocker, ending at the granted root.
pub fn Snapshot::wait_chain(
self : Snapshot,
line : Int32,
) -> Array[Entry] raise LockError {
let result : Array[Entry] = []
let mut current = Some(self.index(line))
while current is Some(i) {
result.push(self.records[i])
current = self.parents[i]
}
result
}
///|
pub fn Snapshot::children(
self : Snapshot,
line : Int32,
) -> Array[Entry] raise LockError {
let target = self.index(line)
let result : Array[Entry] = []
for i, e in self.records {
if self.parents[i] == Some(target) {
result.push(e)
}
}
result
}
///|
/// Only roots with observed waiters. Rank by total descendants, then source line.
pub fn Snapshot::bottlenecks(self : Snapshot) -> Array[Bottleneck] {
let n = self.records.length()
let direct = Array::make(n, 0)
let total = Array::make(n, 0)
for i = n - 1; i >= 0; i = i - 1 {
if self.parents[i] is Some(parent) {
direct[parent] += 1
total[parent] += 1 + total[i]
}
}
let result : Array[Bottleneck] = []
for i, e in self.records {
if e.depth == 0 && total[i] > 0 {
result.push({
holder_line: e.line,
owner: e.owner,
direct_waiters: direct[i],
total_waiters: total[i],
})
}
}
result.sort_by((a, b) => {
if a.total_waiters == b.total_waiters {
a.holder_line.compare(b.holder_line)
} else {
b.total_waiters.compare(a.total_waiters)
}
})
result
}
///|
pub fn ByteRange::overlaps(
self : ByteRange,
other : ByteRange,
) -> Bool raise LockError {
self.validate()
other.validate()
!(self.end is Some(e) && e < other.start) &&
!(other.end is Some(e) && e < self.start)
}
///|
fn ByteRange::validate(self : ByteRange) -> Unit raise LockError {
if self.start < 0L || (self.end is Some(e) && e < self.start) {
invalid(0, "invalid inclusive byte range")
}
}
///|
/// Overlap evidence only: does not predict lock granting or cross-filesystem behavior.
pub fn Snapshot::query_range(
self : Snapshot,
key : FileKey,
range : ByteRange,
include_waiters~ : Bool = true,
) -> Array[Entry] raise LockError {
range.validate()
let result : Array[Entry] = []
for e in self.records {
if e.file == Some(key) &&
(include_waiters || e.depth == 0) &&
e.range.overlaps(range) {
result.push(e)
}
}
result
}
///|
pub fn Snapshot::to_json(self : Snapshot) -> Json {
{
"schema": "moonlocklens/1",
"entries": Json(self.records),
"bottlenecks": Json(self.bottlenecks()),
}
}