@@ -2,7 +2,7 @@ import { asyncJobs, db } from '@sim/db'
22import { createLogger } from '@sim/logger'
33import { toError } from '@sim/utils/errors'
44import { generateShortId } from '@sim/utils/id'
5- import { eq , sql } from 'drizzle-orm'
5+ import { and , eq , inArray , sql } from 'drizzle-orm'
66import {
77 AsyncJobEnqueueError ,
88 type EnqueueOptions ,
@@ -261,7 +261,7 @@ export class DatabaseJobQueue implements JobQueueBackend {
261261 attempts : sql `${ asyncJobs . attempts } + 1` ,
262262 updatedAt : now ,
263263 } )
264- . where ( eq ( asyncJobs . id , jobId ) )
264+ . where ( and ( eq ( asyncJobs . id , jobId ) , eq ( asyncJobs . status , JOB_STATUS . PENDING ) ) )
265265
266266 logger . debug ( 'Started job' , { jobId } )
267267 }
@@ -277,7 +277,12 @@ export class DatabaseJobQueue implements JobQueueBackend {
277277 output : output as Record < string , unknown > ,
278278 updatedAt : now ,
279279 } )
280- . where ( eq ( asyncJobs . id , jobId ) )
280+ . where (
281+ and (
282+ eq ( asyncJobs . id , jobId ) ,
283+ inArray ( asyncJobs . status , [ JOB_STATUS . PENDING , JOB_STATUS . PROCESSING ] )
284+ )
285+ )
281286
282287 logger . debug ( 'Completed job' , { jobId } )
283288 }
@@ -293,33 +298,45 @@ export class DatabaseJobQueue implements JobQueueBackend {
293298 error,
294299 updatedAt : now ,
295300 } )
296- . where ( eq ( asyncJobs . id , jobId ) )
301+ . where (
302+ and (
303+ eq ( asyncJobs . id , jobId ) ,
304+ inArray ( asyncJobs . status , [ JOB_STATUS . PENDING , JOB_STATUS . PROCESSING ] )
305+ )
306+ )
297307
298308 logger . debug ( 'Marked job as failed' , { jobId } )
299309 }
300310
301311 async cancelJob ( jobId : string ) : Promise < void > {
302- // Abort any in-process inline execution first so the running workflow
303- // observes the signal and stops mid-flight. Then mark the row failed so
304- // any future poller skips it.
305- const controller = inlineAbortControllers . get ( jobId )
306- let aborted = false
307- if ( controller ) {
308- controller . abort ( 'Cancelled' )
309- inlineAbortControllers . delete ( jobId )
310- aborted = true
311- }
312-
313312 const now = new Date ( )
314- await db
313+ const cancelledJobs = await db
315314 . update ( asyncJobs )
316315 . set ( {
317316 status : JOB_STATUS . CANCELLED ,
318317 completedAt : now ,
319318 error : 'Cancelled' ,
320319 updatedAt : now ,
321320 } )
322- . where ( eq ( asyncJobs . id , jobId ) )
321+ . where (
322+ and (
323+ eq ( asyncJobs . id , jobId ) ,
324+ inArray ( asyncJobs . status , [ JOB_STATUS . PENDING , JOB_STATUS . PROCESSING ] )
325+ )
326+ )
327+ . returning ( { id : asyncJobs . id } )
328+
329+ if ( cancelledJobs . length === 0 ) {
330+ logger . debug ( 'Cancel target is no longer active in DB queue' , { jobId } )
331+ return
332+ }
333+
334+ const controller = inlineAbortControllers . get ( jobId )
335+ const aborted = Boolean ( controller )
336+ if ( controller ) {
337+ controller . abort ( 'Cancelled' )
338+ inlineAbortControllers . delete ( jobId )
339+ }
323340
324341 logger . debug ( 'Marked job as cancelled (DB queue)' , { jobId, abortedInline : aborted } )
325342 }
@@ -353,10 +370,16 @@ export class DatabaseJobQueue implements JobQueueBackend {
353370 await acquireSlot ( concurrencyKey , concurrencyLimit )
354371 }
355372 try {
373+ abortController . signal . throwIfAborted ( )
356374 await this . startJob ( jobId )
375+ abortController . signal . throwIfAborted ( )
357376 await runner ( payload , abortController . signal )
358377 await this . completeJob ( jobId , null )
359378 } catch ( err ) {
379+ if ( abortController . signal . aborted ) {
380+ logger . info ( `[${ type } ] Inline job ${ jobId } cancelled` )
381+ return
382+ }
360383 const message = toError ( err ) . message
361384 logger . error ( `[${ type } ] Inline job ${ jobId } failed` , { error : message } )
362385 try {
0 commit comments