Raise proxy connection limit to 40

This commit is contained in:
Sucukdeluxe
2026-08-31 15:33:48 +02:00
parent eb68db88d0
commit 9ca5789627
11 changed files with 47 additions and 20 deletions
+4
View File
@@ -4,6 +4,10 @@ All notable changes to Multi-Debrid Downloader are documented in this file.
## [Unreleased] ## [Unreleased]
### Proxy limit tuning
- Increase the default global proxy connection budget for new settings from 20 to 32 and raise the configurable maximum from 32 to 40 while retaining fair sharing and adaptive 429/503 backoff.
## [2.0.77] - 2026-08-31 ## [2.0.77] - 2026-08-31
### Proxy throughput and fairness ### Proxy throughput and fairness
+12 -3
View File
@@ -10,10 +10,10 @@ Diese Datei hält den verifizierten technischen Arbeitsstand fest. Sie enthält
- Verifiziert am: 31. August 2026, Europe/Berlin - Verifiziert am: 31. August 2026, Europe/Berlin
- Lokaler Pfad: `C:\Users\Sascha\Desktop\Claude & ChatGPT Projekte\Multi-Debrid-Downloader` - Lokaler Pfad: `C:\Users\Sascha\Desktop\Claude & ChatGPT Projekte\Multi-Debrid-Downloader`
- Arbeitsbranch: `release/v2.0.77` - Arbeitsbranch: `tune/proxy-limit-40`
- Quellbasis: `release/v2.0.76` - Quellbasis: `release/v2.0.76`
- Release-Tag: `v2.0.77` - Release-Tag: `v2.0.77`
- Baseline-Commit: `61e10034ba4f75c6f7cbe25b5639d52a18d90e37` - Baseline-Commit: `eb68db88d08739f5f4ff23bf5b1133a132c2d8db`
- Paketversion: `2.0.77` - Paketversion: `2.0.77`
- Letztes Release: `Multi-Debrid-Downloader v2.0.77`, veröffentlicht am 31. August 2026 auf GitHub und Forgejo - Letztes Release: `Multi-Debrid-Downloader v2.0.77`, veröffentlicht am 31. August 2026 auf GitHub und Forgejo
- Runtime-Voraussetzung: Node.js `>=20`; lokal verifiziert mit Node.js `24.19.0` und npm `11.17.0` - Runtime-Voraussetzung: Node.js `>=20`; lokal verifiziert mit Node.js `24.19.0` und npm `11.17.0`
@@ -138,6 +138,12 @@ Diese Datei hält den verifizierten technischen Arbeitsstand fest. Sie enthält
- Der Paketfortschritt verwendet bei bekannten Größen dieselben heruntergeladenen und gesamten Bytes wie die Größenanzeige. Dadurch stimmen Prozentwert und `heruntergeladen / gesamt` auch bei unterschiedlich großen Dateien überein. Wenn Größen vollständig fehlen, bleibt die bisherige dateibasierte Berechnung als Rückfall erhalten. - Der Paketfortschritt verwendet bei bekannten Größen dieselben heruntergeladenen und gesamten Bytes wie die Größenanzeige. Dadurch stimmen Prozentwert und `heruntergeladen / gesamt` auch bei unterschiedlich großen Dateien überein. Wenn Größen vollständig fehlen, bleibt die bisherige dateibasierte Berechnung als Rückfall erhalten.
- Die Entpacklogik und ihre Produktionspfade wurden nicht verändert. Eine Serverinstallation oder ein produktiver Neustart ist nicht Bestandteil dieses Releases. - Die Entpacklogik und ihre Produktionspfade wurden nicht verändert. Eine Serverinstallation oder ein produktiver Neustart ist nicht Bestandteil dieses Releases.
## Unveröffentlichte Anpassung nach v2.0.77
- Der Standardwert für neue beziehungsweise noch nicht gesetzte Konfigurationen steigt von 20 auf 32 globale Proxy-Verbindungen. Bereits gespeicherte Benutzerwerte bleiben erhalten.
- Das einstellbare Maximum steigt von 32 auf 40 globale Proxy-Verbindungen. Faire Aufteilung, Proxy-Bewertung, Cooldowns und adaptives 429/503-Backoff gelten unverändert auch oberhalb von 32.
- Die Entpacklogik und ihre Produktionspfade wurden nicht verändert. Diese Anpassung ist noch nicht veröffentlicht, installiert oder produktiv neu gestartet.
## Start-, Build- und Testbefehle ## Start-, Build- und Testbefehle
```powershell ```powershell
@@ -169,6 +175,9 @@ npm exec -- tsc --noEmit
## Verifizierungen vom 31. August 2026 ## Verifizierungen vom 31. August 2026
- Unveröffentlichte 32/40-Limitanpassung: Proxy-Scheduler, Storage, Einstellungen, Renderer-Projektion und visuelle Fixtures 229 von 229 Tests erfolgreich. Die Main-Normalisierung deckt Standard 32, Minimum 2, Maximum 40 und Werte oberhalb des Maximums direkt ab.
- Vollständiger Client-Lauf nach der 32/40-Anpassung ohne die zwei lokal nicht ausführbaren Symlink-Fixtures: 140 Testdateien erfolgreich, 1 JVM-Testdatei übersprungen; 2.658 Tests erfolgreich und 4 übersprungen. Die übrigen 22 Public-Release-Metadatentests liefen separat erfolgreich. Alle 84 Entpacktests sind unverändert grün.
- Nach der 32/40-Anpassung erfolgreich: TypeScript, vollständiger Main-/Renderer-Build, Self-Check und 16 von 16 Backup-API-Tests. Die bekannte Vite-Warnung zum rund 576 KiB großen Renderer-Chunk bleibt bestehen.
- Proxy-Scheduler-Regressionen für `v2.0.77`: 11 von 11 erfolgreich. Abgedeckt sind 32 rollierend vergebene Chunks, deutlich häufigere Chunk-Übernahme durch den schnellen von zwei unterschiedlich verzögerten Proxys, zwei überlappende Downloads mit je höchstens der Hälfte eines gemeinsamen Viererlimits, Ausschluss des festen API-Proxys, Ersatz ausgefallener Proxys, exakte Dateiinhalte und Fortschrittsrückrechnung sowie eine erfolgreiche Fortsetzung nach transientem Origin-HTTP-503 mit Reduktion neuer paralleler Requests von 6 auf höchstens 4. - Proxy-Scheduler-Regressionen für `v2.0.77`: 11 von 11 erfolgreich. Abgedeckt sind 32 rollierend vergebene Chunks, deutlich häufigere Chunk-Übernahme durch den schnellen von zwei unterschiedlich verzögerten Proxys, zwei überlappende Downloads mit je höchstens der Hälfte eines gemeinsamen Viererlimits, Ausschluss des festen API-Proxys, Ersatz ausgefallener Proxys, exakte Dateiinhalte und Fortschrittsrückrechnung sowie eine erfolgreiche Fortsetzung nach transientem Origin-HTTP-503 mit Reduktion neuer paralleler Requests von 6 auf höchstens 4.
- Paketfortschritt, Einstellungen und Proxy-Scheduler gezielt: 232 von 232 Tests erfolgreich. Ein Paket mit 2,25 von 18,60 Größeneinheiten zeigt nun 12 Prozent statt einer dateibasierten Abweichung. - Paketfortschritt, Einstellungen und Proxy-Scheduler gezielt: 232 von 232 Tests erfolgreich. Ein Paket mit 2,25 von 18,60 Größeneinheiten zeigt nun 12 Prozent statt einer dateibasierten Abweichung.
- Storage einschließlich neuem Standardwert 20: 126 von 126 Tests erfolgreich. - Storage einschließlich neuem Standardwert 20: 126 von 126 Tests erfolgreich.
@@ -234,7 +243,7 @@ npm exec -- tsc --noEmit
## Nächste sinnvolle Schritte ## Nächste sinnvolle Schritte
1. Nach separater Installationsfreigabe `v2.0.77` mit Gesamtlimit 20 und zwei echten parallelen Real-Debrid-Dateien prüfen. Erfolgsquote, HTTP-Status, Chunk-Wechsel, Zusatzverkehr, Festplattenlast und Netto-MB/s beobachten, bevor das Maximum von 24 erwogen wird. 1. Die unveröffentlichte Anpassung auf Standard 32 und Maximum 40 erst nach separater Release-Freigabe paketieren. Danach mit zwei echten parallelen Real-Debrid-Dateien zunächst 32 und anschließend optional 40 prüfen sowie 429/503, Zusatzverkehr, Festplattenlast und Netto-MB/s beobachten.
2. Nach einer getrennt freigegebenen Serverinstallation den Ablauf Real-Debrid-Web-Download, Hauptfenster schließen und unmittelbar neu starten am echten Zielsystem verifizieren; vor jedem Eingriff Prozessbaum und Logtail sichern. 2. Nach einer getrennt freigegebenen Serverinstallation den Ablauf Real-Debrid-Web-Download, Hauptfenster schließen und unmittelbar neu starten am echten Zielsystem verifizieren; vor jedem Eingriff Prozessbaum und Logtail sichern.
3. Sicherheitsabhängigkeiten in einem separaten Upgrade-Branch aktualisieren, Electron-/Vite-/Vitest-Major-Wechsel einzeln testen und danach den vollständigen Windows-Paketpfad prüfen. 3. Sicherheitsabhängigkeiten in einem separaten Upgrade-Branch aktualisieren, Electron-/Vite-/Vitest-Major-Wechsel einzeln testen und danach den vollständigen Windows-Paketpfad prüfen.
4. Eine nichtdestruktive Strategie zur Bereinigung der divergierenden `main`-Branches abstimmen; kein Force-Push ohne ausdrückliche Freigabe. 4. Eine nichtdestruktive Strategie zur Bereinigung der divergierenden `main`-Branches abstimmen; kein Force-Push ohne ausdrückliche Freigabe.
+1 -1
View File
@@ -109,7 +109,7 @@ export function defaultSettings(): AppSettings {
proxyDownloadEnabled: false, proxyDownloadEnabled: false,
proxyListPath: "", proxyListPath: "",
proxyApiProxyIndex: 1, proxyApiProxyIndex: 1,
proxyConnectionsPerDownload: 20, proxyConnectionsPerDownload: 32,
updateRepo: DEFAULT_UPDATE_REPO, updateRepo: DEFAULT_UPDATE_REPO,
autoUpdateCheck: true, autoUpdateCheck: true,
clipboardWatch: false, clipboardWatch: false,
+12 -5
View File
@@ -12,6 +12,8 @@ const DEFAULT_MIN_SEGMENT_BYTES = 8 * 1024 * 1024;
const DEFAULT_MAX_CHUNK_BYTES = 16 * 1024 * 1024; const DEFAULT_MAX_CHUNK_BYTES = 16 * 1024 * 1024;
const DEFAULT_CHUNKS_PER_CONNECTION = 8; const DEFAULT_CHUNKS_PER_CONNECTION = 8;
const DEFAULT_PROXY_ATTEMPTS = 3; const DEFAULT_PROXY_ATTEMPTS = 3;
const DEFAULT_CONNECTION_LIMIT = 32;
const MAX_CONNECTION_LIMIT = 40;
const PROXY_PROBE_END = 1; const PROXY_PROBE_END = 1;
const MAX_REDIRECTS = 5; const MAX_REDIRECTS = 5;
const MAX_SEGMENTS = 4_096; const MAX_SEGMENTS = 4_096;
@@ -86,6 +88,11 @@ export type ProxyFallbackReason =
const proxyFileCache = new Map<string, CachedProxyFile>(); const proxyFileCache = new Map<string, CachedProxyFile>();
export function normalizeProxyConnectionLimit(value: number, fallback = DEFAULT_CONNECTION_LIMIT): number {
const candidate = Number.isFinite(value) && value !== 0 ? value : fallback;
return Math.max(2, Math.min(MAX_CONNECTION_LIMIT, Math.floor(candidate)));
}
function proxyEndpointKey(proxy: ProxyEndpoint): string { function proxyEndpointKey(proxy: ProxyEndpoint): string {
return `${proxy.url}\0${proxy.authorization}`; return `${proxy.url}\0${proxy.authorization}`;
} }
@@ -124,8 +131,8 @@ interface ProxyPerformance {
} }
class SharedProxyCoordinator { class SharedProxyCoordinator {
private configuredConnectionLimit = 16; private configuredConnectionLimit = DEFAULT_CONNECTION_LIMIT;
private adaptiveConnectionLimit = 16; private adaptiveConnectionLimit = DEFAULT_CONNECTION_LIMIT;
private activeConnections = 0; private activeConnections = 0;
private readonly activeProxies = new Set<string>(); private readonly activeProxies = new Set<string>();
private readonly activeConnectionsByGroup = new Map<string, number>(); private readonly activeConnectionsByGroup = new Map<string, number>();
@@ -139,7 +146,7 @@ class SharedProxyCoordinator {
private successfulTransfersSinceThrottle = 0; private successfulTransfersSinceThrottle = 0;
public setConnectionLimit(limit: number): void { public setConnectionLimit(limit: number): void {
const normalized = Math.max(2, Math.min(32, Math.floor(limit || 16))); const normalized = normalizeProxyConnectionLimit(limit);
if (normalized !== this.configuredConnectionLimit) { if (normalized !== this.configuredConnectionLimit) {
this.configuredConnectionLimit = normalized; this.configuredConnectionLimit = normalized;
this.adaptiveConnectionLimit = normalized; this.adaptiveConnectionLimit = normalized;
@@ -842,8 +849,8 @@ export async function downloadWithProxySegments(options: ProxySegmentedDownloadO
return { status: "fallback", reason: "no_valid_proxies" }; return { status: "fallback", reason: "no_valid_proxies" };
} }
const poolKey = path.resolve(proxyPath); const poolKey = path.resolve(proxyPath);
const requestedConnections = Math.max(2, Math.min(32, Math.floor(options.connections || 16))); const requestedConnections = normalizeProxyConnectionLimit(options.connections);
const totalConnectionLimit = Math.max(2, Math.min(32, Math.floor(options.totalConnectionLimit ?? requestedConnections))); const totalConnectionLimit = normalizeProxyConnectionLimit(options.totalConnectionLimit ?? requestedConnections, requestedConnections);
sharedProxyCoordinator.setConnectionLimit(totalConnectionLimit); sharedProxyCoordinator.setConnectionLimit(totalConnectionLimit);
const reservedProxyIndex = Math.max(0, Math.floor(options.reservedProxyIndex || 0)); const reservedProxyIndex = Math.max(0, Math.floor(options.reservedProxyIndex || 0));
const reservedProxy = reservedProxyIndex > 0 ? loaded.proxies[reservedProxyIndex - 1] : null; const reservedProxy = reservedProxyIndex > 0 ? loaded.proxies[reservedProxyIndex - 1] : null;
+1 -1
View File
@@ -615,7 +615,7 @@ export function normalizeSettings(settings: AppSettings): AppSettings {
proxyDownloadEnabled: Boolean(settings.proxyDownloadEnabled), proxyDownloadEnabled: Boolean(settings.proxyDownloadEnabled),
proxyListPath: asText(settings.proxyListPath).slice(0, 4096), proxyListPath: asText(settings.proxyListPath).slice(0, 4096),
proxyApiProxyIndex: clampNumber(settings.proxyApiProxyIndex, defaults.proxyApiProxyIndex, 1, 100000), proxyApiProxyIndex: clampNumber(settings.proxyApiProxyIndex, defaults.proxyApiProxyIndex, 1, 100000),
proxyConnectionsPerDownload: clampNumber(settings.proxyConnectionsPerDownload, defaults.proxyConnectionsPerDownload, 2, 32), proxyConnectionsPerDownload: clampNumber(settings.proxyConnectionsPerDownload, defaults.proxyConnectionsPerDownload, 2, 40),
autoUpdateCheck: Boolean(settings.autoUpdateCheck), autoUpdateCheck: Boolean(settings.autoUpdateCheck),
updateRepo: migrateUpdateRepo(asText(settings.updateRepo), defaults.updateRepo), updateRepo: migrateUpdateRepo(asText(settings.updateRepo), defaults.updateRepo),
clipboardWatch: Boolean(settings.clipboardWatch), clipboardWatch: Boolean(settings.clipboardWatch),
+2 -2
View File
@@ -953,7 +953,7 @@ const emptySnapshot = (): UiSnapshot => ({
removeSamplesAfterExtract: false, enableIntegrityCheck: true, autoResumeOnStart: true, removeSamplesAfterExtract: false, enableIntegrityCheck: true, autoResumeOnStart: true,
autoReconnect: false, reconnectWaitSeconds: 45, completedCleanupPolicy: "never", autoReconnect: false, reconnectWaitSeconds: 45, completedCleanupPolicy: "never",
maxParallel: 4, maxParallelExtract: 2, extractCpuPriority: "high", retryLimit: 0, speedLimitEnabled: false, speedLimitKbps: 0, speedLimitMode: "global", maxParallel: 4, maxParallelExtract: 2, extractCpuPriority: "high", retryLimit: 0, speedLimitEnabled: false, speedLimitKbps: 0, speedLimitMode: "global",
proxyDownloadEnabled: false, proxyListPath: "", proxyApiProxyIndex: 1, proxyConnectionsPerDownload: 20, proxyDownloadEnabled: false, proxyListPath: "", proxyApiProxyIndex: 1, proxyConnectionsPerDownload: 32,
updateRepo: "", autoUpdateCheck: true, clipboardWatch: false, minimizeToTray: false, updateRepo: "", autoUpdateCheck: true, clipboardWatch: false, minimizeToTray: false,
theme: "dark", themePreference: "dark", logStorageLocation: "appdata", collapseNewPackages: true, animatePackageDisclosure: true, historyRetentionMode: "permanent", historyMaxEntries: 500, historyMaxAgeDays: 0, autoSortPackagesByProgress: false, autoSkipExtracted: false, hideExtractedItems: true, confirmDeleteSelection: true, backupIncludeDownloads: false, backupIncludeRemoteDiagnostics: false, theme: "dark", themePreference: "dark", logStorageLocation: "appdata", collapseNewPackages: true, animatePackageDisclosure: true, historyRetentionMode: "permanent", historyMaxEntries: 500, historyMaxAgeDays: 0, autoSortPackagesByProgress: false, autoSkipExtracted: false, hideExtractedItems: true, confirmDeleteSelection: true, backupIncludeDownloads: false, backupIncludeRemoteDiagnostics: false,
notifyMention: "", notifyOnPackageCompleted: false, notifyOnPackageFailed: false, notifyOnRunFinished: false, notifyMention: "", notifyOnPackageCompleted: false, notifyOnPackageFailed: false, notifyOnRunFinished: false,
@@ -6067,7 +6067,7 @@ export function App(): ReactElement {
maxParallelExtract: [1, 8, 2], maxParallelExtract: [1, 8, 2],
reconnectWaitSeconds: [10, 600, 45], reconnectWaitSeconds: [10, 600, 45],
proxyApiProxyIndex: [1, 100000, 1], proxyApiProxyIndex: [1, 100000, 1],
proxyConnectionsPerDownload: [2, 32, 20] proxyConnectionsPerDownload: [2, 40, 32]
}; };
const bounds = numericLimits[fieldId as keyof RendererSettingsDraft]; const bounds = numericLimits[fieldId as keyof RendererSettingsDraft];
if (bounds) { if (bounds) {
@@ -460,9 +460,9 @@ export function buildSettingsFormViewModel({
label: "Proxy-Verbindungen insgesamt", label: "Proxy-Verbindungen insgesamt",
value: String(settings.proxyConnectionsPerDownload), value: String(settings.proxyConnectionsPerDownload),
min: 2, min: 2,
max: 32, max: 40,
disabled: !settings.proxyDownloadEnabled, disabled: !settings.proxyDownloadEnabled,
help: `Dieses Gesamtlimit wird fair zwischen allen gleichzeitigen Segmentdownloads geteilt. Bei ${settings.maxParallel} aktiven Downloads laufen zusammen höchstens ${settings.proxyConnectionsPerDownload} Segmentverbindungen; 20 ist der empfohlene Startwert. Langsame und ausgefallene Proxys werden automatisch zurückgestuft. Kleine Dateien, Teil-Downloads und aktive Geschwindigkeitslimits laufen über den festen API-Proxy.` help: `Dieses Gesamtlimit wird fair zwischen allen gleichzeitigen Segmentdownloads geteilt. Bei ${settings.maxParallel} aktiven Downloads laufen zusammen höchstens ${settings.proxyConnectionsPerDownload} Segmentverbindungen; 32 ist der Standardwert, 40 das Maximum. Langsame und ausgefallene Proxys werden automatisch zurückgestuft. Kleine Dateien, Teil-Downloads und aktive Geschwindigkeitslimits laufen über den festen API-Proxy.`
} }
] ]
}, },
+8 -1
View File
@@ -5,7 +5,7 @@ import os from "node:os";
import path from "node:path"; import path from "node:path";
import { Transform } from "node:stream"; import { Transform } from "node:stream";
import { afterEach, describe, expect, it } from "vitest"; import { afterEach, describe, expect, it } from "vitest";
import { downloadWithProxySegments, parseProxyList, selectFixedProxy } from "../src/main/proxy-segmented-download"; import { downloadWithProxySegments, normalizeProxyConnectionLimit, parseProxyList, selectFixedProxy } from "../src/main/proxy-segmented-download";
interface RunningServer { interface RunningServer {
port: number; port: number;
@@ -128,6 +128,13 @@ async function createTempDirectory(): Promise<string> {
} }
describe("proxy segmented download", () => { describe("proxy segmented download", () => {
it("uses 32 connections by default and clamps the configurable maximum to 40", () => {
expect(normalizeProxyConnectionLimit(0)).toBe(32);
expect(normalizeProxyConnectionLimit(40)).toBe(40);
expect(normalizeProxyConnectionLimit(999)).toBe(40);
expect(normalizeProxyConnectionLimit(1)).toBe(2);
});
it("accepts authenticated premium proxy list formats without exposing entries", () => { it("accepts authenticated premium proxy list formats without exposing entries", () => {
const secret = "private-password"; const secret = "private-password";
const count = parseProxyList([ const count = parseProxyList([
+1 -1
View File
@@ -683,7 +683,7 @@ describe("settings views", () => {
label: "Proxy-Verbindungen insgesamt", label: "Proxy-Verbindungen insgesamt",
value: "16", value: "16",
min: 2, min: 2,
max: 32, max: 40,
help: expect.stringContaining("zusammen höchstens 16 Segmentverbindungen") help: expect.stringContaining("zusammen höchstens 16 Segmentverbindungen")
})); }));
}); });
+3 -3
View File
@@ -606,15 +606,15 @@ describe("settings storage", () => {
expect(normalized.proxyDownloadEnabled).toBe(true); expect(normalized.proxyDownloadEnabled).toBe(true);
expect(normalized.proxyListPath).toBe("C:\\proxies.txt"); expect(normalized.proxyListPath).toBe("C:\\proxies.txt");
expect(normalized.proxyApiProxyIndex).toBe(100000); expect(normalized.proxyApiProxyIndex).toBe(100000);
expect(normalized.proxyConnectionsPerDownload).toBe(32); expect(normalized.proxyConnectionsPerDownload).toBe(40);
expect(normalized.outputDir).toBe(defaultSettings().outputDir); expect(normalized.outputDir).toBe(defaultSettings().outputDir);
expect(normalized.extractDir).toBe(defaultSettings().extractDir); expect(normalized.extractDir).toBe(defaultSettings().extractDir);
expect(normalized.mkvLibraryDir).toBe(defaultSettings().mkvLibraryDir); expect(normalized.mkvLibraryDir).toBe(defaultSettings().mkvLibraryDir);
expect(normalized.updateRepo).toBe(defaultSettings().updateRepo); expect(normalized.updateRepo).toBe(defaultSettings().updateRepo);
}); });
it("uses 20 total proxy connections as the default for new settings", () => { it("uses 32 total proxy connections as the default for new settings", () => {
expect(defaultSettings().proxyConnectionsPerDownload).toBe(20); expect(defaultSettings().proxyConnectionsPerDownload).toBe(32);
}); });
it("migrates the previous private update repository to the public release repository", () => { it("migrates the previous private update repository to the public release repository", () => {
+1 -1
View File
@@ -120,7 +120,7 @@ function createSettings(): AppSettings {
proxyDownloadEnabled: false, proxyDownloadEnabled: false,
proxyListPath: "", proxyListPath: "",
proxyApiProxyIndex: 1, proxyApiProxyIndex: 1,
proxyConnectionsPerDownload: 20, proxyConnectionsPerDownload: 32,
updateRepo: "Sucukdeluxe/Multi-Debrid-Downloader", updateRepo: "Sucukdeluxe/Multi-Debrid-Downloader",
autoUpdateCheck: true, autoUpdateCheck: true,
clipboardWatch: true, clipboardWatch: true,