diff --git a/services/web-ui/public/jobs.html b/services/web-ui/public/jobs.html index f87c205..d52592f 100644 --- a/services/web-ui/public/jobs.html +++ b/services/web-ui/public/jobs.html @@ -656,14 +656,25 @@ function renderRow(job) { ${dur} - + + `; return tr; } +async function killJob(jobId, ev) { + ev.stopPropagation(); + if (!confirm('Remove this job from the queue? If a worker is still processing it, the run is abandoned.')) return; + try { + const r = await fetch('/api/v1/jobs/' + encodeURIComponent(jobId), { method: 'DELETE', credentials: 'include' }); + if (r.ok) { toast('Job removed', 'success'); fetchJobs(); } + else { const d = await r.json().catch(()=>({})); toast('Remove failed: ' + (d.error || r.statusText), 'error'); } + } catch (err) { + toast('Remove failed: ' + err.message, 'error'); + } +} + function statusBadge(status) { const map = { active: 'Active', diff --git a/services/worker/src/index.js b/services/worker/src/index.js index 5c238d7..53163cb 100644 --- a/services/worker/src/index.js +++ b/services/worker/src/index.js @@ -16,7 +16,15 @@ const parseRedisUrl = (url) => { const redisOptions = parseRedisUrl(process.env.REDIS_URL || 'redis://localhost:6379'); const createWorker = (queueName, handler) => { - const worker = new Worker(queueName, handler, { connection: redisOptions }); + const worker = new Worker(queueName, handler, { + connection: redisOptions, + // Stall detection: if a worker dies mid-job, BullMQ moves it back to wait + // after stalledInterval. Without this a crashed run sits in active forever. + stalledInterval: 30000, + maxStalledCount: 1, + lockDuration: 60000, + lockRenewTime: 15000, + }); worker.on('completed', (job) => { console.log(`[${queueName}] Job ${job.id} completed`); @@ -26,7 +34,10 @@ const createWorker = (queueName, handler) => { console.error(`[${queueName}] Job ${job.id} failed:`, err.message); }); - // job.progress is a property (the value set by updateProgress), not a function + worker.on('stalled', (jobId) => { + console.warn(`[${queueName}] Job ${jobId} stalled — reclaimed`); + }); + worker.on('progress', (job, progress) => { console.log(`[${queueName}] Job ${job.id} progress:`, progress); });