mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
Simplify db tests
This commit is contained in:
parent
4d91409bbc
commit
322ebc33d1
@ -7,7 +7,6 @@ use anyhow::Context;
|
|||||||
use gpui::AppContext;
|
use gpui::AppContext;
|
||||||
pub use indoc::indoc;
|
pub use indoc::indoc;
|
||||||
pub use lazy_static;
|
pub use lazy_static;
|
||||||
use parking_lot::{Mutex, RwLock};
|
|
||||||
pub use smol;
|
pub use smol;
|
||||||
pub use sqlez;
|
pub use sqlez;
|
||||||
pub use sqlez_macros;
|
pub use sqlez_macros;
|
||||||
@ -17,11 +16,9 @@ pub use util::paths::DB_DIR;
|
|||||||
use sqlez::domain::Migrator;
|
use sqlez::domain::Migrator;
|
||||||
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
use sqlez::thread_safe_connection::ThreadSafeConnection;
|
||||||
use sqlez_macros::sql;
|
use sqlez_macros::sql;
|
||||||
use std::fs::create_dir_all;
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
use util::channel::ReleaseChannel;
|
use util::channel::ReleaseChannel;
|
||||||
use util::{async_iife, ResultExt};
|
use util::{async_iife, ResultExt};
|
||||||
|
|
||||||
@ -42,10 +39,8 @@ const DB_FILE_NAME: &'static str = "db.sqlite";
|
|||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty());
|
pub static ref ZED_STATELESS: bool = std::env::var("ZED_STATELESS").map_or(false, |v| !v.is_empty());
|
||||||
pub static ref BACKUP_DB_PATH: RwLock<Option<PathBuf>> = RwLock::new(None);
|
|
||||||
pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false);
|
pub static ref ALL_FILE_DB_FAILED: AtomicBool = AtomicBool::new(false);
|
||||||
}
|
}
|
||||||
static DB_FILE_OPERATIONS: Mutex<()> = Mutex::new(());
|
|
||||||
|
|
||||||
/// Open or create a database at the given directory path.
|
/// Open or create a database at the given directory path.
|
||||||
/// This will retry a couple times if there are failures. If opening fails once, the db directory
|
/// This will retry a couple times if there are failures. If opening fails once, the db directory
|
||||||
@ -63,66 +58,14 @@ pub async fn open_db<M: Migrator + 'static>(
|
|||||||
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
|
let main_db_dir = db_dir.join(Path::new(&format!("0-{}", release_channel_name)));
|
||||||
|
|
||||||
let connection = async_iife!({
|
let connection = async_iife!({
|
||||||
// Note: This still has a race condition where 1 set of migrations succeeds
|
smol::fs::create_dir_all(&main_db_dir)
|
||||||
// (e.g. (Workspace, Editor)) and another fails (e.g. (Workspace, Terminal))
|
.await
|
||||||
// This will cause the first connection to have the database taken out
|
.context("Could not create db directory")
|
||||||
// from under it. This *should* be fine though. The second dabatase failure will
|
.log_err()?;
|
||||||
// cause errors in the log and so should be observed by developers while writing
|
|
||||||
// soon-to-be good migrations. If user databases are corrupted, we toss them out
|
|
||||||
// and try again from a blank. As long as running all migrations from start to end
|
|
||||||
// on a blank database is ok, this race condition will never be triggered.
|
|
||||||
//
|
|
||||||
// Basically: Don't ever push invalid migrations to stable or everyone will have
|
|
||||||
// a bad time.
|
|
||||||
|
|
||||||
// If no db folder, create one at 0-{channel}
|
|
||||||
create_dir_all(&main_db_dir).context("Could not create db directory")?;
|
|
||||||
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
|
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
|
||||||
|
open_main_db(&db_path).await
|
||||||
// Optimistically open databases in parallel
|
})
|
||||||
if !DB_FILE_OPERATIONS.is_locked() {
|
.await;
|
||||||
// Try building a connection
|
|
||||||
if let Some(connection) = open_main_db(&db_path).await {
|
|
||||||
return Ok(connection)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take a lock in the failure case so that we move the db once per process instead
|
|
||||||
// of potentially multiple times from different threads. This shouldn't happen in the
|
|
||||||
// normal path
|
|
||||||
let _lock = DB_FILE_OPERATIONS.lock();
|
|
||||||
if let Some(connection) = open_main_db(&db_path).await {
|
|
||||||
return Ok(connection)
|
|
||||||
};
|
|
||||||
|
|
||||||
let backup_timestamp = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.expect("System clock is set before the unix timestamp, Zed does not support this region of spacetime")
|
|
||||||
.as_millis();
|
|
||||||
|
|
||||||
// If failed, move 0-{channel} to {current unix timestamp}-{channel}
|
|
||||||
let backup_db_dir = db_dir.join(Path::new(&format!(
|
|
||||||
"{}-{}",
|
|
||||||
backup_timestamp,
|
|
||||||
release_channel_name,
|
|
||||||
)));
|
|
||||||
|
|
||||||
std::fs::rename(&main_db_dir, &backup_db_dir)
|
|
||||||
.context("Failed clean up corrupted database, panicking.")?;
|
|
||||||
|
|
||||||
// Set a static ref with the failed timestamp and error so we can notify the user
|
|
||||||
{
|
|
||||||
let mut guard = BACKUP_DB_PATH.write();
|
|
||||||
*guard = Some(backup_db_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new 0-{channel}
|
|
||||||
create_dir_all(&main_db_dir).context("Should be able to create the database directory")?;
|
|
||||||
let db_path = main_db_dir.join(Path::new(DB_FILE_NAME));
|
|
||||||
|
|
||||||
// Try again
|
|
||||||
open_main_db(&db_path).await.context("Could not newly created db")
|
|
||||||
}).await.log_err();
|
|
||||||
|
|
||||||
if let Some(connection) = connection {
|
if let Some(connection) = connection {
|
||||||
return connection;
|
return connection;
|
||||||
@ -249,13 +192,13 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::{fs, thread};
|
use std::thread;
|
||||||
|
|
||||||
use sqlez::{connection::Connection, domain::Domain};
|
use sqlez::domain::Domain;
|
||||||
use sqlez_macros::sql;
|
use sqlez_macros::sql;
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
|
|
||||||
use crate::{open_db, DB_FILE_NAME};
|
use crate::open_db;
|
||||||
|
|
||||||
// Test bad migration panics
|
// Test bad migration panics
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
@ -321,31 +264,10 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.is_none()
|
.is_none()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut corrupted_backup_dir = fs::read_dir(tempdir.path())
|
|
||||||
.unwrap()
|
|
||||||
.find(|entry| {
|
|
||||||
!entry
|
|
||||||
.as_ref()
|
|
||||||
.unwrap()
|
|
||||||
.file_name()
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.starts_with("0")
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
.unwrap()
|
|
||||||
.path();
|
|
||||||
corrupted_backup_dir.push(DB_FILE_NAME);
|
|
||||||
|
|
||||||
let backup = Connection::open_file(&corrupted_backup_dir.to_string_lossy());
|
|
||||||
assert!(backup.select_row::<usize>("SELECT * FROM test").unwrap()()
|
|
||||||
.unwrap()
|
|
||||||
.is_none());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test that DB exists but corrupted (causing recreate)
|
/// Test that DB exists but corrupted (causing recreate)
|
||||||
#[gpui::test]
|
#[gpui::test(iterations = 30)]
|
||||||
async fn test_simultaneous_db_corruption() {
|
async fn test_simultaneous_db_corruption() {
|
||||||
enum CorruptedDB {}
|
enum CorruptedDB {}
|
||||||
|
|
||||||
|
@ -3502,27 +3502,11 @@ fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut Asy
|
|||||||
if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
|
if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
|
||||||
workspace.show_notification_once(0, cx, |cx| {
|
workspace.show_notification_once(0, cx, |cx| {
|
||||||
cx.add_view(|_| {
|
cx.add_view(|_| {
|
||||||
MessageNotification::new("Failed to load any database file.")
|
MessageNotification::new("Failed to load the database file.")
|
||||||
.with_click_message("Click to let us know about this error")
|
.with_click_message("Click to let us know about this error")
|
||||||
.on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
|
.on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
let backup_path = (*db::BACKUP_DB_PATH).read();
|
|
||||||
if let Some(backup_path) = backup_path.clone() {
|
|
||||||
workspace.show_notification_once(1, cx, move |cx| {
|
|
||||||
cx.add_view(move |_| {
|
|
||||||
MessageNotification::new(format!(
|
|
||||||
"Database file was corrupted. Old database backed up to {}",
|
|
||||||
backup_path.display()
|
|
||||||
))
|
|
||||||
.with_click_message("Click to show old database in finder")
|
|
||||||
.on_click(move |cx| {
|
|
||||||
cx.platform().open_url(&backup_path.to_string_lossy())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
Loading…
Reference in New Issue
Block a user