Skip to content

Commit c5a2e21

Browse files
committed
fix(webapp): trigger-time concurrency keeps the task's inline limit gate
Passing concurrency at trigger time replaces only the task's declared named limits. The anonymous limit/task/ gate that carries an inline limit on a shared queue is preserved, and an empty array clears named limits without dropping it. Trigger-time limit names are also validated against the queue-safe charset so lossy sanitization can never remap them onto another limit's row.
1 parent 1cfd64f commit c5a2e21

1 file changed

Lines changed: 26 additions & 5 deletions

File tree

‎apps/webapp/app/runEngine/concerns/queues.server.ts‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,32 @@ export class DefaultQueueManager implements QueueManager {
221221
}
222222

223223
const triggerLimits = request.body.options?.concurrency;
224-
const concurrencyGates = triggerLimits?.map(
225-
(name): { queue: string; concurrencyKey?: string } => ({
226-
queue: `limit/${sanitizeQueueName(name)}`,
227-
})
224+
225+
for (const name of triggerLimits ?? []) {
226+
if (!/^[a-zA-Z0-9_-]{1,122}$/.test(name)) {
227+
throw new ServiceValidationError(
228+
`Invalid concurrency limit name "${name}": names are 1-122 characters using only letters, numbers, underscores and hyphens.`
229+
);
230+
}
231+
}
232+
233+
/**
234+
* Trigger-time names replace the task's declared NAMED limits only. The task's
235+
* inline limit rides in its stored gates as an anonymous "limit/task/" gate and
236+
* always applies, so it is carried over into the replacement (an empty array
237+
* clears the named limits but keeps the inline one).
238+
*/
239+
const inlineTaskGates = (taskGates ?? []).filter((gate) =>
240+
gate.queue.startsWith("limit/task/")
228241
);
242+
const concurrencyGates = triggerLimits
243+
? [
244+
...inlineTaskGates,
245+
...triggerLimits.map((name): { queue: string; concurrencyKey?: string } => ({
246+
queue: `limit/${name}`,
247+
})),
248+
]
249+
: undefined;
229250

230251
const requestedGates =
231252
concurrencyGates ?? request.body.options?.gates ?? taskGates ?? undefined;
@@ -234,7 +255,7 @@ export class DefaultQueueManager implements QueueManager {
234255
const sanitized = sanitizeQueueName(gate.queue);
235256
return sanitized ? [{ queue: sanitized, concurrencyKey: gate.concurrencyKey }] : [];
236257
})
237-
.slice(0, 2);
258+
.slice(0, 3);
238259

239260
return {
240261
queueName,

0 commit comments

Comments
 (0)