fix(scheduler): harden scheduled actions and speed limits

- Persist scheduler dispatch markers and retry unacknowledged events across renderer and process restarts.
- Fence queue admission, Torrent moves, and permit activation during system actions with an explicit force path.
- Preserve Aria2 zero-limit overrides and normalize global limits across startup, queued, retry, and live paths.
- Guard scheduler lifecycle races, completion post-actions, settings bindings, and regression coverage.
This commit is contained in:
NimBold
2026-08-12 18:57:31 +03:30
parent 885e3d0100
commit e9ad226b93
22 changed files with 528 additions and 71 deletions
+4
View File
@@ -15,6 +15,10 @@ const download = (id: string, status: DownloadItem['status']): DownloadItem => (
});
describe('schedulerCompletionState', () => {
it('does not treat an empty scheduler set as completed', () => {
expect(schedulerCompletionState([], [])).toBe('incomplete');
});
it('stays active while any tracked scheduler download can still progress', () => {
expect(schedulerCompletionState([
download('a', 'completed'),
+2
View File
@@ -7,6 +7,8 @@ export const schedulerCompletionState = (
downloads: DownloadItem[],
schedulerActiveDownloadIds: string[],
): SchedulerCompletionState => {
if (schedulerActiveDownloadIds.length === 0) return 'incomplete';
const scheduledItems = schedulerActiveDownloadIds.map(id =>
downloads.find(download => download.id === id)
);
+13
View File
@@ -0,0 +1,13 @@
import { describe, expect, it } from 'vitest';
import { beginSchedulerControl, isSchedulerControlCurrent } from './schedulerControl';
describe('scheduler control generation', () => {
it('invalidates an older asynchronous scheduler operation', () => {
const first = beginSchedulerControl();
expect(isSchedulerControlCurrent(first)).toBe(true);
const second = beginSchedulerControl();
expect(isSchedulerControlCurrent(first)).toBe(false);
expect(isSchedulerControlCurrent(second)).toBe(true);
});
});
+14
View File
@@ -0,0 +1,14 @@
let schedulerControlGeneration = 0;
/**
* Start a new scheduler control lifecycle. A later manual pause or scheduler
* event invalidates earlier asynchronous queue operations before they can
* publish stale running state.
*/
export const beginSchedulerControl = (): number => {
schedulerControlGeneration += 1;
return schedulerControlGeneration;
};
export const isSchedulerControlCurrent = (generation: number): boolean =>
schedulerControlGeneration === generation;