|
9 | 9 | updateQueueConcurrencyLimits, |
10 | 10 | updateQueueTotalConcurrencyLimits, |
11 | 11 | } from "../runQueue.server"; |
12 | | -import { RunQueueConcurrencyKeyLimitExceededError } from "@internal/run-engine"; |
13 | 12 | import { engine } from "../runEngine.server"; |
14 | 13 |
|
15 | 14 | export type ConcurrencySystemOptions = { |
@@ -109,37 +108,6 @@ export class ConcurrencySystem { |
109 | 108 | .andThen((queue) => syncQueueTotalConcurrencyToEngine(environment, queue)) |
110 | 109 | .andThen((queue) => getQueueStats(environment, queue)); |
111 | 110 | }, |
112 | | - overrideConcurrencyKeyLimit: ( |
113 | | - environment: AuthenticatedEnvironment, |
114 | | - queue: QueueInput, |
115 | | - concurrencyKey: string, |
116 | | - concurrencyLimit: number, |
117 | | - overriddenBy?: User |
118 | | - ) => { |
119 | | - return findQueueFromInput(this.db, environment, queue) |
120 | | - .andThen((queue) => |
121 | | - overrideQueueConcurrencyKeyLimit( |
122 | | - this.db, |
123 | | - environment, |
124 | | - queue, |
125 | | - concurrencyKey, |
126 | | - concurrencyLimit, |
127 | | - overriddenBy |
128 | | - ) |
129 | | - ) |
130 | | - .andThen((queue) => getQueueStats(environment, queue)); |
131 | | - }, |
132 | | - resetConcurrencyKeyLimit: ( |
133 | | - environment: AuthenticatedEnvironment, |
134 | | - queue: QueueInput, |
135 | | - concurrencyKey: string |
136 | | - ) => { |
137 | | - return findQueueFromInput(this.db, environment, queue) |
138 | | - .andThen((queue) => |
139 | | - resetQueueConcurrencyKeyLimit(this.db, environment, queue, concurrencyKey) |
140 | | - ) |
141 | | - .andThen((queue) => getQueueStats(environment, queue)); |
142 | | - }, |
143 | 111 | /** |
144 | 112 | * Recalculates the materialized limit of every percent-based override in the environment |
145 | 113 | * against its CURRENT maximumConcurrencyLimit and syncs changed queues to the run engine. |
@@ -498,153 +466,6 @@ function syncQueueTotalConcurrencyToEngine( |
498 | 466 | })).andThen(() => okAsync(queue)); |
499 | 467 | } |
500 | 468 |
|
501 | | -/** |
502 | | - * Persist first, then enforce: a database failure leaves nothing enforced and the |
503 | | - * request errors cleanly, while an engine failure after persistence leaves a durable |
504 | | - * record and a retry converges (the upsert is idempotent). A cap rejection deletes |
505 | | - * the row unconditionally: the cap only rejects keys absent from the engine hash |
506 | | - * (updates to present keys always succeed), so a cap-rejected row is never backing |
507 | | - * an enforced limit and must not survive as an authoritative override. |
508 | | - */ |
509 | | -function overrideQueueConcurrencyKeyLimit( |
510 | | - db: PrismaClientOrTransaction, |
511 | | - environment: AuthenticatedEnvironment, |
512 | | - queue: TaskQueue, |
513 | | - concurrencyKey: string, |
514 | | - concurrencyLimit: number, |
515 | | - overriddenBy?: User |
516 | | -) { |
517 | | - const maximum = environment.maximumConcurrencyLimit; |
518 | | - |
519 | | - if (!Number.isFinite(concurrencyLimit) || concurrencyLimit < 0) { |
520 | | - return errAsync({ |
521 | | - type: "invalid_override" as const, |
522 | | - message: "Concurrency limit must be a non-negative number", |
523 | | - }); |
524 | | - } |
525 | | - |
526 | | - if (concurrencyLimit > maximum) { |
527 | | - return errAsync({ |
528 | | - type: "concurrency_limit_exceeds_maximum" as const, |
529 | | - message: `Concurrency limit (${concurrencyLimit}) cannot exceed the environment limit (${maximum})`, |
530 | | - }); |
531 | | - } |
532 | | - |
533 | | - return fromPromise( |
534 | | - db.taskQueueConcurrencyKeyOverride.findFirst({ |
535 | | - where: { taskQueueId: queue.id, concurrencyKey }, |
536 | | - select: { id: true }, |
537 | | - }), |
538 | | - (error) => ({ type: "other" as const, cause: error }) |
539 | | - ) |
540 | | - .andThen(() => |
541 | | - fromPromise( |
542 | | - db.taskQueueConcurrencyKeyOverride.upsert({ |
543 | | - where: { taskQueueId_concurrencyKey: { taskQueueId: queue.id, concurrencyKey } }, |
544 | | - create: { |
545 | | - taskQueueId: queue.id, |
546 | | - concurrencyKey, |
547 | | - concurrencyLimit, |
548 | | - overriddenBy: overriddenBy?.id ?? null, |
549 | | - }, |
550 | | - update: { |
551 | | - concurrencyLimit, |
552 | | - overriddenAt: new Date(), |
553 | | - overriddenBy: overriddenBy?.id ?? null, |
554 | | - }, |
555 | | - }), |
556 | | - (error) => ({ |
557 | | - type: "queue_update_failed" as const, |
558 | | - cause: error, |
559 | | - }) |
560 | | - ) |
561 | | - ) |
562 | | - .andThen((written) => |
563 | | - fromPromise( |
564 | | - engine.runQueue.updateQueueConcurrencyKeyLimit( |
565 | | - environment, |
566 | | - queue.name, |
567 | | - concurrencyKey, |
568 | | - concurrencyLimit |
569 | | - ), |
570 | | - (error) => { |
571 | | - if (error instanceof RunQueueConcurrencyKeyLimitExceededError) { |
572 | | - return { type: "too_many_key_overrides" as const, message: error.message }; |
573 | | - } |
574 | | - return { type: "sync_queue_concurrency_to_engine_failed" as const, cause: error }; |
575 | | - } |
576 | | - ).orElse((error) => { |
577 | | - if (error.type === "too_many_key_overrides") { |
578 | | - return fromPromise( |
579 | | - /** |
580 | | - * Deletes only the exact row generation this request wrote, so a |
581 | | - * concurrent request that succeeded after capacity freed keeps its |
582 | | - * durable record. |
583 | | - */ |
584 | | - db.taskQueueConcurrencyKeyOverride.deleteMany({ |
585 | | - where: { id: written.id, overriddenAt: written.overriddenAt }, |
586 | | - }), |
587 | | - () => error |
588 | | - ).andThen(() => errAsync(error)); |
589 | | - } |
590 | | - return errAsync(error); |
591 | | - }) |
592 | | - ) |
593 | | - .andThen(() => okAsync(queue)); |
594 | | -} |
595 | | - |
596 | | -/** |
597 | | - * Enforce first, then persist: removing the engine limit is idempotent, so an engine |
598 | | - * failure leaves the override row in place and a retry converges instead of being |
599 | | - * rejected as not overridden while the old limit is still enforced. |
600 | | - */ |
601 | | -function resetQueueConcurrencyKeyLimit( |
602 | | - db: PrismaClientOrTransaction, |
603 | | - environment: AuthenticatedEnvironment, |
604 | | - queue: TaskQueue, |
605 | | - concurrencyKey: string |
606 | | -) { |
607 | | - return fromPromise( |
608 | | - db.taskQueueConcurrencyKeyOverride.findFirst({ |
609 | | - where: { taskQueueId: queue.id, concurrencyKey }, |
610 | | - select: { id: true, overriddenAt: true }, |
611 | | - }), |
612 | | - (error) => ({ type: "other" as const, cause: error }) |
613 | | - ).andThen((existing) => { |
614 | | - if (!existing) { |
615 | | - return errAsync({ type: "queue_not_overridden" as const }); |
616 | | - } |
617 | | - |
618 | | - return fromPromise( |
619 | | - engine.runQueue.removeQueueConcurrencyKeyLimit(environment, queue.name, concurrencyKey), |
620 | | - (error) => ({ |
621 | | - type: "sync_queue_concurrency_to_engine_failed" as const, |
622 | | - cause: error, |
623 | | - }) |
624 | | - ) |
625 | | - .andThen(() => |
626 | | - fromPromise( |
627 | | - /** |
628 | | - * Deletes only the exact row generation this reset read, so a concurrent |
629 | | - * override that re-wrote the row after the reset began keeps its record |
630 | | - * (its next write, or the deploy-time restore, re-syncs the engine). |
631 | | - */ |
632 | | - db.taskQueueConcurrencyKeyOverride.deleteMany({ |
633 | | - where: { |
634 | | - id: existing.id, |
635 | | - overriddenAt: existing.overriddenAt, |
636 | | - }, |
637 | | - }), |
638 | | - (error) => ({ |
639 | | - type: "queue_update_failed" as const, |
640 | | - cause: error, |
641 | | - }) |
642 | | - ) |
643 | | - ) |
644 | | - .andThen(() => okAsync(queue)); |
645 | | - }); |
646 | | -} |
647 | | - |
648 | 469 | function getQueueStats(environment: AuthenticatedEnvironment, queue: TaskQueue) { |
649 | 470 | return fromPromise( |
650 | 471 | Promise.all([ |
|
0 commit comments