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

297 lines
7.4 KiB
TypeScript

import type { Prisma } from "@trigger.dev/database";
import { logger } from "~/services/logger.server";
import { isFinalAttemptStatus, isFinalRunStatus } from "../taskStatus";
import { BaseService } from "./baseService.server";
import { ResumeBatchRunService } from "./resumeBatchRun.server";
import { ResumeTaskDependencyService } from "./resumeTaskDependency.server";
import { $transaction } from "~/db.server";
import { completeBatchTaskRunItemV3 } from "./batchTriggerV3.server";
type Output =
| {
success: true;
action:
| "resume-scheduled"
| "batch-resume-scheduled"
| "no-dependencies"
| "not-finished"
| "dev";
}
| {
success: false;
error: string;
};
const taskRunDependencySelect = {
select: {
id: true,
taskRunId: true,
taskRun: {
select: {
id: true,
status: true,
friendlyId: true,
runtimeEnvironment: {
select: {
type: true,
},
},
},
},
dependentAttempt: {
select: {
id: true,
},
},
dependentBatchRun: {
select: {
id: true,
batchVersion: true,
},
},
},
} as const;
type Dependency = Prisma.TaskRunDependencyGetPayload<typeof taskRunDependencySelect>;
/** This will resume a dependent (parent) run if there is one and it makes sense. */
export class ResumeDependentParentsService extends BaseService {
public async call({ id }: { id: string }): Promise<Output> {
try {
const dependency = await this._prisma.taskRunDependency.findFirst({
...taskRunDependencySelect,
where: {
taskRunId: id,
},
});
logger.log("ResumeDependentParentsService: tried to find dependency", {
runId: id,
dependency: dependency,
});
if (!dependency) {
logger.log("ResumeDependentParentsService: dependency not found", {
runId: id,
});
//no dependency, that's fine most runs won't have one.
return {
success: true,
action: "no-dependencies",
};
}
if (dependency.taskRun.runtimeEnvironment.type === "DEVELOPMENT") {
return {
success: true,
action: "dev",
};
}
if (!isFinalRunStatus(dependency.taskRun.status)) {
logger.debug(
"ResumeDependentParentsService: run not finished yet, can't resume parent yet",
{
runId: id,
dependency,
}
);
// the child run isn't finished yet, so we can't resume the parent yet.
return {
success: true,
action: "not-finished",
};
}
if (dependency.dependentAttempt) {
return this.#singleRunDependency(dependency);
} else if (dependency.dependentBatchRun) {
return this.#batchRunDependency(dependency);
} else {
logger.error("ResumeDependentParentsService: dependency has no dependencies", {
runId: id,
dependency,
});
return {
success: false,
error: `Dependency has no dependencies (single or batch)`,
};
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : JSON.stringify(error),
};
}
}
async #singleRunDependency(dependency: Dependency): Promise<Output> {
logger.debug(
`ResumeDependentParentsService.singleRunDependency(): Resuming dependent parent for run`,
{
dependency,
}
);
const lastAttempt = await this._prisma.taskRunAttempt.findFirst({
select: {
id: true,
status: true,
},
where: {
taskRunId: dependency.taskRunId,
},
orderBy: {
id: "desc",
},
});
if (!lastAttempt) {
logger.error(
"ResumeDependentParentsService.singleRunDependency(): dependency child attempt not found",
{
dependency,
}
);
return {
success: false,
error: `Dependency child attempt not found for run ${dependency.taskRunId}`,
};
}
if (!isFinalAttemptStatus(lastAttempt.status)) {
//We still want to continue if this happens because the run is final but log it
logger.error(
"ResumeDependentParentsService.singleRunDependency(): dependency child attempt not final, but the run is.",
{
dependency,
lastAttempt,
}
);
return {
success: false,
error: `Dependency child attempt not final, but the run is`,
};
}
//resume the dependent task
await ResumeTaskDependencyService.enqueue(dependency.id, lastAttempt.id);
return {
success: true,
action: "resume-scheduled",
};
}
async #batchRunDependency(dependency: Dependency): Promise<Output> {
logger.debug(
`ResumeDependentParentsService.batchRunDependency(): Resuming dependent batch for run`,
{
dependency,
}
);
if (!dependency.dependentBatchRun) {
logger.error(
"ResumeDependentParentsService.batchRunDependency(): dependency has no dependent batch",
{
dependency,
}
);
return {
success: false,
error: `Dependency has no dependent batch`,
};
}
const lastAttempt = await this._prisma.taskRunAttempt.findFirst({
select: {
id: true,
status: true,
},
where: {
taskRunId: dependency.taskRunId,
},
orderBy: {
id: "desc",
},
});
if (!lastAttempt) {
logger.error(
"ResumeDependentParentsService.singleRunDependency(): dependency child attempt not found",
{
dependency,
}
);
return {
success: false,
error: `Dependency child attempt not found for run ${dependency.taskRunId}`,
};
}
logger.log(
"ResumeDependentParentsService.batchRunDependency(): Setting the batchTaskRunItem to COMPLETED",
{
dependency,
lastAttempt,
}
);
if (dependency.dependentBatchRun!.batchVersion === "v3") {
const batchTaskRunItem = await this._prisma.batchTaskRunItem.findFirst({
where: {
batchTaskRunId: dependency.dependentBatchRun!.id,
taskRunId: dependency.taskRunId,
},
});
if (batchTaskRunItem) {
await completeBatchTaskRunItemV3(
batchTaskRunItem.id,
batchTaskRunItem.batchTaskRunId,
this._prisma,
true,
lastAttempt.id
);
} else {
logger.debug(
"ResumeDependentParentsService.batchRunDependency() v3: batchTaskRunItem not found",
{
dependency,
lastAttempt,
}
);
}
} else {
await $transaction(this._prisma, async (tx) => {
await tx.batchTaskRunItem.update({
where: {
batchTaskRunId_taskRunId: {
batchTaskRunId: dependency.dependentBatchRun!.id,
taskRunId: dependency.taskRunId,
},
},
data: {
status: "COMPLETED",
taskRunAttemptId: lastAttempt.id,
},
});
await ResumeBatchRunService.enqueue(dependency.dependentBatchRun!.id, false, tx);
});
}
return {
success: true,
action: "batch-resume-scheduled",
};
}
}