huskies: merge 1194 bug db shadow-table tests flake when SHADOW_DB_PATH is not initialized
This commit is contained in:
@@ -14,6 +14,7 @@ use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::sync::OnceLock;
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// One migration row in the live database that is not in the compiled-in set.
|
||||
@@ -66,18 +67,30 @@ pub(crate) static PIPELINE_DB: OnceLock<PipelineDb> = OnceLock::new();
|
||||
/// which test won the `PIPELINE_DB` init race.
|
||||
pub(crate) static SHADOW_DB_PATH: OnceLock<std::path::PathBuf> = OnceLock::new();
|
||||
|
||||
/// Guards the body of [`init`] so concurrent callers race safely.
|
||||
///
|
||||
/// Before this existed, `PIPELINE_DB` and `SHADOW_DB_PATH` were two separate
|
||||
/// `OnceLock`s set at different points in the function (one right at the
|
||||
/// start, one only after the pool/migrations/write-task were fully set up).
|
||||
/// Two callers racing past the old top-of-function check could each open
|
||||
/// their own SQLite file, with one call's `SHADOW_DB_PATH` winning while a
|
||||
/// *different* call's `PIPELINE_DB` (and its background writer) won —
|
||||
/// leaving readers pointed at a file that the writer never touched
|
||||
/// (story 1194). `get_or_try_init` ensures only the first caller runs the
|
||||
/// body below; every other caller — even racing with a different `db_path`
|
||||
/// — awaits and observes that same result.
|
||||
static INIT: OnceCell<()> = OnceCell::const_new();
|
||||
|
||||
/// Initialise the pipeline database.
|
||||
///
|
||||
/// Opens (or creates) the SQLite file at `db_path`, runs embedded migrations,
|
||||
/// loads existing story content into the in-memory store, and spawns the
|
||||
/// background write task. Safe to call only once; subsequent calls are no-ops.
|
||||
pub async fn init(db_path: &Path) -> Result<(), sqlx::Error> {
|
||||
if PIPELINE_DB.get().is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
// Record the path before doing any real work so tests can always find the
|
||||
// correct file even if two callers race — the OnceLock ensures only one
|
||||
// path wins, and whichever wins will also win the PIPELINE_DB set below.
|
||||
INIT.get_or_try_init(|| async {
|
||||
// Record the path before doing any real work so tests can always
|
||||
// find the correct file — this closure is guaranteed to run at most
|
||||
// once, so there is no longer a race with the `PIPELINE_DB` set below.
|
||||
let _ = SHADOW_DB_PATH.set(db_path.to_path_buf());
|
||||
|
||||
// Story 1087: before running the migration that splits `stage` into
|
||||
@@ -162,6 +175,9 @@ pub async fn init(db_path: &Path) -> Result<(), sqlx::Error> {
|
||||
});
|
||||
|
||||
let _ = PIPELINE_DB.set(PipelineDb { tx });
|
||||
Ok::<(), sqlx::Error>(())
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user