diff --git a/src/client.rs b/src/client.rs index 27dd691..1b8b189 100644 --- a/src/client.rs +++ b/src/client.rs @@ -237,7 +237,14 @@ impl Client { return notifications.queue().pop(); } let left = deadline.saturating_duration_since(std::time::Instant::now()); - if left.is_zero() || !notifications.ready().wait_timeout(left) { + if left.is_zero() { + return notifications.queue().pop(); + } + // may keeps park timeouts in whole milliseconds, and releases up to + // 0.3.51 truncate anything shorter to "no timeout" (the wait would + // never return): never ask it for less than 1 ms. + let left = left.max(std::time::Duration::from_millis(1)); + if !notifications.ready().wait_timeout(left) { return notifications.queue().pop(); } // A permit can outnumber queued notifications when the queue was diff --git a/tests/test/main.rs b/tests/test/main.rs index 9e12501..9b82f4e 100644 --- a/tests/test/main.rs +++ b/tests/test/main.rs @@ -712,6 +712,27 @@ fn next_notification_returns_queued_notifications_first_and_times_out_empty() { ); } +#[test] +fn next_notification_returns_for_sub_millisecond_timeouts() { + // In a coroutine, so the wait parks on may's timer; a remaining time under + // 1 ms must not turn into "no timeout". + let (tx, rx) = std::sync::mpsc::channel(); + let _waiter = may::go!(move || { + let client = connect("user=postgres"); + client.batch_execute("LISTEN test_short_wait").unwrap(); + for us in [1u64, 100, 500, 999, 1500] { + assert!(client + .next_notification(std::time::Duration::from_micros(us)) + .is_none()); + } + tx.send(()).unwrap(); + }); + assert!( + rx.recv_timeout(std::time::Duration::from_secs(10)).is_ok(), + "a sub-millisecond wait did not return" + ); +} + #[test] fn next_notification_returns_when_the_connection_closes() { let client = connect("user=postgres");