fix: preserve automation queue safety limit
This commit is contained in:
@@ -14,10 +14,20 @@
|
|||||||
return Array.isArray(value) ? value : [];
|
return Array.isArray(value) ? value : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeQueueLimit(value) {
|
||||||
|
const isNumber = typeof value === 'number';
|
||||||
|
const isString = typeof value === 'string';
|
||||||
|
if (!isNumber && !isString) return 15000;
|
||||||
|
if (isString && value.trim() === '') return 15000;
|
||||||
|
const number = Number(value);
|
||||||
|
if (!Number.isFinite(number) || number < 0) return 15000;
|
||||||
|
if (number === 0 && isString && value.trim() !== '0') return 15000;
|
||||||
|
return Math.floor(number);
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeAutomationSettings(value = {}) {
|
function normalizeAutomationSettings(value = {}) {
|
||||||
const settings = asObject(value);
|
const settings = asObject(value);
|
||||||
const rawLimit = Number(settings.queueLimitJobs);
|
const queueLimitJobs = normalizeQueueLimit(settings.queueLimitJobs);
|
||||||
const queueLimitJobs = Number.isFinite(rawLimit) && rawLimit >= 0 ? Math.floor(rawLimit) : 15000;
|
|
||||||
const rawInterval = Number(settings.reconcileIntervalMinutes);
|
const rawInterval = Number(settings.reconcileIntervalMinutes);
|
||||||
return {
|
return {
|
||||||
queueLimitJobs,
|
queueLimitJobs,
|
||||||
@@ -39,8 +49,7 @@
|
|||||||
);
|
);
|
||||||
const rawCurrentJobCount = Number(value.currentJobCount);
|
const rawCurrentJobCount = Number(value.currentJobCount);
|
||||||
const currentJobCount = Number.isFinite(rawCurrentJobCount) ? Math.max(0, Math.floor(rawCurrentJobCount)) : 0;
|
const currentJobCount = Number.isFinite(rawCurrentJobCount) ? Math.max(0, Math.floor(rawCurrentJobCount)) : 0;
|
||||||
const rawQueueLimitJobs = value.queueLimitJobs === undefined ? 15000 : Number(value.queueLimitJobs);
|
const queueLimitJobs = normalizeQueueLimit(value.queueLimitJobs);
|
||||||
const queueLimitJobs = Number.isFinite(rawQueueLimitJobs) && rawQueueLimitJobs >= 0 ? Math.floor(rawQueueLimitJobs) : 15000;
|
|
||||||
let available = queueLimitJobs === 0 ? Number.POSITIVE_INFINITY : Math.max(0, queueLimitJobs - currentJobCount);
|
let available = queueLimitJobs === 0 ? Number.POSITIVE_INFINITY : Math.max(0, queueLimitJobs - currentJobCount);
|
||||||
const admittedPaths = [];
|
const admittedPaths = [];
|
||||||
const deferredPaths = [];
|
const deferredPaths = [];
|
||||||
|
|||||||
@@ -49,6 +49,16 @@ test('automation settings normalize invalid limits intervals and pause timestamp
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('automation settings allow only numeric and trimmed string zero to disable the queue limit', () => {
|
||||||
|
const invalidLimits = [null, '', ' ', false, true, {}, undefined, NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, '0.0', '00', '+0', '-0'];
|
||||||
|
assert.deepEqual(
|
||||||
|
invalidLimits.map((queueLimitJobs) => normalizeAutomationSettings({ queueLimitJobs }).queueLimitJobs),
|
||||||
|
invalidLimits.map(() => 15000)
|
||||||
|
);
|
||||||
|
assert.equal(normalizeAutomationSettings({ queueLimitJobs: 0 }).queueLimitJobs, 0);
|
||||||
|
assert.equal(normalizeAutomationSettings({ queueLimitJobs: ' 0 ' }).queueLimitJobs, 0);
|
||||||
|
});
|
||||||
|
|
||||||
test('capacity counts only executable and running queue jobs', () => {
|
test('capacity counts only executable and running queue jobs', () => {
|
||||||
const statuses = ['preview', 'queued', 'getting-server', 'uploading', 'retrying', 'done', 'error', 'aborted', 'skipped'];
|
const statuses = ['preview', 'queued', 'getting-server', 'uploading', 'retrying', 'done', 'error', 'aborted', 'skipped'];
|
||||||
assert.equal(countAutomaticQueueJobs(statuses.map((status, index) => ({ id: String(index), status }))), 5);
|
assert.equal(countAutomaticQueueJobs(statuses.map((status, index) => ({ id: String(index), status }))), 5);
|
||||||
@@ -111,6 +121,31 @@ test('admission tolerates malformed candidates and numeric inputs', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('admission allows only numeric and trimmed string zero to disable the queue limit', () => {
|
||||||
|
const invalidLimits = [null, '', ' ', false, true, {}, undefined, NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, '0.0', '00', '+0', '-0'];
|
||||||
|
const candidate = { path: 'large.mkv', mtimeMs: 1, eligibleJobCount: 16000 };
|
||||||
|
assert.deepEqual(
|
||||||
|
invalidLimits.map((queueLimitJobs) => planAtomicAdmissions({ candidates: [candidate], queueLimitJobs })),
|
||||||
|
invalidLimits.map(() => ({
|
||||||
|
admittedPaths: [],
|
||||||
|
deferredPaths: ['large.mkv'],
|
||||||
|
currentJobCount: 0,
|
||||||
|
plannedJobs: 0,
|
||||||
|
availableSlots: 15000
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
[0, ' 0 '].map((queueLimitJobs) => planAtomicAdmissions({ candidates: [candidate], queueLimitJobs })),
|
||||||
|
[0, ' 0 '].map(() => ({
|
||||||
|
admittedPaths: ['large.mkv'],
|
||||||
|
deferredPaths: [],
|
||||||
|
currentJobCount: 0,
|
||||||
|
plannedJobs: 16000,
|
||||||
|
availableSlots: null
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('daily telemetry resets atomically on the local calendar day boundary', () => {
|
test('daily telemetry resets atomically on the local calendar day boundary', () => {
|
||||||
const before = { dateKey: '2026-08-25', detected: 8, queued: 4, skipped: 2, deferred: 1 };
|
const before = { dateKey: '2026-08-25', detected: 8, queued: 4, skipped: 2, deferred: 1 };
|
||||||
const now = new Date(2026, 7, 26, 0, 0, 1).getTime();
|
const now = new Date(2026, 7, 26, 0, 0, 1).getTime();
|
||||||
|
|||||||
Reference in New Issue
Block a user