Merge 'Fix clear_page_cache method and rollback' from Preston Thorpe

Previously we were iterating over every entry in the page cache,
clearing the dirty flag from each page.

Reviewed-by: Pere Diaz Bou <pere-altea@homail.com>
Reviewed-by: Nikita Sivukhin (@sivukhin)

Closes #2988
This commit is contained in:
Jussi Saurio
2025-09-10 11:11:37 +03:00
committed by GitHub
2 changed files with 31 additions and 26 deletions

View File

@@ -528,20 +528,36 @@ impl PageCache {
}
pub fn clear(&mut self) -> Result<(), CacheError> {
for e in self.entries.iter() {
if self.map.len() == 0 {
// Fast path: nothing to do.
self.clock_hand = NULL;
return Ok(());
}
for node in self.map.iter() {
let e = &self.entries[node.slot_index];
if let Some(ref p) = e.page {
if p.is_dirty() {
return Err(CacheError::Dirty { pgno: p.get().id });
}
}
}
let mut used_slots = Vec::with_capacity(self.map.len());
for node in self.map.iter() {
used_slots.push(node.slot_index);
}
// don't touch already-free slots at all.
for &i in &used_slots {
if let Some(p) = self.entries[i].page.take() {
p.clear_loaded();
let _ = p.get().contents.take();
}
self.entries[i].clear_ref();
self.entries[i].reset_links();
}
self.entries.fill(PageCacheEntry::empty());
self.map.clear();
self.clock_hand = NULL;
self.freelist.clear();
for i in (0..self.capacity).rev() {
self.map = PageHashMap::new(self.capacity);
for &i in used_slots.iter().rev() {
self.freelist.push(i);
}
Ok(())
@@ -631,16 +647,6 @@ impl PageCache {
self.capacity
}
pub fn unset_dirty_all_pages(&mut self) {
let entries = &self.entries;
for entry in entries.iter() {
if entry.page.is_none() {
continue;
}
entry.page.as_ref().unwrap().clear_dirty();
}
}
#[cfg(test)]
fn verify_cache_integrity(&self) {
let map = &self.map;

View File

@@ -1563,12 +1563,15 @@ impl Pager {
/// of a rollback or in case we want to invalidate page cache after starting a read transaction
/// right after new writes happened which would invalidate current page cache.
pub fn clear_page_cache(&self) {
self.dirty_pages.borrow_mut().clear();
self.page_cache.write().unset_dirty_all_pages();
self.page_cache
.write()
.clear()
.expect("Failed to clear page cache");
let dirty_pages = self.dirty_pages.borrow();
let mut cache = self.page_cache.write();
for page_id in dirty_pages.iter() {
let page_key = PageCacheKey::new(*page_id);
if let Some(page) = cache.get(&page_key).unwrap_or(None) {
page.clear_dirty();
}
}
cache.clear().expect("Failed to clear page cache");
}
/// Checkpoint in Truncate mode and delete the WAL file. This method is _only_ to be called
@@ -2118,6 +2121,7 @@ impl Pager {
is_write: bool,
) -> Result<(), LimboError> {
tracing::debug!(schema_did_change);
self.clear_page_cache();
if is_write {
self.dirty_pages.borrow_mut().clear();
} else {
@@ -2126,12 +2130,7 @@ impl Pager {
"dirty pages should be empty for read txn"
);
}
let mut cache = self.page_cache.write();
self.reset_internal_states();
cache.unset_dirty_all_pages();
cache.clear().expect("failed to clear page cache");
if schema_did_change {
connection.schema.replace(connection._db.clone_schema()?);
}