Skip to content
Open
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
17 changes: 17 additions & 0 deletions src/include/duckdb/storage/buffer/block_handle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,20 @@ class BlockMemory : public enable_shared_from_this<BlockMemory> {
idx_t NextEvictionSequenceNumber() {
return ++eviction_seq_num;
}
//! Returns true if the block needs to be added to the eviction queue.
//! A block needs insertion when it has no valid entry in the queue (freshly loaded from disk).
//! Once added, the entry remains valid across pin/unpin cycles until the block is evicted.
bool NeedsEvictionQueueInsertion() const {
return needs_eviction_queue_insert.load(std::memory_order_relaxed);
}
//! Marks the block as needing eviction queue insertion (called when loaded from disk).
void MarkNeedsEvictionQueueInsert() {
needs_eviction_queue_insert.store(true, std::memory_order_relaxed);
}
//! Clears the flag (called after successfully adding to the eviction queue).
void ClearNeedsEvictionQueueInsert() {
needs_eviction_queue_insert.store(false, std::memory_order_relaxed);
}
//! Get the LRU timestamp.
int64_t GetLRUTimestamp() const {
return lru_timestamp_msec;
Expand Down Expand Up @@ -213,6 +227,9 @@ class BlockMemory : public enable_shared_from_this<BlockMemory> {
unique_ptr<FileBuffer> buffer;
//! The internal eviction sequence number.
atomic<idx_t> eviction_seq_num;
//! Whether the block needs to be added to the eviction queue.
//! True when block has no valid queue entry (initial state, or after eviction/unload).
atomic<bool> needs_eviction_queue_insert {true};
//! The LRU timestamp for age-based eviction.
atomic<int64_t> lru_timestamp_msec;
//! When to destroy the data buffer.
Expand Down
1 change: 1 addition & 0 deletions src/storage/buffer/block_handle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ BufferHandle BlockHandle::Load(QueryContext context, unique_ptr<FileBuffer> reus
}
memory.SetState(BlockState::BLOCK_LOADED);
memory.SetReaders(1);
memory.MarkNeedsEvictionQueueInsert();
return BufferHandle(shared_from_this(), memory.GetBuffer());
}

Expand Down
25 changes: 19 additions & 6 deletions src/storage/buffer/buffer_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ shared_ptr<BlockMemory> BufferEvictionNode::TryGetBlockMemory() {
// The block memory has been destroyed.
return nullptr;
}
if (!CanUnload(*shared_memory_p)) {
// The memory handle was used in between.
if (handle_sequence_number != shared_memory_p->GetEvictionSequenceNumber()) {
// A newer queue entry supersedes this one.
return nullptr;
}
// The node is the latest node in the queue with this memory.
// The node is the latest entry in the queue for this memory.
return shared_memory_p;
}

Expand Down Expand Up @@ -273,6 +273,13 @@ bool BufferPool::AddToEvictionQueue(shared_ptr<BlockHandle> &handle) {
// The block handle is locked during this operation (Unpin),
// or the block handle is still a local variable (ConvertToPersistent)
D_ASSERT(memory.GetReaders() == 0);

// If the block was not pinned since it was last added to the eviction queue,
// its previous queue entry is still valid — skip re-insertion to avoid creating dead nodes.
if (!memory.NeedsEvictionQueueInsertion()) {
return false;
}

auto ts = memory.NextEvictionSequenceNumber();
if (track_eviction_timestamps) {
memory.SetLRUTimestamp(std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now())
Expand All @@ -287,6 +294,7 @@ bool BufferPool::AddToEvictionQueue(shared_ptr<BlockHandle> &handle) {

// Get the eviction queue for the block and add it
BufferEvictionNode node(handle->GetMemoryWeak(), ts);
memory.ClearNeedsEvictionQueueInsert();
return queue.AddToEvictionQueue(std::move(node));
}

Expand Down Expand Up @@ -468,7 +476,7 @@ void EvictionQueue::IterateUnloadableBlocks(FN fn) {
}

// get a reference to the underlying block pointer
auto handle = node.TryGetBlockMemory();
auto handle = node.memory_p.lock();
if (debug_sleep_micros > 0) {
// Debug race conditions regarding the ownership of the BlockMemory.
// Note that for this to trigger we need at least one purge iteration with the setting active.
Expand All @@ -482,8 +490,13 @@ void EvictionQueue::IterateUnloadableBlocks(FN fn) {
// we might be able to free this block: grab the mutex and check if we can free it
auto lock = handle->GetLock();
if (!node.CanUnload(*handle)) {
// something changed in the mean-time, bail out
DecrementDeadNodes();
if (node.handle_sequence_number == handle->GetEvictionSequenceNumber()) {
// The entry was valid (seq_num matches) but the block can't be evicted right now.
// Mark it for re-insertion on the next unpin since we consumed its queue entry.
handle->MarkNeedsEvictionQueueInsert();
} else {
DecrementDeadNodes();
}
continue;
}

Expand Down
Loading