Skip to content

Commit c7bfe5c

Browse files
committed
WIP
1 parent 7f7f993 commit c7bfe5c

File tree

23 files changed

+3715
-11
lines changed

23 files changed

+3715
-11
lines changed

apps/webapp/app/components/runs/v3/BatchStatus.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/20/solid";
1+
import {
2+
CheckCircleIcon,
3+
ExclamationTriangleIcon,
4+
XCircleIcon,
5+
} from "@heroicons/react/20/solid";
26
import type { BatchTaskRunStatus } from "@trigger.dev/database";
37
import assertNever from "assert-never";
48
import { Spinner } from "~/components/primitives/Spinner";
59
import { cn } from "~/utils/cn";
610

7-
export const allBatchStatuses = ["PENDING", "COMPLETED", "ABORTED"] as const satisfies Readonly<
8-
Array<BatchTaskRunStatus>
9-
>;
11+
export const allBatchStatuses = [
12+
"PROCESSING",
13+
"PENDING",
14+
"COMPLETED",
15+
"PARTIAL_FAILED",
16+
"ABORTED",
17+
] as const satisfies Readonly<Array<BatchTaskRunStatus>>;
1018

1119
const descriptions: Record<BatchTaskRunStatus, string> = {
20+
PROCESSING: "The batch is being processed and runs are being created.",
1221
PENDING: "The batch has child runs that have not yet completed.",
1322
COMPLETED: "All the batch child runs have finished.",
14-
ABORTED: "The batch was aborted because some child tasks could not be triggered.",
23+
PARTIAL_FAILED: "Some runs failed to be created. Successfully created runs are still executing.",
24+
ABORTED: "The batch was aborted because child tasks could not be triggered.",
1525
};
1626

