mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-21 16:36:50 +00:00
fix(queue): prevent stale resume waiters from hanging
This commit is contained in:
@@ -4584,6 +4584,7 @@ async fn resume_download(
|
|||||||
&id_clone,
|
&id_clone,
|
||||||
&queue_id,
|
&queue_id,
|
||||||
lifecycle_generation,
|
lifecycle_generation,
|
||||||
|
control_epoch,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
@@ -4791,6 +4792,7 @@ async fn resume_download(
|
|||||||
&id_clone,
|
&id_clone,
|
||||||
&queue_id_clone,
|
&queue_id_clone,
|
||||||
lifecycle_generation,
|
lifecycle_generation,
|
||||||
|
resume_epoch,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
};
|
};
|
||||||
|
|||||||
+20
-2
@@ -472,6 +472,7 @@ impl<R: tauri::Runtime> QueueManager<R> {
|
|||||||
};
|
};
|
||||||
if released {
|
if released {
|
||||||
self.aria2_retry_cancelled.lock().await.remove(id);
|
self.aria2_retry_cancelled.lock().await.remove(id);
|
||||||
|
self.notify.notify_waiters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,6 +620,7 @@ impl<R: tauri::Runtime> QueueManager<R> {
|
|||||||
let mut epochs = self.aria2_control_epochs.lock().await;
|
let mut epochs = self.aria2_control_epochs.lock().await;
|
||||||
let epoch = epochs.get(id).copied().unwrap_or_default().wrapping_add(1);
|
let epoch = epochs.get(id).copied().unwrap_or_default().wrapping_add(1);
|
||||||
epochs.insert(id.to_string(), epoch);
|
epochs.insert(id.to_string(), epoch);
|
||||||
|
self.notify.notify_waiters();
|
||||||
epoch
|
epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -817,17 +819,28 @@ impl<R: tauri::Runtime> QueueManager<R> {
|
|||||||
id: &str,
|
id: &str,
|
||||||
queue_id: &str,
|
queue_id: &str,
|
||||||
lifecycle_generation: u64,
|
lifecycle_generation: u64,
|
||||||
|
control_epoch: u64,
|
||||||
) -> Option<OwnedSemaphorePermit> {
|
) -> Option<OwnedSemaphorePermit> {
|
||||||
loop {
|
loop {
|
||||||
if !self.is_registered_generation(id, lifecycle_generation).await {
|
if !self.is_registered_generation(id, lifecycle_generation).await
|
||||||
|
|| self.is_aria2_retry_cancelled(id).await
|
||||||
|
|| !self.is_aria2_control_epoch_current(id, control_epoch).await
|
||||||
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let notified = self.notify.notified();
|
let notified = self.notify.notified();
|
||||||
|
tokio::pin!(notified);
|
||||||
|
notified.as_mut().enable();
|
||||||
if let Some(permit) = self
|
if let Some(permit) = self
|
||||||
.try_reserve_queue_slot(id, queue_id, lifecycle_generation)
|
.try_reserve_queue_slot(id, queue_id, lifecycle_generation)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
if self.is_registered_generation(id, lifecycle_generation).await {
|
if self.is_registered_generation(id, lifecycle_generation).await
|
||||||
|
&& !self.is_aria2_retry_cancelled(id).await
|
||||||
|
&& self
|
||||||
|
.is_aria2_control_epoch_current(id, control_epoch)
|
||||||
|
.await
|
||||||
|
{
|
||||||
return Some(permit);
|
return Some(permit);
|
||||||
}
|
}
|
||||||
self.release_queue_reservation_for_generation(id, lifecycle_generation)
|
self.release_queue_reservation_for_generation(id, lifecycle_generation)
|
||||||
@@ -1141,6 +1154,8 @@ impl<R: tauri::Runtime> QueueManager<R> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let notified = self.notify.notified();
|
let notified = self.notify.notified();
|
||||||
|
tokio::pin!(notified);
|
||||||
|
notified.as_mut().enable();
|
||||||
let generation = self
|
let generation = self
|
||||||
.registered_lifecycle_generation(id)
|
.registered_lifecycle_generation(id)
|
||||||
.await
|
.await
|
||||||
@@ -1301,6 +1316,8 @@ impl<R: tauri::Runtime> QueueManager<R> {
|
|||||||
pub async fn run_dispatcher(self: Arc<Self>) {
|
pub async fn run_dispatcher(self: Arc<Self>) {
|
||||||
loop {
|
loop {
|
||||||
let notified = self.notify.notified();
|
let notified = self.notify.notified();
|
||||||
|
tokio::pin!(notified);
|
||||||
|
notified.as_mut().enable();
|
||||||
if let Some((permit, task)) = self.try_admit_next_task().await {
|
if let Some((permit, task)) = self.try_admit_next_task().await {
|
||||||
// Admission owns the global and per-queue reservation before
|
// Admission owns the global and per-queue reservation before
|
||||||
// this task is spawned. Keep the dispatcher free to admit
|
// this task is spawned. Keep the dispatcher free to admit
|
||||||
@@ -1623,6 +1640,7 @@ impl<R: tauri::Runtime> QueueManager<R> {
|
|||||||
.await
|
.await
|
||||||
.insert(id.to_string());
|
.insert(id.to_string());
|
||||||
self.aria2_retry_cancel_notify.notify_waiters();
|
self.aria2_retry_cancel_notify.notify_waiters();
|
||||||
|
self.notify.notify_waiters();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn allow_aria2_retries(&self, id: &str) {
|
pub async fn allow_aria2_retries(&self, id: &str) {
|
||||||
|
|||||||
@@ -916,6 +916,75 @@ async fn aria2_resume_waits_for_shrunk_capacity() {
|
|||||||
.expect("resume task should not panic"));
|
.expect("resume task should not panic"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn cancelled_resume_waiter_exits_without_capacity_release() {
|
||||||
|
let (mgr, _spawner) = make_manager(1);
|
||||||
|
let manager = Arc::new(mgr);
|
||||||
|
manager
|
||||||
|
.reserve_enqueue_generation("cancelled-resume", 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let blocker = manager.acquire_permit().await.unwrap();
|
||||||
|
manager.park_permit("blocker", blocker).await;
|
||||||
|
|
||||||
|
let waiter = {
|
||||||
|
let manager = Arc::clone(&manager);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
manager
|
||||||
|
.acquire_aria2_permit_candidate_for_queue("cancelled-resume", "main", 1, 0)
|
||||||
|
.await
|
||||||
|
.is_none()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
assert!(
|
||||||
|
!waiter.is_finished(),
|
||||||
|
"resume worker should be waiting for capacity"
|
||||||
|
);
|
||||||
|
|
||||||
|
manager.cancel_aria2_retries("cancelled-resume").await;
|
||||||
|
assert!(timeout(Duration::from_secs(1), waiter)
|
||||||
|
.await
|
||||||
|
.expect("cancellation should wake the waiting resume worker")
|
||||||
|
.expect("resume worker should not panic"));
|
||||||
|
manager.release_permit("blocker").await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stale_control_epoch_wakes_a_waiting_resume_worker() {
|
||||||
|
let (mgr, _spawner) = make_manager(1);
|
||||||
|
let manager = Arc::new(mgr);
|
||||||
|
manager
|
||||||
|
.reserve_enqueue_generation("stale-resume", 1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let control_epoch = manager.next_aria2_control_epoch("stale-resume").await;
|
||||||
|
let blocker = manager.acquire_permit().await.unwrap();
|
||||||
|
manager.park_permit("blocker", blocker).await;
|
||||||
|
|
||||||
|
let waiter = {
|
||||||
|
let manager = Arc::clone(&manager);
|
||||||
|
tokio::spawn(async move {
|
||||||
|
manager
|
||||||
|
.acquire_aria2_permit_candidate_for_queue("stale-resume", "main", 1, control_epoch)
|
||||||
|
.await
|
||||||
|
.is_none()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
assert!(
|
||||||
|
!waiter.is_finished(),
|
||||||
|
"resume worker should be waiting for capacity"
|
||||||
|
);
|
||||||
|
|
||||||
|
manager.next_aria2_control_epoch("stale-resume").await;
|
||||||
|
assert!(timeout(Duration::from_secs(1), waiter)
|
||||||
|
.await
|
||||||
|
.expect("a newer control epoch should wake the stale worker")
|
||||||
|
.expect("resume worker should not panic"));
|
||||||
|
manager.release_permit("blocker").await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn dispatcher_skips_a_full_front_queue_for_later_eligible_work() {
|
async fn dispatcher_skips_a_full_front_queue_for_later_eligible_work() {
|
||||||
let (manager, _spawner) = make_manager(2);
|
let (manager, _spawner) = make_manager(2);
|
||||||
@@ -1146,7 +1215,7 @@ async fn stale_queue_resume_reservation_is_removed_when_activation_fails() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(previous.is_none());
|
assert!(previous.is_none());
|
||||||
let candidate = manager
|
let candidate = manager
|
||||||
.acquire_aria2_permit_candidate_for_queue("candidate", "queue-a", 1)
|
.acquire_aria2_permit_candidate_for_queue("candidate", "queue-a", 1, 0)
|
||||||
.await
|
.await
|
||||||
.expect("candidate should reserve the queue slot");
|
.expect("candidate should reserve the queue slot");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user