use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::{Context, Result};
use parking_lot::Mutex;
use super::{ExecuteResult, SqliteBatchStatement, SqliteDb};
use crate::actor::internal_storage::schema::HAS_INTERNAL_SCHEMA_SQL;
#[derive(Default)]
pub(super) struct DeferredBootstrap {
statements: Mutex<Option<Vec<SqliteBatchStatement>>>,
staged: AtomicBool,
verify: AtomicBool,
carrier: Mutex<Option<String>>,
}
impl SqliteDb {
pub(crate) fn defer_bootstrap(&self, statements: Vec<SqliteBatchStatement>) {
*self.deferred_bootstrap.statements.lock() = Some(statements);
self.deferred_bootstrap
.verify
.store(false, Ordering::Release);
self.deferred_bootstrap
.staged
.store(true, Ordering::Release);
}
pub(crate) fn has_deferred_bootstrap(&self) -> bool {
self.deferred_bootstrap.staged.load(Ordering::Acquire)
}
pub(super) async fn commit_deferred_bootstrap(&self) -> Result<()> {
if !self.has_deferred_bootstrap() {
return Ok(());
}
let transaction = self
.begin_transaction(None)
.await
.context("begin deferred sqlite bootstrap")?;
transaction
.commit()
.await
.context("commit deferred sqlite bootstrap")
}
pub(super) async fn apply_deferred_bootstrap(&self, key: &str) -> Result<()> {
if !self.has_deferred_bootstrap() {
return Ok(());
}
let Some(statements) = self.deferred_bootstrap.statements.lock().clone() else {
return Ok(());
};
let result = self.apply_bootstrap_statements(key, statements).await;
if let Err(error) = result {
if let Err(rollback_error) = self.finish_transaction_inner(key, false).await {
tracing::warn!(
?rollback_error,
"rollback after a failed deferred sqlite bootstrap failed; the transaction was likely already released"
);
}
return Err(error.context("apply deferred sqlite bootstrap"));
}
Ok(())
}
async fn apply_bootstrap_statements(
&self,
key: &str,
statements: Vec<SqliteBatchStatement>,
) -> Result<()> {
if self.deferred_bootstrap.verify.load(Ordering::Acquire) {
let probe = self
.transaction_execute_inner(key, HAS_INTERNAL_SCHEMA_SQL.to_owned(), None)
.await
.context("check for a schema an earlier bootstrap may have committed")?;
if !probe.rows.is_empty() {
self.clear_deferred_bootstrap();
return Ok(());
}
}
self.deferred_bootstrap
.verify
.store(true, Ordering::Release);
*self.deferred_bootstrap.carrier.lock() = Some(key.to_owned());
self.transaction_execute_statements_inner(key, statements)
.await
}
pub(super) async fn remote_execute_batch_with_bootstrap(
&self,
statements: Vec<SqliteBatchStatement>,
) -> Result<Vec<ExecuteResult>> {
let bootstrap = self.deferred_bootstrap_for_batch();
let Some(bootstrap) = bootstrap else {
let _guard = self.begin_regular_operation().await?;
return self.remote_execute_batch(statements).await;
};
let _guard = self.begin_admitted_operation().await?;
if !self.has_deferred_bootstrap() {
return self.remote_execute_batch(statements).await;
}
let carried = bootstrap.len();
let mut combined = bootstrap;
combined.extend(statements);
let mut results = self.remote_execute_batch(combined).await?;
self.clear_deferred_bootstrap();
Ok(results.split_off(carried))
}
fn deferred_bootstrap_for_batch(&self) -> Option<Vec<SqliteBatchStatement>> {
if !self.has_deferred_bootstrap() || self.deferred_bootstrap.verify.load(Ordering::Acquire)
{
return None;
}
let statements = self.deferred_bootstrap.statements.lock().clone()?;
self.deferred_bootstrap
.verify
.store(true, Ordering::Release);
Some(statements)
}
pub(super) fn deferred_bootstrap_committed(&self, key: &str) {
let carried = self.deferred_bootstrap.carrier.lock().as_deref() == Some(key);
if carried {
self.clear_deferred_bootstrap();
}
}
fn clear_deferred_bootstrap(&self) {
self.deferred_bootstrap
.staged
.store(false, Ordering::Release);
self.deferred_bootstrap.statements.lock().take();
self.deferred_bootstrap.carrier.lock().take();
self.deferred_bootstrap
.verify
.store(false, Ordering::Release);
}
}