revert(network): remove network-outage auto-pause entirely — false positives froze batches

This commit is contained in:
Administrator 2026-06-10 16:52:37 +02:00
parent 8e03212554
commit 6df00a0948
5 changed files with 7 additions and 181 deletions

View File

@ -43,46 +43,6 @@ class UploadManager extends EventEmitter {
this._accountOverrides = new Map(); // hoster -> fallback account object
this._doodApiKeyCache = new Map(); // accountId/username -> derived doodstream API key ('' = tried, none)
this._baselineCache = new Map(); // hoster:apiKey -> Promise<Set<file_code>> (one fetch shared across all jobs in batch)
this._networkOnline = true;
this._networkWaiters = [];
}
setNetworkOnline(online) {
const next = !!online;
if (next === this._networkOnline) return;
this._networkOnline = next;
if (next) {
const waiters = this._networkWaiters.splice(0);
for (const resolve of waiters) { try { resolve(); } catch {} }
this._rotLog('network-online', { releasedWaiters: waiters.length });
} else {
this._rotLog('network-offline', {});
}
}
isNetworkOnline() {
return this._networkOnline;
}
_waitForNetwork(signal) {
if (this._networkOnline) return Promise.resolve();
if (signal && signal.aborted) return Promise.reject(new Error('Abgebrochen'));
return new Promise((resolve, reject) => {
let settled = false;
const onResume = () => {
if (settled) return;
settled = true;
if (signal) signal.removeEventListener('abort', onAbort);
resolve();
};
const onAbort = () => {
if (settled) return;
settled = true;
reject(new Error('Abgebrochen'));
};
this._networkWaiters.push(onResume);
if (signal) signal.addEventListener('abort', onAbort, { once: true });
});
}
switchAccount(hoster, fallbackAccount) {
@ -545,12 +505,6 @@ class UploadManager extends EventEmitter {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
if (signal.aborted || this.stopAfterActive) break;
if (!this._networkOnline) {
this._rotLog('network-wait', { jobId, hoster: task.hoster, fileName, attempt });
await this._waitForNetwork(signal);
if (signal.aborted || this.stopAfterActive) break;
}
if (attempt > 1) {
this._emitProgress(uploadId, fileName, task.hoster, { accountId: task.accountId,
jobId,
@ -719,18 +673,6 @@ class UploadManager extends EventEmitter {
}
lastError = err;
// Network outage in progress: the failure is the outage, not the
// file or the account. Hold the job until the gate reopens and
// retry WITHOUT consuming an attempt — a 30-minute ISP flake must
// not burn through the whole retry budget.
if (this._isTransientNetworkError(err) && !this._networkOnline) {
this._rotLog('network-hold', {
jobId, hoster: task.hoster, fileName, accountId: task.accountId, attempt
});
attempt--;
try { await this._waitForNetwork(signal); } catch { lastError = new Error('Abgebrochen'); break; }
continue;
}
// File-specific rejection — re-uploading won't change the server's
// mind. Break out immediately; the outer file-rejected branch then
// records the final error without burning through 5 × 3s retries.

55
main.js
View File

@ -229,57 +229,6 @@ function rotLog(msg, ts) {
} catch {}
}
const NET_CHECK_HOSTS = ['one.one.one.one', 'dns.google'];
let _netMonitorTimer = null;
let _netOnline = true;
let _netFails = 0;
let _netOks = 0;
let _netHostIdx = 0;
function _dnsProbe(host) {
return new Promise((resolve) => {
const timer = setTimeout(() => resolve(false), 5000);
try {
require('dns').resolve(host, (err) => { clearTimeout(timer); resolve(!err); });
} catch { clearTimeout(timer); resolve(false); }
});
}
async function _netCheckTick() {
const host = NET_CHECK_HOSTS[_netHostIdx++ % NET_CHECK_HOSTS.length];
const ok = await _dnsProbe(host);
if (ok) { _netOks++; _netFails = 0; } else { _netFails++; _netOks = 0; }
if (_netOnline && _netFails >= 2) {
_netOnline = false;
debugLog('network-monitor: OFFLINE (2 consecutive DNS probe failures)');
rotLog('network-monitor: offline — holding job starts + retries');
if (uploadManager && typeof uploadManager.setNetworkOnline === 'function') uploadManager.setNetworkOnline(false);
safeSend('network-status', { online: false });
} else if (!_netOnline && _netOks >= 2) {
_netOnline = true;
debugLog('network-monitor: ONLINE again (2 consecutive DNS probe successes)');
rotLog('network-monitor: online — resuming held jobs');
if (uploadManager && typeof uploadManager.setNetworkOnline === 'function') uploadManager.setNetworkOnline(true);
safeSend('network-status', { online: true });
}
}
function startNetworkMonitor() {
if (_netMonitorTimer) return;
_netOnline = true; _netFails = 0; _netOks = 0;
_netMonitorTimer = setInterval(() => { _netCheckTick().catch(() => {}); }, 8000);
debugLog('network-monitor: started (8s probe interval)');
}
function stopNetworkMonitor() {
if (_netMonitorTimer) { clearInterval(_netMonitorTimer); _netMonitorTimer = null; }
if (!_netOnline && uploadManager && typeof uploadManager.setNetworkOnline === 'function') {
uploadManager.setNetworkOnline(true);
}
_netOnline = true;
debugLog('network-monitor: stopped');
}
function _sleepMs(ms) { return new Promise((r) => setTimeout(r, ms)); }
async function _postWebhookWithRetry(req, maxAttempts) {
@ -1265,7 +1214,6 @@ app.on('window-all-closed', () => {
app.on('before-quit', () => {
if (uploadManager) try { uploadManager.cancel(); } catch {}
try { stopNetworkMonitor(); } catch {}
try { folderMonitor.stop(); } catch {}
try {
if (remoteServer) { remoteServer.stop(); remoteServer = null; }
@ -1733,7 +1681,6 @@ ipcMain.handle('start-upload', (_event, payload) => {
debugLog(`batch-done: total=${summary.total} ok=${summary.succeeded} fail=${summary.failed}`);
logMarker('BATCH END', { total: summary.total, ok: summary.succeeded, fail: summary.failed });
logMemorySnapshot('batch-done');
stopNetworkMonitor();
const _batchDurationSec = _thisManager && _thisManager.startTime
? Math.round((Date.now() - _thisManager.startTime) / 1000)
: 0;
@ -1763,13 +1710,11 @@ ipcMain.handle('start-upload', (_event, payload) => {
process.nextTick(() => {
if (!uploadManager) { debugLog('nextTick: uploadManager was nulled before startBatch'); return; }
debugLog(`nextTick: calling startBatch now (priming ${_sessionFailedAccounts.size} failed accounts, ${_sessionAccountOverrides.size} overrides from session)`);
startNetworkMonitor();
uploadManager.startBatch(tasks, {
primeFailedAccounts: Array.from(_sessionFailedAccounts.keys()),
primeOverrides: Array.from(_sessionAccountOverrides.entries())
}).catch((err) => {
debugLog(`startBatch REJECTED: ${err && err.stack ? err.stack : err}`);
stopNetworkMonitor();
const errorSummary = {
id: 'error',
timestamp: new Date().toISOString(),

View File

@ -120,9 +120,6 @@ contextBridge.exposeInMainWorld('api', {
resetAllSessionFailedAccounts: () => ipcRenderer.invoke('reset-all-session-failed-accounts'),
getLogPaths: () => ipcRenderer.invoke('get-log-paths'),
testWebhook: (payload) => ipcRenderer.invoke('test-webhook', payload),
onNetworkStatus: (callback) => {
ipcRenderer.on('network-status', (_event, data) => callback(data));
},
revealLogFile: (target) => ipcRenderer.invoke('reveal-log-file', target),
setLogVerbose: (enabled) => ipcRenderer.invoke('set-log-verbose', enabled),
createSupportBundle: () => ipcRenderer.invoke('create-support-bundle'),

View File

@ -140,18 +140,6 @@ async function init() {
handleStats(data);
});
window.api.onShutdownCountdown(handleShutdownCountdown);
if (window.api.onNetworkStatus) {
window.api.onNetworkStatus((data) => {
if (!data || typeof data !== 'object') return;
_networkOffline = !data.online;
if (_networkOffline) {
showCopyToast('Netzwerk-Ausfall erkannt — Uploads pausiert bis die Verbindung zurück ist.', 10000);
} else {
showCopyToast('Netzwerk wieder verfügbar — Uploads werden fortgesetzt.', 6000);
}
updateStatusBar();
});
}
window.api.onUploadLogFallback((data) => {
const path = data && data.fallbackPath ? data.fallbackPath : '(Fallback)';
showCopyToast(`Log-Pfad nicht beschreibbar — schreibe nach: ${path}`, 8000);
@ -2173,7 +2161,6 @@ function handleBatchDone(summary) {
}
let _sessionFailedKeys = new Set();
let _networkOffline = false;
const _autoRetryState = { round: 0, timer: null };
function _cancelAutoRetry(resetRound) {
@ -2631,15 +2618,13 @@ function updateStatusBar() {
? Math.round(stats.bytesRemaining / (lastUploadStats.globalSpeedKbs * 1024))
: 0;
const stateText = (_networkOffline && (uploading || lastUploadStats.state === 'uploading'))
? 'Netzwerk-Ausfall — pausiert'
: lastUploadStats.state === 'uploading'
? 'Upload läuft...'
: lastUploadStats.state === 'stopping'
? 'Stoppt nach aktiven Uploads...'
: uploading
? 'Upload vorbereitet...'
: 'Bereit';
const stateText = lastUploadStats.state === 'uploading'
? 'Upload läuft...'
: lastUploadStats.state === 'stopping'
? 'Stoppt nach aktiven Uploads...'
: uploading
? 'Upload vorbereitet...'
: 'Bereit';
document.getElementById('sbState').textContent = stateText;
document.getElementById('sbSpeed').textContent = formatSpeed(lastUploadStats.globalSpeedKbs || 0);

View File

@ -46,49 +46,6 @@ describe('UploadManager', () => {
UploadManager = require('../lib/upload-manager');
});
it('network gate: _waitForNetwork resolves immediately when online', async () => {
const mgr = new UploadManager({});
assert.strictEqual(mgr.isNetworkOnline(), true);
await mgr._waitForNetwork();
});
it('network gate: waiters block while offline and release on setNetworkOnline(true)', async () => {
const mgr = new UploadManager({});
mgr.setNetworkOnline(false);
assert.strictEqual(mgr.isNetworkOnline(), false);
let resolved = false;
const waiter = mgr._waitForNetwork().then(() => { resolved = true; });
await new Promise(r => setTimeout(r, 30));
assert.strictEqual(resolved, false, 'must still be blocked while offline');
mgr.setNetworkOnline(true);
await waiter;
assert.strictEqual(resolved, true);
});
it('network gate: abort signal rejects a pending waiter', async () => {
const mgr = new UploadManager({});
mgr.setNetworkOnline(false);
const ac = new AbortController();
const waiter = mgr._waitForNetwork(ac.signal);
ac.abort();
await assert.rejects(waiter, /Abgebrochen/);
});
it('network gate: batch with offline gate holds queued job until resume', async () => {
const mgr = new UploadManager({});
mgr.setNetworkOnline(false);
const statuses = [];
mgr.on('progress', (d) => statuses.push(d.status));
const batch = mgr.startBatch([
{ file: '/test/video1.mp4', hoster: 'doodstream.com', apiKey: 'key1' }
]);
await new Promise(r => setTimeout(r, 60));
assert.ok(!statuses.includes('done'), 'job must not complete while gate is closed');
mgr.setNetworkOnline(true);
await batch;
assert.ok(statuses.includes('done'), 'job completes after gate reopens');
});
it('emits progress events for each task', async () => {
const mgr = new UploadManager({});
const events = [];