From 15c0df4de7ee71b43526d9850b47c9107efe303e Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Mon, 22 Jan 2024 15:27:29 +0100 Subject: [PATCH] fixup(#6037): actually fix the issue, #6388 failed to do so (#6429) Before this patch, the select! still retured immediately if `futs` was empty. Must have tested a stale build in my manual testing of #6388. --- pageserver/src/page_service.rs | 37 ++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/pageserver/src/page_service.rs b/pageserver/src/page_service.rs index 1013131a99..77ce9981f0 100644 --- a/pageserver/src/page_service.rs +++ b/pageserver/src/page_service.rs @@ -386,12 +386,18 @@ impl PageServerHandler { /// Future that completes when we need to shut down the connection. /// - /// Reasons for need to shut down are: - /// - any of the timelines we hold GateGuards for in `shard_timelines` is cancelled - /// - task_mgr requests shutdown of the connection + /// We currently need to shut down when any of the following happens: + /// 1. any of the timelines we hold GateGuards for in `shard_timelines` is cancelled + /// 2. task_mgr requests shutdown of the connection /// - /// The need to check for `task_mgr` cancellation arises mainly from `handle_pagerequests` - /// where, at first, `shard_timelines` is empty, see + /// NB on (1): the connection's lifecycle is not actually tied to any of the + /// `shard_timelines`s' lifecycles. But it's _necessary_ in the current + /// implementation to be responsive to timeline cancellation because + /// the connection holds their `GateGuards` open (sored in `shard_timelines`). + /// We currently do the easy thing and terminate the connection if any of the + /// shard_timelines gets cancelled. But really, we cuold spend more effort + /// and simply remove the cancelled timeline from the `shard_timelines`, thereby + /// dropping the guard. /// /// NB: keep in sync with [`Self::is_connection_cancelled`] async fn await_connection_cancelled(&self) { @@ -404,16 +410,17 @@ impl PageServerHandler { // immutable &self). So it's fine to evaluate shard_timelines after the sleep, we don't risk // missing any inserts to the map. - let mut futs = self - .shard_timelines - .values() - .map(|ht| ht.timeline.cancel.cancelled()) - .collect::>(); - - tokio::select! { - _ = task_mgr::shutdown_watcher() => { } - _ = futs.next() => {} - } + let mut cancellation_sources = Vec::with_capacity(1 + self.shard_timelines.len()); + use futures::future::Either; + cancellation_sources.push(Either::Left(task_mgr::shutdown_watcher())); + cancellation_sources.extend( + self.shard_timelines + .values() + .map(|ht| Either::Right(ht.timeline.cancel.cancelled())), + ); + FuturesUnordered::from_iter(cancellation_sources) + .next() + .await; } /// Checking variant of [`Self::await_connection_cancelled`].