Optimize real-time updates in downloads service with throttling to reduce CPU usage

This commit is contained in:
KennyG 2026-03-30 09:24:04 -04:00
parent 84c6418f91
commit 0a807de861
2 changed files with 34 additions and 4 deletions

View file

@ -1,7 +1,7 @@
import { AsyncPipe, DatePipe, KeyValuePipe } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, ElementRef, viewChild, inject, OnDestroy, OnInit } from '@angular/core';
import { Observable, map, distinctUntilChanged } from 'rxjs';
import { Observable, map, distinctUntilChanged, auditTime } from 'rxjs';
import { FormsModule } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
@ -250,8 +250,13 @@ export class App implements AfterViewInit, OnInit, OnDestroy {
this.rebuildSortedDone();
this.cdr.markForCheck();
});
// Subscribe to real-time updates
this.downloads.updated.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
// Subscribe to real-time updates (throttled to reduce CPU on large queues).
this.downloads.updated
.pipe(
auditTime(200),
takeUntilDestroyed(this.destroyRef)
)
.subscribe(() => {
this.updateMetrics();
this.cdr.markForCheck();
});

View file

@ -36,6 +36,9 @@ export class DownloadsService {
ytdlOptionsChanged = new Subject<Record<string, unknown>>();
configurationChanged = new Subject<Record<string, unknown>>();
updated = new Subject<void>();
private updateRefreshScheduled = false;
private readonly foregroundRefreshMs = 250;
private readonly backgroundRefreshMs = 30000;
configuration: Record<string, unknown> = {};
customDirs: Record<string, string[]> = {};
@ -68,7 +71,7 @@ export class DownloadsService {
data.checked = !!dl?.checked;
data.deleting = !!dl?.deleting;
this.queue.set(data.url, data);
this.updated.next();
this.scheduleUpdatedRefresh();
});
this.socket.fromEvent('completed')
.pipe(takeUntilDestroyed())
@ -117,6 +120,28 @@ export class DownloadsService {
});
}
private scheduleUpdatedRefresh() {
// Coalesce high-frequency download progress events into a capped refresh rate.
// requestAnimationFrame (~60fps) is smooth but expensive on large queues.
// We trade a little smoothness for much lower CPU:
// - foreground: ~8fps
// - background: ~1fps
if (this.updateRefreshScheduled) {
return;
}
this.updateRefreshScheduled = true;
const flush = () => {
this.updateRefreshScheduled = false;
this.updated.next();
};
const delay = document.hidden
? this.backgroundRefreshMs
: this.foregroundRefreshMs;
setTimeout(() => flush(), delay);
}
handleHTTPError(error: HttpErrorResponse) {
const msg = error.error instanceof ErrorEvent
? error.error.message