11import type { TaskQueue , User } from "@trigger.dev/database" ;
22import { errAsync , fromPromise , okAsync } from "neverthrow" ;
3- import type { PrismaClientOrTransaction } from "~/db.server" ;
3+ import { Prisma , type PrismaClientOrTransaction } from "~/db.server" ;
44import type { AuthenticatedEnvironment } from "~/services/apiAuth.server" ;
55import {
66 removeQueueConcurrencyLimits ,
@@ -128,8 +128,8 @@ export class ConcurrencyLimitsSystem {
128128 } ,
129129 reset : ( environment : AuthenticatedEnvironment , name : string ) => {
130130 return findLimitByName ( this . db , environment , name )
131+ . andThen ( ( row ) => syncResetToEngine ( environment , row ) )
131132 . andThen ( ( row ) => resetLimitOverrides ( this . db , row ) )
132- . andThen ( ( row ) => syncLimitToEngine ( environment , row ) )
133133 . andThen ( ( row ) =>
134134 fromPromise ( toLimitItems ( environment , [ row ] ) , ( error ) => ( {
135135 type : "other" as const ,
@@ -246,17 +246,43 @@ function applyLimitOverride(
246246 data . totalConcurrencyLimitOverriddenBy = overriddenBy ?. id ?? null ;
247247 }
248248
249- return fromPromise ( db . taskQueue . update ( { where : { id : row . id } , data } ) , ( error ) => ( {
250- type : "limit_update_failed" as const ,
251- cause : error ,
252- } ) ) ;
249+ return guardedLimitUpdate ( db , row , data ) ;
253250}
254251
255- function resetLimitOverrides ( db : PrismaClientOrTransaction , row : TaskQueue ) {
252+ /**
253+ * Enforce first, then persist: the engine syncs to the declared base BEFORE the
254+ * override markers clear, so an engine failure leaves the markers set and a retry
255+ * converges instead of being rejected while the overridden limit stays enforced.
256+ */
257+ function syncResetToEngine ( environment : AuthenticatedEnvironment , row : TaskQueue ) {
256258 if ( row . concurrencyLimitOverriddenAt === null && row . totalConcurrencyLimitOverriddenAt === null ) {
257259 return errAsync ( { type : "limit_not_overridden" as const } ) ;
258260 }
259261
262+ const perKeyTarget = row . concurrencyLimitOverriddenAt
263+ ? row . concurrencyLimitBase
264+ : row . concurrencyLimit ;
265+ const totalTarget = row . totalConcurrencyLimitOverriddenAt
266+ ? row . totalConcurrencyLimitBase
267+ : row . totalConcurrencyLimit ;
268+
269+ const perKeySync =
270+ typeof perKeyTarget === "number"
271+ ? updateQueueConcurrencyLimits ( environment , row . name , perKeyTarget )
272+ : removeQueueConcurrencyLimits ( environment , row . name ) ;
273+
274+ const totalSync =
275+ typeof totalTarget === "number"
276+ ? updateQueueTotalConcurrencyLimits ( environment , row . name , totalTarget )
277+ : removeQueueTotalConcurrencyLimits ( environment , row . name ) ;
278+
279+ return fromPromise ( Promise . all ( [ perKeySync , totalSync ] ) , ( error ) => ( {
280+ type : "sync_limit_to_engine_failed" as const ,
281+ cause : error ,
282+ } ) ) . map ( ( ) => row ) ;
283+ }
284+
285+ function resetLimitOverrides ( db : PrismaClientOrTransaction , row : TaskQueue ) {
260286 const data : Record < string , unknown > = { } ;
261287
262288 if ( row . concurrencyLimitOverriddenAt !== null ) {
@@ -273,10 +299,35 @@ function resetLimitOverrides(db: PrismaClientOrTransaction, row: TaskQueue) {
273299 data . totalConcurrencyLimitOverriddenBy = null ;
274300 }
275301
276- return fromPromise ( db . taskQueue . update ( { where : { id : row . id } , data } ) , ( error ) => ( {
277- type : "limit_update_failed" as const ,
278- cause : error ,
279- } ) ) ;
302+ return guardedLimitUpdate ( db , row , data ) ;
303+ }
304+
305+ /**
306+ * Optimistic update: the where clause carries the override markers as read, so a
307+ * concurrent override or reset makes this update miss (P2025) and the caller gets
308+ * a conflict instead of silently clobbering the newer state.
309+ */
310+ function guardedLimitUpdate (
311+ db : PrismaClientOrTransaction ,
312+ row : TaskQueue ,
313+ data : Record < string , unknown >
314+ ) {
315+ return fromPromise (
316+ db . taskQueue . update ( {
317+ where : {
318+ id : row . id ,
319+ concurrencyLimitOverriddenAt : row . concurrencyLimitOverriddenAt ,
320+ totalConcurrencyLimitOverriddenAt : row . totalConcurrencyLimitOverriddenAt ,
321+ } ,
322+ data,
323+ } ) ,
324+ ( error ) => {
325+ if ( error instanceof Prisma . PrismaClientKnownRequestError && error . code === "P2025" ) {
326+ return { type : "conflict" as const } ;
327+ }
328+ return { type : "limit_update_failed" as const , cause : error } ;
329+ }
330+ ) ;
280331}
281332
282333/**
0 commit comments