fix: multiple bugs found in deep code analysis
- Guard startBatch against null uploadManager in nextTick (race on fast cancel) - Fix updateSettings not creating globalThrottle when none existed at start - Fix updateSettings not updating globalSemaphore limit live - Fix retry pause: 2500ms → 3000ms as intended - Remove dead isError code in history (was always false after continue) - Add signal.aborted check in API upload generator (hosters.js) - Add extra signal check in throttle consume loop for faster abort - Fix doodstream debug log path (process.cwd → __dirname) - Fix updater fetchJson signal listener leak - Make progress column sortable in queue table Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
153ea2b193
commit
25a6b77650
@ -10,7 +10,7 @@ const UPLOAD_TIMEOUT = 1800000; // 30 min
|
|||||||
function _debugLog(msg) {
|
function _debugLog(msg) {
|
||||||
try {
|
try {
|
||||||
const ts = new Date().toISOString();
|
const ts = new Date().toISOString();
|
||||||
fs.appendFileSync(path.join(process.cwd(), 'doodstream-debug.log'), `[${ts}] ${msg}\n`);
|
fs.appendFileSync(path.join(__dirname, '..', 'doodstream-debug.log'), `[${ts}] ${msg}\n`);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -254,6 +254,7 @@ function createUploadBody(filePath, formFields, onProgress, throttle, signal) {
|
|||||||
yield preambleBuf;
|
yield preambleBuf;
|
||||||
const fileStream = fs.createReadStream(filePath, { highWaterMark: CHUNK_SIZE });
|
const fileStream = fs.createReadStream(filePath, { highWaterMark: CHUNK_SIZE });
|
||||||
for await (const chunk of fileStream) {
|
for await (const chunk of fileStream) {
|
||||||
|
if (signal && signal.aborted) throw new Error('Aborted');
|
||||||
if (throttle) await throttle.consume(chunk.length, signal);
|
if (throttle) await throttle.consume(chunk.length, signal);
|
||||||
bytesRead += chunk.length;
|
bytesRead += chunk.length;
|
||||||
yield chunk;
|
yield chunk;
|
||||||
|
|||||||
@ -21,6 +21,7 @@ class Throttle {
|
|||||||
bytes -= available;
|
bytes -= available;
|
||||||
}
|
}
|
||||||
if (bytes > 0) {
|
if (bytes > 0) {
|
||||||
|
if (signal && signal.aborted) return;
|
||||||
// Wait 50ms for tokens to refill
|
// Wait 50ms for tokens to refill
|
||||||
await new Promise((r) => setTimeout(r, 50));
|
await new Promise((r) => setTimeout(r, 50));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,8 @@ function findLatestYml(assets) {
|
|||||||
async function fetchJson(url, signal) {
|
async function fetchJson(url, signal) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT);
|
const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT);
|
||||||
if (signal) signal.addEventListener('abort', () => controller.abort());
|
const onAbort = () => controller.abort();
|
||||||
|
if (signal) signal.addEventListener('abort', onAbort);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
@ -67,6 +68,7 @@ async function fetchJson(url, signal) {
|
|||||||
return await res.json();
|
return await res.json();
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
|
if (signal) signal.removeEventListener('abort', onAbort);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,13 +48,20 @@ class UploadManager extends EventEmitter {
|
|||||||
sem.updateLimit(settings.parallelCount);
|
sem.updateLimit(settings.parallelCount);
|
||||||
}
|
}
|
||||||
// Update global throttle if speed limit changed
|
// Update global throttle if speed limit changed
|
||||||
if (this.globalThrottle) {
|
|
||||||
const newKbs = (this.globalSettings.globalMaxSpeedKbs || 0);
|
const newKbs = (this.globalSettings.globalMaxSpeedKbs || 0);
|
||||||
if (newKbs > 0) {
|
if (newKbs > 0) {
|
||||||
|
if (this.globalThrottle) {
|
||||||
this.globalThrottle.updateRate(newKbs * 1024);
|
this.globalThrottle.updateRate(newKbs * 1024);
|
||||||
|
} else {
|
||||||
|
this.globalThrottle = new Throttle(newKbs * 1024);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.globalThrottle = null;
|
this.globalThrottle = null;
|
||||||
}
|
}
|
||||||
|
// Update global semaphore live
|
||||||
|
const globalLimit = this._getGlobalParallelLimit();
|
||||||
|
if (globalLimit > 0 && this.globalSemaphore) {
|
||||||
|
this.globalSemaphore.updateLimit(globalLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +269,7 @@ class UploadManager extends EventEmitter {
|
|||||||
attempt,
|
attempt,
|
||||||
maxAttempts
|
maxAttempts
|
||||||
});
|
});
|
||||||
await this._sleep(2500, signal);
|
await this._sleep(3000, signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
const jobStart = Date.now();
|
const jobStart = Date.now();
|
||||||
|
|||||||
1
main.js
1
main.js
@ -577,6 +577,7 @@ ipcMain.handle('start-upload', (_event, payload) => {
|
|||||||
// This ensures webContents.send() calls from upload events
|
// This ensures webContents.send() calls from upload events
|
||||||
// are not interleaved with the handle() response.
|
// are not interleaved with the handle() response.
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
|
if (!uploadManager) { debugLog('nextTick: uploadManager was nulled before startBatch'); return; }
|
||||||
debugLog('nextTick: calling startBatch now');
|
debugLog('nextTick: calling startBatch now');
|
||||||
uploadManager.startBatch(tasks).catch((err) => {
|
uploadManager.startBatch(tasks).catch((err) => {
|
||||||
debugLog(`startBatch REJECTED: ${err && err.stack ? err.stack : err}`);
|
debugLog(`startBatch REJECTED: ${err && err.stack ? err.stack : err}`);
|
||||||
|
|||||||
@ -691,6 +691,7 @@ function sortQueueJobs(jobs) {
|
|||||||
else if (key === 'host') cmp = a.hoster.localeCompare(b.hoster);
|
else if (key === 'host') cmp = a.hoster.localeCompare(b.hoster);
|
||||||
else if (key === 'status') cmp = getStatusOrder(a.status) - getStatusOrder(b.status);
|
else if (key === 'status') cmp = getStatusOrder(a.status) - getStatusOrder(b.status);
|
||||||
else if (key === 'speed') cmp = (a.speedKbs || 0) - (b.speedKbs || 0);
|
else if (key === 'speed') cmp = (a.speedKbs || 0) - (b.speedKbs || 0);
|
||||||
|
else if (key === 'progress') cmp = (a.progress || 0) - (b.progress || 0);
|
||||||
return cmp * factor;
|
return cmp * factor;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1951,12 +1952,11 @@ async function loadHistory() {
|
|||||||
for (const file of (batch.files || [])) {
|
for (const file of (batch.files || [])) {
|
||||||
for (const result of (file.results || [])) {
|
for (const result of (file.results || [])) {
|
||||||
if (result.status === 'aborted' || result.status === 'error') continue;
|
if (result.status === 'aborted' || result.status === 'error') continue;
|
||||||
const isError = result.status === 'error';
|
|
||||||
historyRowsData.push({
|
historyRowsData.push({
|
||||||
date: dt.text, dateTs: dt.ts,
|
date: dt.text, dateTs: dt.ts,
|
||||||
filename: file.name || '', host: result.hoster || '',
|
filename: file.name || '', host: result.hoster || '',
|
||||||
link: isError ? `[Fehler] ${result.error || ''}` : (result.download_url || result.embed_url || ''),
|
link: result.download_url || result.embed_url || '',
|
||||||
isError, order: order++
|
isError: false, order: order++
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,7 @@
|
|||||||
<th class="col-elapsed">Zeit</th>
|
<th class="col-elapsed">Zeit</th>
|
||||||
<th class="col-remaining">Rest</th>
|
<th class="col-remaining">Rest</th>
|
||||||
<th class="col-speed sortable" data-sort="speed">Speed</th>
|
<th class="col-speed sortable" data-sort="speed">Speed</th>
|
||||||
<th class="col-progress">Fortschritt</th>
|
<th class="col-progress sortable" data-sort="progress">Fortschritt</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="queueBody"></tbody>
|
<tbody id="queueBody"></tbody>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user