Skip to content

Commit 0587b5a

Browse files
committed
handle reserve concurrency with recursive deadlocks
1 parent 7061d3a commit 0587b5a

File tree

19 files changed

+974
-368
lines changed

19 files changed

+974
-368
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import { CheckCircleIcon } from "@heroicons/react/20/solid";
1+
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/20/solid";
22
import { BatchTaskRunStatus } from "@trigger.dev/database";
33
import assertNever from "assert-never";
44
import { Spinner } from "~/components/primitives/Spinner";
55
import { cn } from "~/utils/cn";
66

7-
export const allBatchStatuses = ["PENDING", "COMPLETED"] as const satisfies Readonly<
7+
export const allBatchStatuses = ["PENDING", "COMPLETED", "ABORTED"] as const satisfies Readonly<
88
Array<BatchTaskRunStatus>
99
>;
1010

1111
const descriptions: Record<BatchTaskRunStatus, string> = {
1212
PENDING: "The batch has child runs that have not yet completed.",
1313
COMPLETED: "All the batch child runs have finished.",
14+
ABORTED: "The batch was aborted because some child tasks could not be triggered.",
1415
};
1516

1617
export function descriptionForBatchStatus(status: BatchTaskRunStatus): string {
@@ -50,6 +51,8 @@ export function BatchStatusIcon({
5051
return <Spinner className={cn(batchStatusColor(status), className)} />;
5152
case "COMPLETED":
5253
return <CheckCircleIcon className={cn(batchStatusColor(status), className)} />;
54+
case "ABORTED":
55+
return <XCircleIcon className={cn(batchStatusColor(status), className)} />;
5356
default: {
5457
assertNever(status);
5558
}
@@ -62,6 +65,8 @@ export function batchStatusColor(status: BatchTaskRunStatus): string {
6265
return "text-pending";
6366
case "COMPLETED":
6467
return "text-success";
68+
case "ABORTED":
69+
return "text-error";
6570
default: {
6671
assertNever(status);
6772
}
@@ -74,6 +79,8 @@ export function batchStatusTitle(status: BatchTaskRunStatus): string {
7479
return "In progress";
7580
case "COMPLETED":
7681
return "Completed";
82+
case "ABORTED":
83+
return "Aborted";
7784
default: {
7885
assertNever(status);
7986
}

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

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

193-
const hasFinished = batch.status === "COMPLETED";
193+
const hasFinished = batch.status !== "PENDING";
194194

195195
return {
196196
id: batch.id,

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

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ export class SpanPresenter extends BasePresenter {
118118
metadata: true,
119119
metadataType: true,
120120
maxAttempts: true,
121+
output: true,
122+
outputType: true,
123+
error: true,
121124
project: {
122125
include: {
123126
organization: true,
@@ -162,31 +165,13 @@ export class SpanPresenter extends BasePresenter {
162165

163166
const isFinished = isFinalRunStatus(run.status);
164167

165-
const finishedAttempt = isFinished
166-
? await this._replica.taskRunAttempt.findFirst({
167-
select: {
168-
output: true,
169-
outputType: true,
170-
error: true,
171-
},
172-
where: {
173-
status: { in: FINAL_ATTEMPT_STATUSES },
174-
taskRunId: run.id,
175-
},
176-
orderBy: {
177-
createdAt: "desc",
178-
},
179-
})
180-
: null;
181-
182-
const output =
183-
finishedAttempt === null
184-
? undefined
185-
: finishedAttempt.outputType === "application/store"
186-
? `/resources/packets/${run.runtimeEnvironment.id}/${finishedAttempt.output}`
187-
: typeof finishedAttempt.output !== "undefined" && finishedAttempt.output !== null
188-
? await prettyPrintPacket(finishedAttempt.output, finishedAttempt.outputType ?? undefined)
189-
: undefined;
168+
const output = !isFinished
169+
? undefined
170+
: run.outputType === "application/store"
171+
? `/resources/packets/${run.runtimeEnvironment.id}/${run.output}`
172+
: typeof run.output !== "undefined" && run.output !== null
173+
? await prettyPrintPacket(run.output, run.outputType ?? undefined)
174+
: undefined;
190175

191176
const payload =
192177
run.payloadType === "application/store"
@@ -196,14 +181,14 @@ export class SpanPresenter extends BasePresenter {
196181
: undefined;
197182

198183
let error: TaskRunError | undefined = undefined;
199-
if (finishedAttempt?.error) {
200-
const result = TaskRunError.safeParse(finishedAttempt.error);
184+
if (run?.error) {
185+
const result = TaskRunError.safeParse(run.error);
201186
if (result.success) {
202187
error = result.data;
203188
} else {
204189
error = {
205190
type: "CUSTOM_ERROR",
206-
raw: JSON.stringify(finishedAttempt.error),
191+
raw: JSON.stringify(run.error),
207192
};
208193
}
209194
}
@@ -300,7 +285,7 @@ export class SpanPresenter extends BasePresenter {
300285
payload,
301286
payloadType: run.payloadType,
302287
output,
303-
outputType: finishedAttempt?.outputType ?? "application/json",
288+
outputType: run?.outputType ?? "application/json",
304289
error,
305290
relationships: {
306291
root: run.rootTaskRun

apps/webapp/app/v3/eventRepository.server.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ export type EventBuilder = {
9595
traceId: string;
9696
spanId: string;
9797
setAttribute: SetAttribute<TraceAttributes>;
98+
stop: () => void;
99+
failWithError: (error: TaskRunError) => void;
98100
};
99101

100102
export type EventRepoConfig = {
@@ -916,6 +918,9 @@ export class EventRepository {
916918
]
917919
: [];
918920

921+
let isStopped = false;
922+
let failedWithError: TaskRunError | undefined;
923+
919924
const eventBuilder = {
920925
traceId,
921926
spanId,
@@ -933,10 +938,20 @@ export class EventRepository {
933938
}
934939
}
935940
},
941+
stop: () => {
942+
isStopped = true;
943+
},
944+
failWithError: (error: TaskRunError) => {
945+
failedWithError = error;
946+
},
936947
};
937948

938949
const result = await callback(eventBuilder, traceContext, propagatedContext?.traceparent);
939950

951+
if (isStopped) {
952+
return result;
953+
}
954+
940955
const duration = process.hrtime.bigint() - start;
941956

942957
const metadata = {
@@ -970,13 +985,14 @@ export class EventRepository {
970985
parentId,
971986
tracestate,
972987
duration: options.incomplete ? 0 : duration,
973-
isPartial: options.incomplete,
988+
isPartial: failedWithError ? false : options.incomplete,
989+
isError: !!failedWithError,
974990
message: message,
975991
serviceName: "api server",
976992
serviceNamespace: "trigger.dev",
977993
level: "TRACE",
978994
kind: options.kind,
979-
status: "OK",
995+
status: failedWithError ? "ERROR" : "OK",
980996
startTime,
981997
environmentId: options.environment.id,
982998
environmentType: options.environment.type,
@@ -1004,6 +1020,17 @@ export class EventRepository {
10041020
payload: options.attributes.payload,
10051021
payloadType: options.attributes.payloadType,
10061022
idempotencyKey: options.attributes.idempotencyKey,
1023+
events: failedWithError
1024+
? [
1025+
{
1026+
name: "exception",
1027+
time: new Date(),
1028+
properties: {
1029+
exception: createExceptionPropertiesFromError(failedWithError),
1030+
},
1031+
},
1032+
]
1033+
: undefined,
10071034
};
10081035

10091036
if (options.immediate) {

0 commit comments

Comments
 (0)