Fix concurrent upload batch accounting

Reserve accepted job IDs before asynchronous upload preparation so parallel additions cannot start duplicate jobs or replace their abort controllers. Track late-added jobs in the batch total and keep failure counts non-negative, with focused race and summary regressions.
This commit is contained in:
Sucukdeluxe
2026-08-13 21:42:14 +02:00
parent df9d5a5af4
commit 86d10aad1f
2 changed files with 77 additions and 4 deletions
+9 -3
View File
@@ -53,6 +53,8 @@ class UploadManager extends EventEmitter {
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._recoveryClaims = createRecoveryClaimRegistry();
this._batchJobIds = new Set();
this._batchTotal = 0;
}
updateAccountPools(accountPools) {
@@ -336,6 +338,8 @@ class UploadManager extends EventEmitter {
async startBatch(tasks, opts = {}) {
const pendingCancelledJobIds = new Set(this.pendingCancelledJobIds);
const pendingCancelAll = this.pendingCancelAll;
this._batchJobIds = new Set(tasks.map((task) => task.jobId).filter(Boolean));
this._batchTotal = tasks.length;
this.pendingCancelledJobIds.clear();
this.pendingCancelAll = false;
this.running = true;
@@ -422,7 +426,7 @@ class UploadManager extends EventEmitter {
this._emitStats();
const files = Array.from(results.values());
const total = tasks.length;
const total = this._batchTotal;
const succeeded = files.reduce((count, file) => count + file.results.filter((result) => result.status === 'done').length, 0);
const skipped = files.reduce((count, file) => count + file.results.filter((result) => result.status === 'skipped').length, 0);
@@ -431,7 +435,7 @@ class UploadManager extends EventEmitter {
timestamp: new Date().toISOString(),
total,
succeeded,
failed: total - succeeded - skipped,
failed: Math.max(0, total - succeeded - skipped),
skipped,
files
};
@@ -1461,16 +1465,18 @@ class UploadManager extends EventEmitter {
const addResult = { added: 0, alreadyInBatchJobIds: [] };
for (const task of tasks) {
// Skip if this job is already being processed (prevent duplicates)
if (task.jobId && this.jobAbortControllers.has(task.jobId)) {
if (task.jobId && this._batchJobIds.has(task.jobId)) {
addResult.alreadyInBatchJobIds.push(task.jobId);
continue;
}
if (task.jobId) this._batchJobIds.add(task.jobId);
const fileName = path.basename(task.file);
if (!results.has(task.file)) {
let size = 0;
try { size = fs.statSync(task.file).size; } catch {}
results.set(task.file, { name: fileName, size, results: [] });
}
this._batchTotal++;
this._additionalPromises.push(this._runJob(task, results, signal));
addResult.added++;
}