///|
/// An append-only record of operations released by a causal buffer.
pub(all) struct LoggedOperation {
operation : CausalOperation
applied_at : Int
} derive(Debug)
///| A deterministic replay log. It deliberately stores the original causal
///| operation rather than a callback, so the trace remains serializable by a
///|
/// host application and reproducible in tests.
pub struct OperationLog {
entries : Array[LoggedOperation]
} derive(Debug)
///|
pub fn OperationLog::new() -> OperationLog {
{ entries: [] }
}
///|
pub fn OperationLog::entries(self : OperationLog) -> Array[LoggedOperation] {
let output : Array[LoggedOperation] = []
for entry in self.entries {
output.push(entry)
}
output
}
///|
pub fn OperationLog::length(self : OperationLog) -> Int {
self.entries.length()
}
///|
/// Append operations in their already-validated causal release order.
pub fn OperationLog::append(
self : OperationLog,
operations : Array[CausalOperation],
applied_at : Int,
) -> OperationLog {
let entries : Array[LoggedOperation] = []
for entry in self.entries {
entries.push(entry)
}
for operation in operations {
entries.push({ operation, applied_at })
}
{ entries, }
}
///|
/// Return a checkpointable prefix whose operations are covered by `frontier`.
pub fn OperationLog::stable_prefix(
self : OperationLog,
frontier : VersionVector,
) -> Array[LoggedOperation] {
let output : Array[LoggedOperation] = []
for entry in self.entries {
if entry.operation.counter <= frontier.counter(entry.operation.replica) {
output.push(entry)
} else {
return output
}
}
output
}
///| Split the log at the stable frontier. The returned prefix may be folded
///| into a durable checkpoint; the returned log contains exactly the entries
///|
/// that still need replay. The original log is unchanged.
pub fn OperationLog::compact(
self : OperationLog,
frontier : VersionVector,
) -> (Array[LoggedOperation], OperationLog) {
let stable = self.stable_prefix(frontier)
let remaining : Array[LoggedOperation] = []
for index in stable.length()..