1727
export function descriptionForBatchStatus(status: BatchTaskRunStatus): string {
@@ -47,10 +57,14 @@ export function BatchStatusIcon({
4757
className: string;
4858
}) {
4959
switch (status) {
60+
case "PROCESSING":
61+
return <Spinner className={cn(batchStatusColor(status), className)} />;
5062
case "PENDING":
5163
return <Spinner className={cn(batchStatusColor(status), className)} />;
5264
case "COMPLETED":
5365
return <CheckCircleIcon className={cn(batchStatusColor(status), className)} />;
66+
case "PARTIAL_FAILED":
67+
return <ExclamationTriangleIcon className={cn(batchStatusColor(status), className)} />;
5468
case "ABORTED":
5569
return <XCircleIcon className={cn(batchStatusColor(status), className)} />;
5670
default: {
@@ -61,10 +75,14 @@ export function BatchStatusIcon({
6175

6276
export function batchStatusColor(status: BatchTaskRunStatus): string {
6377
switch (status) {
78+
case "PROCESSING":
79+
return "text-blue-500";
6480
case "PENDING":
6581
return "text-pending";
6682
case "COMPLETED":
6783
return "text-success";
84+
case "PARTIAL_FAILED":
85+
return "text-warning";
6886
case "ABORTED":
6987
return "text-error";
7088
default: {
@@ -75,10 +93,14 @@ export function batchStatusColor(status: BatchTaskRunStatus): string {
7593

7694
export function batchStatusTitle(status: BatchTaskRunStatus): string {
7795
switch (status) {
96+
case "PROCESSING":
97+
return "Processing";
7898
case "PENDING":
7999
return "In progress";
80100
case "COMPLETED":
81101
return "Completed";
102+
case "PARTIAL_FAILED":
103+
return "Partial failure";
82104
case "ABORTED":
83105
return "Aborted";
84106
default: {

apps/webapp/app/env.server.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,12 @@ const EnvironmentSchema = z
931931
.default(process.env.REDIS_TLS_DISABLED ?? "false"),
932932
BATCH_TRIGGER_WORKER_REDIS_CLUSTER_MODE_ENABLED: z.string().default("0"),
933933

934+
// BatchQueue DRR settings (Run Engine v2)
935+
BATCH_QUEUE_DRR_QUANTUM: z.coerce.number().int().optional(),
936+
BATCH_QUEUE_MAX_DEFICIT: z.coerce.number().int().optional(),
937+
BATCH_QUEUE_CONSUMER_COUNT: z.coerce.number().int().optional(),
938+
BATCH_QUEUE_CONSUMER_INTERVAL_MS: z.coerce.number().int().optional(),
939+
934940
ADMIN_WORKER_ENABLED: z.string().default(process.env.WORKER_ENABLED ?? "true"),
935941
ADMIN_WORKER_CONCURRENCY_WORKERS: z.coerce.number().int().default(2),
936942
ADMIN_WORKER_CONCURRENCY_TASKS_PER_WORKER: z.coerce.number().int().default(10),

apps/webapp/app/presenters/v3/BatchListPresenter.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ WHERE
195195
throw new Error(`Environment not found for Batch ${batch.id}`);
196196
}
197197

198-
const hasFinished = batch.status !== "PENDING";
198+
const hasFinished = batch.status !== "PENDING" && batch.status !== "PROCESSING";
199199

200200
return {
201201
id: batch.id,

apps/webapp/app/routes/api.v1.batches.$batchId.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export const loader = createLoaderApiRoute(
1818
friendlyId: params.batchId,
1919
runtimeEnvironmentId: auth.environment.id,
2020
},
21+
include: {
22+
errors: true,
23+
},
2124
});
2225
},
2326
authorization: {
@@ -35,6 +38,18 @@ export const loader = createLoaderApiRoute(
3538
updatedAt: batch.updatedAt,
3639
runCount: batch.runCount,
3740
runs: batch.runIds,
41+
// Include error details for PARTIAL_FAILED batches
42+
successfulRunCount: batch.successfulRunCount ?? undefined,
43+
failedRunCount: batch.failedRunCount ?? undefined,
44+
errors:
45+
batch.errors.length > 0
46+
? batch.errors.map((err) => ({
47+
index: err.index,
48+
taskIdentifier: err.taskIdentifier,
49+
error: err.error,
50+
errorCode: err.errorCode ?? undefined,
51+
}))
52+
: undefined,
3853
});
3954
}
4055
);

apps/webapp/app/routes/api.v2.batches.$batchId.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export const loader = createLoaderApiRoute(
1818
friendlyId: params.batchId,
1919
runtimeEnvironmentId: auth.environment.id,
2020
},
21+
include: {
22+
errors: true,
23+
},
2124
});
2225
},
2326
authorization: {
@@ -35,6 +38,18 @@ export const loader = createLoaderApiRoute(
3538
updatedAt: batch.updatedAt,
3639
runCount: batch.runCount,
3740
runs: batch.runIds,
41+
// Include error details for PARTIAL_FAILED batches
42+
successfulRunCount: batch.successfulRunCount ?? undefined,
43+
failedRunCount: batch.failedRunCount ?? undefined,
44+
errors:
45+
batch.errors.length > 0
46+
? batch.errors.map((err) => ({
47+
index: err.index,
48+
taskIdentifier: err.taskIdentifier,
49+
error: err.error,
50+
errorCode: err.errorCode ?? undefined,
51+
}))
52+
: undefined,
3853
});
3954
}
4055
);

apps/webapp/app/routes/api.v2.tasks.batch.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { prisma } from "~/db.server";
88
import { env } from "~/env.server";
99
import { RunEngineBatchTriggerService } from "~/runEngine/services/batchTrigger.server";
10+
import { RunEngineBatchTriggerServiceV2 } from "~/runEngine/services/batchTriggerV2.server";
1011
import { AuthenticatedEnvironment, getOneTimeUseToken } from "~/services/apiAuth.server";
1112
import { logger } from "~/services/logger.server";
1213
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
@@ -20,6 +21,7 @@ import { OutOfEntitlementError } from "~/v3/services/triggerTask.server";
2021
import { HeadersSchema } from "./api.v1.tasks.$taskId.trigger";
2122
import { determineRealtimeStreamsVersion } from "~/services/realtime/v1StreamsGlobal.server";
2223
import { extractJwtSigningSecretKey } from "~/services/realtime/jwtAuth.server";
24+
import { engine } from "~/v3/runEngine.server";
2325

2426
const { action, loader } = createActionApiRoute(
2527
{
@@ -110,6 +112,68 @@ const { action, loader } = createActionApiRoute(
110112
? { traceparent, tracestate }
111113
: { external: { traceparent, tracestate } };
112114

115+
// Use the new V2 service with DRR-based BatchQueue if enabled, otherwise fall back to V1
116+
const useBatchQueueV2 = engine.isBatchQueueEnabled();
117+
118+
logger.debug("Batch trigger service selection", {
119+
useBatchQueueV2,
120+
batchProcessingStrategy,
121+
});
122+
123+
if (useBatchQueueV2) {
124+
// V2: Uses Redis-based BatchQueue with DRR fair scheduling
125+
const serviceV2 = new RunEngineBatchTriggerServiceV2();
126+
127+
serviceV2.onBatchTaskRunCreated.attachOnce(async (batch) => {
128+
await saveRequestIdempotency(requestIdempotencyKey, "batch-trigger", batch.id);
129+
});
130+
131+
try {
132+
const batch = await serviceV2.call(authentication.environment, body, {
133+
triggerVersion: triggerVersion ?? undefined,
134+
traceContext,
135+
spanParentAsLink: spanParentAsLink === 1,
136+
oneTimeUseToken,
137+
realtimeStreamsVersion: determineRealtimeStreamsVersion(
138+
realtimeStreamsVersion ?? undefined
139+
),
140+
idempotencyKey: requestIdempotencyKey ?? undefined,
141+
});
142+
143+
const $responseHeaders = await responseHeaders(
144+
batch,
145+
authentication.environment,
146+
triggerClient
147+
);
148+
149+
return json(batch, {
150+
status: 202,
151+
headers: $responseHeaders,
152+
});
153+
} catch (error) {
154+
logger.error("Batch trigger error (V2)", {
155+
error: {
156+
message: (error as Error).message,
157+
stack: (error as Error).stack,
158+
},
159+
});
160+
161+
if (error instanceof ServiceValidationError) {
162+
return json({ error: error.message }, { status: 422 });
163+
} else if (error instanceof OutOfEntitlementError) {
164+
return json({ error: error.message }, { status: 422 });
165+
} else if (error instanceof Error) {
166+
return json(
167+
{ error: error.message },
168+
{ status: 500, headers: { "x-should-retry": "false" } }
169+
);
170+
}
171+
172+
return json({ error: "Something went wrong" }, { status: 500 });
173+
}
174+
}
175+
176+
// V1: Legacy batch trigger service
113177
const service = new RunEngineBatchTriggerService(batchProcessingStrategy ?? undefined);
114178

115179
service.onBatchTaskRunCreated.attachOnce(async (batch) => {

0 commit comments

Comments
 (0)