···2626 repo::{AccountStatus, RepoInfo, RepoState},
2727 resync_queue::ResyncItem,
2828};
2929-use crate::util::unix_now;
2929+use crate::util::{TokenExt, unix_now};
30303131/// How long to wait between queue polls when no workers are running.
3232const IDLE_POLL_MS: u64 = 500;
···146146147147 if workers.is_empty() {
148148 // Nothing running, nothing claimable right now. Yield before polling.
149149- tokio::select! {
150150- _ = token.cancelled() => return Ok(()),
151151- _ = tokio::time::sleep(tokio::time::Duration::from_millis(IDLE_POLL_MS)) => {}
152152- }
149149+ if !token.sleep(Duration::from_millis(IDLE_POLL_MS)).await {
150150+ return Ok(());
151151+ };
153152 continue;
154153 }
155154156155 // Wait for the next worker to finish, then loop to try claiming more.
157157- let next = tokio::select! {
158158- _ = token.cancelled() => return Ok(()),
159159- r = workers.join_next_with_id() => r,
156156+ let Some(next) = token.run(workers.join_next_with_id()).await else {
157157+ return Ok(());
160158 };
161159 if let Some(join_result) = next {
162160 // JoinError also carries the task ID, so we can recover the DID
+20
src/util.rs
···66 .unwrap_or_default()
77 .as_secs()
88}
99+1010+pub trait TokenExt {
1111+ /// alias for CancellationToken::run_until_cancelled
1212+ fn run<F: Future>(&self, fut: F) -> impl Future<Output = Option<F::Output>>;
1313+ /// sleep that ends early if if cancelled
1414+ ///
1515+ /// returns `true` when completed after the full sleep completed
1616+ fn sleep(&self, d: tokio::time::Duration) -> impl Future<Output = bool>;
1717+}
1818+1919+impl TokenExt for tokio_util::sync::CancellationToken {
2020+ async fn run<F: Future>(&self, f: F) -> Option<F::Output> {
2121+ self.run_until_cancelled(f).await
2222+ }
2323+ async fn sleep(&self, d: tokio::time::Duration) -> bool {
2424+ self.run_until_cancelled(tokio::time::sleep(d))
2525+ .await
2626+ .is_none()
2727+ }
2828+}