This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-3268-36eae2969b0773ee5b6f23d9be230fd0a17ffe92 in repository https://gitbox.apache.org/repos/asf/iceberg-rust.git
commit de19a3986fbdf399f09f71cd8c08e2573630c80f Author: Dhruv Arya <[email protected]> AuthorDate: Thu Oct 1 08:00:37 2026 +0000 fix: avoid lost-wakeup hang in get_equality_delete_predicate_for_delete_file_path (#3268) * fix: avoid lost-wakeup hang in get_equality_delete_predicate_for_delete_file_path The equality-delete loader signals completion with Notify::notify_waiters(), which stores no permit and only wakes Notified futures created before the call. get_equality_delete_predicate_for_delete_file_path observed the Loading state, released the read lock, and only then created the Notified future; a notify_waiters() fired in that window is lost and the await hangs forever. Create the Notified (via notified_owned) while still holding the read lock, so it is guaranteed to precede any notify_waiters() and be delivered. Signed-off-by: Dhruv Arya <[email protected]> * docs: explain why building Notified under the read lock prevents the lost wakeup State the mechanism rather than the outcome: notified_owned() snapshots tokio's notify_waiters_calls counter at construction, and holding the read lock orders that snapshot before insert_equality_delete can advance it. This keeps a later refactor from moving notified_owned() out from under the lock unnoticed. Signed-off-by: Dhruv Arya <[email protected]> --------- Signed-off-by: Dhruv Arya <[email protected]> Co-authored-by: Dhruv Arya <[email protected]> --- crates/iceberg/src/arrow/delete_filter.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/iceberg/src/arrow/delete_filter.rs b/crates/iceberg/src/arrow/delete_filter.rs index cef81afda..6f13be71d 100644 --- a/crates/iceberg/src/arrow/delete_filter.rs +++ b/crates/iceberg/src/arrow/delete_filter.rs @@ -168,17 +168,22 @@ impl DeleteFilter { &self, file_path: &str, ) -> Option<Predicate> { - let notifier = { + // Build the `Notified` while holding the read lock. `notified_owned()` records tokio's + // `notify_waiters_calls` counter at construction and completes on first poll if that + // counter has since advanced. Reading the counter under the lock guarantees it is taken + // before `insert_equality_delete` can advance it via `notify_waiters()`, so the + // notification is never missed even though we `.await` after releasing the lock. + let notified = { match self.state.read().unwrap().equality_deletes.get(file_path) { None => return None, - Some(EqDelState::Loading(notifier)) => notifier.clone(), + Some(EqDelState::Loading(notifier)) => notifier.clone().notified_owned(), Some(EqDelState::Loaded(predicate)) => { return Some(predicate.clone()); } } }; - notifier.notified().await; + notified.await; match self.state.read().unwrap().equality_deletes.get(file_path) { Some(EqDelState::Loaded(predicate)) => Some(predicate.clone()),
