///|
/// One rollback stack shared by every layer of an instantiation.
///
/// Participants register inverse operations as they acquire state. Rollback
/// runs them in reverse order; commit discards them. Both terminal operations
/// are idempotent.
struct InstantiationTransaction {
rollbacks : Array[() -> Unit]
finalizers : Array[() -> Unit]
mut active : Bool
mut core_publication : Bool
}
///|
pub fn InstantiationTransaction::new() -> InstantiationTransaction {
{ rollbacks: [], finalizers: [], active: true, core_publication: false, }
}
///|
struct RollbackRegistrar {
transaction : InstantiationTransaction
}
///|
impl Debug for RollbackRegistrar with fn to_repr(_self) {
Repr::literal("rollback-registrar")
}
///|
pub fn InstantiationTransaction::registrar(
self : InstantiationTransaction,
) -> RollbackRegistrar {
{ transaction: self, }
}
///|
pub fn RollbackRegistrar::on_rollback(
self : RollbackRegistrar,
rollback : () -> Unit,
) -> Unit {
if self.transaction.active {
self.transaction.rollbacks.push(rollback)
}
}
///|
pub fn RollbackRegistrar::on_finish(
self : RollbackRegistrar,
finalizer : () -> Unit,
) -> Unit {
if self.transaction.active {
self.transaction.finalizers.push(finalizer)
}
}
///|
pub fn InstantiationTransaction::commit(
self : InstantiationTransaction,
) -> Unit {
if self.active {
self.active = false
self.rollbacks.clear()
self.rollbacks.shrink_to_fit()
for finalizer in self.finalizers {
finalizer()
}
self.finalizers.clear()
self.finalizers.shrink_to_fit()
}
}
///|
pub fn InstantiationTransaction::rollback(
self : InstantiationTransaction,
) -> Unit {
if !self.active {
return
}
self.active = false
let mut index = self.rollbacks.length()
while index > 0 {
index -= 1
self.rollbacks[index]()
}
self.rollbacks.clear()
self.rollbacks.shrink_to_fit()
for finalizer in self.finalizers {
finalizer()
}
self.finalizers.clear()
self.finalizers.shrink_to_fit()
}
///|
/// Join an enclosing component transaction, or own rollback for a Core entry.
pub fn[T, E : Error] Store::with_instantiation(
self : Store,
run : () -> T raise E,
) -> T raise E {
if self.instantiation is Some(_) {
return run()
}
let transaction = InstantiationTransaction::new()
transaction.core_publication = true
self.register_rollback(transaction.registrar())
defer transaction.rollback()
let result = run()
transaction.commit()
result
}
///|
pub async fn[T, E : Error] Store::with_async_instantiation(
self : Store,
run : async () -> T raise E,
) -> T raise E {
if self.instantiation is Some(_) {
return run()
}
let transaction = InstantiationTransaction::new()
transaction.core_publication = true
self.register_rollback(transaction.registrar())
defer transaction.rollback()
let result = run()
transaction.commit()
result
}
///|
/// Core segment/start traps preserve prior writes to imported resources. Once
/// an instance can escape through those writes, keep its addresses and budget
/// alive. An enclosing Component transaction keeps its own commit boundary.
pub fn Store::publish_core_instance(self : Store) -> Unit {
if self.instantiation is Some(registrar) &&
registrar.transaction.core_publication {
registrar.transaction.commit()
}
}