From 284020328b41ca8e5bdd597d5f765511b73cec3c Mon Sep 17 00:00:00 2001 From: Dhruv Arya Date: Tue, 23 Jun 2026 00:59:49 +0000 Subject: [PATCH] fix: avoid lost-wakeup hang in DeleteFileIndex::get_deletes_for_data_file DeleteFileIndex signals completion of its background populating task with tokio::sync::Notify::notify_waiters(), which stores no permit and only wakes Notified futures that already exist. get_deletes_for_data_file observed the Populating state, released the read lock, and only then created the Notified future; if the populator flipped the state to Populated and called notify_waiters() in that window, the wakeup was lost and the query hung forever. Create the Notified future (via notified_owned) while still holding the read lock. The populator cannot call notify_waiters() until it takes the write lock, which is blocked while the read lock is held, so the notification is guaranteed to occur after the future exists and is delivered. Signed-off-by: Dhruv Arya --- crates/iceberg/src/delete_file_index.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/iceberg/src/delete_file_index.rs b/crates/iceberg/src/delete_file_index.rs index bdeab4aea7..380ec3f3db 100644 --- a/crates/iceberg/src/delete_file_index.rs +++ b/crates/iceberg/src/delete_file_index.rs @@ -86,17 +86,21 @@ impl DeleteFileIndex { data_file: &DataFile, seq_num: Option, ) -> Vec { - let notifier = { + // Create the `Notified` while holding the read lock. `notify_waiters()` stores no + // permit and only wakes futures created before it; the populator can't signal until + // it takes the write lock (blocked by this read lock), so the notification is + // guaranteed to land after creation. `notified_owned` lets it outlive the guard. + let notified = { let guard = self.state.read().unwrap(); - match *guard { - DeleteFileIndexState::Populating(ref notifier) => notifier.clone(), - DeleteFileIndexState::Populated(ref index) => { + match &*guard { + DeleteFileIndexState::Populating(notifier) => notifier.clone().notified_owned(), + DeleteFileIndexState::Populated(index) => { return index.get_deletes_for_data_file(data_file, seq_num); } } }; - notifier.notified().await; + notified.await; let guard = self.state.read().unwrap(); match guard.deref() {