Files
2026-07-13 13:32:57 +08:00

201 lines
5.6 KiB
TypeScript

import { tryCatch } from "@trigger.dev/core/utils";
import type { TaskRunInternalError } from "@trigger.dev/core/v3";
import { sanitizeError, TaskRunErrorCodes } from "@trigger.dev/core/v3";
import type { TaskRun, TaskRunAttempt } from "@trigger.dev/database";
import type { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
import { FailedTaskRunRetryHelper } from "../failedTaskRun.server";
import { CRASHABLE_ATTEMPT_STATUSES, isCrashableRunStatus } from "../taskStatus";
import { BaseService } from "./baseService.server";
import { FinalizeTaskRunService } from "./finalizeTaskRun.server";
import { getEventRepositoryForStore } from "../eventRepository/index.server";
export type CrashTaskRunServiceOptions = {
reason?: string;
exitCode?: number;
logs?: string;
crashAttempts?: boolean;
crashedAt?: Date;
overrideCompletion?: boolean;
errorCode?: TaskRunInternalError["code"];
};
export class CrashTaskRunService extends BaseService {
public async call(runId: string, options?: CrashTaskRunServiceOptions) {
const opts = {
reason: "Worker crashed",
crashAttempts: true,
crashedAt: new Date(),
...options,
};
logger.debug("CrashTaskRunService.call", { runId, opts });
if (options?.overrideCompletion) {
logger.error("CrashTaskRunService.call: overrideCompletion is deprecated", { runId });
return;
}
const taskRun = await this.runStore.findRun({ id: runId }, this._prisma);
if (!taskRun) {
logger.error("[CrashTaskRunService] Task run not found", { runId });
return;
}
// Make sure the task run is in a crashable state
if (!opts.overrideCompletion && !isCrashableRunStatus(taskRun.status)) {
logger.error("[CrashTaskRunService] Task run is not in a crashable state", {
runId,
status: taskRun.status,
});
return;
}
logger.debug("[CrashTaskRunService] Completing attempt", { runId, options });
const retryHelper = new FailedTaskRunRetryHelper(this._prisma);
const retryResult = await retryHelper.call({
runId,
completion: {
ok: false,
id: runId,
error: {
type: "INTERNAL_ERROR",
code: opts.errorCode ?? TaskRunErrorCodes.TASK_RUN_CRASHED,
message: opts.reason,
stackTrace: opts.logs,
},
},
isCrash: true,
});
logger.debug("[CrashTaskRunService] Completion result", { runId, retryResult });
if (retryResult === "RETRIED") {
logger.debug("[CrashTaskRunService] Retried task run", { runId });
return;
}
if (!opts.overrideCompletion) {
return;
}
logger.debug("[CrashTaskRunService] Overriding completion", { runId, options });
const finalizeService = new FinalizeTaskRunService();
const crashedTaskRun = await finalizeService.call({
id: taskRun.id,
status: "CRASHED",
completedAt: new Date(),
include: {
attempts: {
where: {
status: {
in: CRASHABLE_ATTEMPT_STATUSES,
},
},
include: {
backgroundWorker: true,
runtimeEnvironment: true,
},
},
dependency: true,
runtimeEnvironment: {
include: {
organization: true,
project: true,
},
},
},
attemptStatus: "FAILED",
error: {
type: "INTERNAL_ERROR",
code: opts.errorCode ?? TaskRunErrorCodes.TASK_RUN_CRASHED,
message: opts.reason,
stackTrace: opts.logs,
},
});
const eventRepository = await getEventRepositoryForStore(
crashedTaskRun.taskEventStore,
crashedTaskRun.runtimeEnvironment.organizationId
);
const [createAttemptFailedEventError] = await tryCatch(
eventRepository.completeFailedRunEvent({
run: crashedTaskRun,
endTime: opts.crashedAt,
exception: {
type: opts.errorCode ?? TaskRunErrorCodes.TASK_RUN_CRASHED,
message: opts.reason,
stacktrace: opts.logs,
},
})
);
if (createAttemptFailedEventError) {
logger.error("[CrashTaskRunService] Failed to complete failed run event", {
error: createAttemptFailedEventError,
runId: crashedTaskRun.id,
});
}
if (!opts.crashAttempts) {
return;
}
// Cancel any in progress attempts
for (const attempt of crashedTaskRun.attempts) {
await this.#failAttempt(
attempt,
crashedTaskRun,
new Date(),
crashedTaskRun.runtimeEnvironment,
{
reason: opts.reason,
logs: opts.logs,
code: opts.errorCode,
}
);
}
}
async #failAttempt(
attempt: TaskRunAttempt,
run: TaskRun,
failedAt: Date,
environment: AuthenticatedEnvironment,
error: {
reason: string;
logs?: string;
code?: TaskRunInternalError["code"];
}
) {
return await this.traceWithEnv(
"[CrashTaskRunService] failAttempt()",
environment,
async (span) => {
span.setAttribute("taskRunId", run.id);
span.setAttribute("attemptId", attempt.id);
await this._prisma.taskRunAttempt.update({
where: {
id: attempt.id,
},
data: {
status: "FAILED",
completedAt: failedAt,
error: sanitizeError({
type: "INTERNAL_ERROR",
code: error.code ?? TaskRunErrorCodes.TASK_RUN_CRASHED,
message: error.reason,
stackTrace: error.logs,
}),
},
});
}
);
}
}