use std::time::Duration; use domain::errors::DomainError; use sqlx::SqlitePool; /// Server and worker both migrate at startup, so two processes can race on the /// same database. The loser's migration fails (e.g. duplicate column) because /// the winner already applied it; re-running re-reads `_sqlx_migrations` and /// skips what is done. Only an error that persists across all retries is returned. const RETRY_BACKOFFS: [Duration; 5] = [ Duration::from_millis(100), Duration::from_millis(250), Duration::from_millis(500), Duration::from_secs(1), Duration::from_secs(1), ]; pub(crate) async fn run(pool: &SqlitePool) -> Result<(), DomainError> { let mut migrator = sqlx::migrate!("./migrations"); migrator.set_ignore_missing(true); let mut backoffs = RETRY_BACKOFFS.iter(); loop { match migrator.run(pool).await { Ok(()) => return Ok(()), Err(e) => match backoffs.next() { Some(delay) => { tracing::warn!("migration failed, retrying (concurrent migrator?): {e}"); tokio::time::sleep(*delay).await; } None => return Err(DomainError::InfrastructureError(e.to_string())), }, } } }