Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion crates/devkit-ports/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,11 @@ mod tests {
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
assert!(ready, "server never started listening");
assert!(
ready,
"server never started listening on {port}; its log:\n{}",
std::fs::read_to_string(&tmp).unwrap_or_else(|e| format!("unreadable: {e}"))
);

// Cleanup: stop the spawned pid and release the reservation.
if let Some(pid) = out[0].pid {
Expand Down
14 changes: 7 additions & 7 deletions crates/devkit-rules/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
//! Every reader loads the whole [`RuleIndex`] and filters it in memory, so
//! the file keeps each file and rule as JSON in index order, beside a `meta`
//! row naming the source kind, repository, remote and revision it holds. A
//! refresh replaces everything in one transaction, so a reader sees the
//! whole old index or the whole new one. A file whose format, kind,
//! repository or remote is not this cache's reads as no cache, and the next
//! refresh replaces it.
//! refresh replaces everything in one WAL-mode transaction, so a reader sees
//! the whole old index or the whole new one and never waits on a refresh.
//! A file whose format, kind, repository or remote is not this cache's reads
//! as no cache, and the next refresh replaces it.

use std::{
path::{Path, PathBuf},
Expand All @@ -22,8 +22,8 @@ use crate::model::{Rule, RuleFile, RuleIndex};
/// The layout this version of devkit reads and writes.
const FORMAT: i64 = 1;

/// How long a connection waits on another's lock: a reader on a refresh,
/// or one refresh on another.
/// How long a connection waits on another's lock, chiefly one refresh on
/// another.
const BUSY: Duration = Duration::from_secs(2);

const LAYOUT: &str = "
Expand Down Expand Up @@ -130,7 +130,7 @@ impl RuleCache {
}
let mut conn = Connection::open(&self.path).with_context(writing)?;
conn.busy_timeout(BUSY).with_context(writing)?;
conn.pragma_update(None, "journal_mode", "DELETE")
conn.pragma_update(None, "journal_mode", "WAL")
.with_context(writing)?;
let tx = conn
.transaction_with_behavior(TransactionBehavior::Immediate)
Expand Down
15 changes: 15 additions & 0 deletions crates/devkit-rules/tests/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,21 @@ fn at_state_dir_names_the_file_by_kind_and_repository() {
);
}

/// A reader reads the whole old index straight through a refresh that has
/// not committed, rather than waiting on it.
#[test]
fn a_refresh_in_progress_does_not_hold_up_a_reader() {
let dir = tempfile::tempdir().unwrap();
let cache = cache(dir.path(), "a");
cache.write(Write::Replace, 1, &index()).unwrap();
let mut refresh = rusqlite::Connection::open(cache.path()).unwrap();
let tx = refresh
.transaction_with_behavior(rusqlite::TransactionBehavior::Exclusive)
.unwrap();
tx.execute_batch("DROP TABLE rules").unwrap();
assert_eq!(value(&cache.read().unwrap().unwrap()), value(&index()));
Comment thread
AbysmalBiscuit marked this conversation as resolved.
Resolved
}

/// Whether `e` is SQLite giving up on a lock another writer held past the
/// busy timeout, which leaves the cache as it was.
fn is_busy(e: &anyhow::Error) -> bool {
Expand Down
Loading