use std::sync::atomic::Ordering;
use std::sync::Arc;
use super::TransactionManager;
use crate::context::TransactionContext;
use crate::types::*;
use linkrs_core::event_dispatch::{EventFilter, EventSubscriptions, SubscriptionId};
impl TransactionManager {
fn is_commit_event(event: &TransactionEvent) -> bool {
matches!(
event,
TransactionEvent::Committed { .. }
| TransactionEvent::CommitDurableButUnfinalized { .. }
)
}
fn is_rollback_event(event: &TransactionEvent) -> bool {
matches!(event, TransactionEvent::Aborted { .. })
}
fn is_budget_warning_event(event: &TransactionEvent) -> bool {
matches!(event, TransactionEvent::BudgetWarning { .. })
}
pub fn register_txn_callback(&self, callback: TxnCallback) -> SubscriptionId {
self.txn_callbacks.add(callback)
}
pub fn register_txn_callback_filtered(
&self,
callback: TxnCallback,
filter: EventFilter<TransactionEvent>,
) -> SubscriptionId {
self.txn_callbacks.add_filtered(callback, Some(filter))
}
pub fn unregister_txn_callback(&self, id: SubscriptionId) -> bool {
self.txn_callbacks.remove(id)
}
pub fn txn_callback_count(&self) -> usize {
self.txn_callbacks.len()
}
pub fn shared_txn_callbacks(&self) -> Arc<EventSubscriptions<TransactionEvent>> {
Arc::clone(&self.txn_callbacks)
}
pub fn register_commit_veto(&self, callback: CommitVetoCallback) -> SubscriptionId {
let id = self.next_veto_id.fetch_add(1, Ordering::SeqCst);
self.commit_vetoes.write().push((id, callback));
id
}
pub fn unregister_commit_veto(&self, id: SubscriptionId) -> bool {
let mut vetoes = self.commit_vetoes.write();
let before = vetoes.len();
vetoes.retain(|(entry_id, _)| *entry_id != id);
vetoes.len() != before
}
pub fn commit_veto_count(&self) -> usize {
self.commit_vetoes.read().len()
}
pub(crate) fn eval_commit_vetoes(&self, context: &TransactionContext) -> Option<String> {
let vetoes: Vec<CommitVetoCallback> = {
let guard = self.commit_vetoes.read();
if guard.is_empty() {
return None;
}
guard.iter().map(|(_, cb)| Arc::clone(cb)).collect()
};
let view = CommitVetoContext {
txn_id: context.id,
write_timestamp: context.timestamp(),
};
for (index, callback) in vetoes.iter().enumerate() {
let outcome =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(&view)));
match outcome {
Ok(decision) if decision.veto => {
let reason = decision
.reason
.unwrap_or_else(|| "commit vetoed".to_string());
log::warn!(
"commit veto #{} blocked transaction {}: {}",
index,
view.txn_id,
reason,
);
return Some(reason);
}
Ok(_) => {}
Err(_) => {
log::error!("commit veto #{} panicked; treating as allow", index,);
}
}
}
None
}
pub fn register_commit_callback(&self, callback: CommitCallback) -> SubscriptionId {
self.txn_callbacks
.add_filtered(callback, Some(Arc::new(Self::is_commit_event)))
}
pub fn register_commit_callback_filtered(
&self,
callback: CommitCallback,
filter: EventFilter<TransactionEvent>,
) -> SubscriptionId {
let combined: EventFilter<TransactionEvent> =
Arc::new(move |event| Self::is_commit_event(event) && filter(event));
self.txn_callbacks.add_filtered(callback, Some(combined))
}
pub fn unregister_commit_callback(&self, id: SubscriptionId) -> bool {
self.txn_callbacks.remove(id)
}
pub fn commit_callback_count(&self) -> usize {
self.txn_callbacks.len()
}
pub fn register_rollback_callback(&self, callback: RollbackCallback) -> SubscriptionId {
self.txn_callbacks
.add_filtered(callback, Some(Arc::new(Self::is_rollback_event)))
}
pub fn register_rollback_callback_filtered(
&self,
callback: RollbackCallback,
filter: EventFilter<TransactionEvent>,
) -> SubscriptionId {
let combined: EventFilter<TransactionEvent> =
Arc::new(move |event| Self::is_rollback_event(event) && filter(event));
self.txn_callbacks.add_filtered(callback, Some(combined))
}
pub fn unregister_rollback_callback(&self, id: SubscriptionId) -> bool {
self.txn_callbacks.remove(id)
}
pub fn rollback_callback_count(&self) -> usize {
self.txn_callbacks.len()
}
pub fn register_budget_warning_callback(&self, callback: TxnCallback) -> SubscriptionId {
self.txn_callbacks
.add_filtered(callback, Some(Arc::new(Self::is_budget_warning_event)))
}
pub fn register_budget_warning_callback_filtered(
&self,
callback: TxnCallback,
filter: EventFilter<TransactionEvent>,
) -> SubscriptionId {
let combined: EventFilter<TransactionEvent> =
Arc::new(move |event| Self::is_budget_warning_event(event) && filter(event));
self.txn_callbacks.add_filtered(callback, Some(combined))
}
pub fn unregister_budget_warning_callback(&self, id: SubscriptionId) -> bool {
self.txn_callbacks.remove(id)
}
pub(crate) fn emit_commit_event(&self, event: TransactionEvent) {
if self.txn_callbacks.is_empty() {
return;
}
let outcome = self.txn_callbacks.dispatch_detailed("commit", &event);
self.stats
.record_hook_dispatch(outcome.delivered, outcome.panics);
for _ in 0..outcome.panics {
self.stats.increment_cleanup_failure();
}
}
pub(crate) fn emit_rollback_event(&self, event: TransactionEvent) {
if self.txn_callbacks.is_empty() {
return;
}
let outcome = self.txn_callbacks.dispatch_detailed("rollback", &event);
self.stats
.record_hook_dispatch(outcome.delivered, outcome.panics);
for _ in 0..outcome.panics {
self.stats.increment_cleanup_failure();
}
}
pub(crate) fn emit_budget_warning_event(&self, event: TransactionEvent) {
if self.txn_callbacks.is_empty() {
return;
}
let outcome = self.txn_callbacks.dispatch_detailed("txn-budget", &event);
self.stats
.record_hook_dispatch(outcome.delivered, outcome.panics);
for _ in 0..outcome.panics {
self.stats.increment_cleanup_failure();
}
}
pub fn drain_context_budget_warnings(&self, context: &TransactionContext) {
for event in context.drain_budget_warnings() {
self.emit_budget_warning_event(event);
}
}
}