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

176 lines
6.0 KiB
TypeScript

import type { TaskRunDependency } from "@trigger.dev/database";
import { logger } from "~/services/logger.server";
import { marqs } from "~/v3/marqs/index.server";
import { commonWorker } from "../commonWorker.server";
import { BaseService } from "./baseService.server";
import { isV3Disabled } from "../engineDeprecation.server";
export class ResumeTaskDependencyService extends BaseService {
public async call(dependencyId: string, sourceTaskAttemptId: string) {
const dependency = await this._prisma.taskRunDependency.findFirst({
where: { id: dependencyId },
include: {
taskRun: {
include: {
runtimeEnvironment: {
include: {
project: true,
organization: true,
},
},
},
},
dependentAttempt: {
include: {
taskRun: true,
},
},
},
});
// Dependencies with a dependentBatchRun are handled already by the ResumeBatchRunService
if (!dependency || !dependency.dependentAttempt) {
return;
}
// v3 (engine V1) shutdown: don't resume dependencies for abandoned V1 runs. v4 is unaffected.
if (isV3Disabled() && dependency.taskRun.engine === "V1") {
logger.debug("[ResumeTaskDependencyService] Skipping resume for shut-down v3 run", {
dependencyId,
});
return;
}
if (dependency.taskRun.runtimeEnvironment.type === "DEVELOPMENT") {
return;
}
const dependentRun = dependency.dependentAttempt.taskRun;
if (dependency.dependentAttempt.status === "PAUSED" && dependency.checkpointEventId) {
logger.debug(
"Task dependency resume: Attempt is paused and there's a checkpoint. Enqueuing resume with checkpoint.",
{
attemptId: dependency.id,
dependentAttempt: dependency.dependentAttempt,
checkpointEventId: dependency.checkpointEventId,
hasCheckpointEvent: !!dependency.checkpointEventId,
runId: dependentRun.id,
}
);
const wasUpdated = await this.#setDependencyToResumedOnce(dependency);
if (!wasUpdated) {
logger.debug("Task dependency resume: Attempt with checkpoint was already resumed", {
attemptId: dependency.id,
dependentAttempt: dependency.dependentAttempt,
checkpointEventId: dependency.checkpointEventId,
hasCheckpointEvent: !!dependency.checkpointEventId,
runId: dependentRun.id,
});
return;
}
// TODO: use the new priority queue thingie
await marqs?.enqueueMessage(
dependency.taskRun.runtimeEnvironment,
dependentRun.queue,
dependentRun.id,
{
type: "RESUME",
completedAttemptIds: [sourceTaskAttemptId],
resumableAttemptId: dependency.dependentAttempt.id,
checkpointEventId: dependency.checkpointEventId,
taskIdentifier: dependency.taskRun.taskIdentifier,
projectId: dependency.taskRun.runtimeEnvironment.projectId,
environmentId: dependency.taskRun.runtimeEnvironment.id,
environmentType: dependency.taskRun.runtimeEnvironment.type,
},
dependentRun.concurrencyKey ?? undefined,
dependentRun.queueTimestamp ?? dependentRun.createdAt,
undefined,
"resume"
);
} else {
logger.debug("Task dependency resume: Attempt is not paused or there's no checkpoint event", {
attemptId: dependency.id,
dependentAttempt: dependency.dependentAttempt,
checkpointEventId: dependency.checkpointEventId,
hasCheckpointEvent: !!dependency.checkpointEventId,
runId: dependentRun.id,
});
if (dependency.dependentAttempt.status === "PAUSED" && !dependency.checkpointEventId) {
// In case of race conditions the status can be PAUSED without a checkpoint event
// When the checkpoint is created, it will continue the run
logger.error("Task dependency resume: Attempt is paused but there's no checkpoint event", {
attemptId: dependency.id,
dependentAttemptId: dependency.dependentAttempt.id,
});
return;
}
const wasUpdated = await this.#setDependencyToResumedOnce(dependency);
if (!wasUpdated) {
logger.debug("Task dependency resume: Attempt without checkpoint was already resumed", {
attemptId: dependency.id,
dependentAttempt: dependency.dependentAttempt,
checkpointEventId: dependency.checkpointEventId,
hasCheckpointEvent: !!dependency.checkpointEventId,
runId: dependentRun.id,
});
return;
}
await marqs.requeueMessage(
dependentRun.id,
{
type: "RESUME",
completedAttemptIds: [sourceTaskAttemptId],
resumableAttemptId: dependency.dependentAttempt.id,
checkpointEventId: dependency.checkpointEventId ?? undefined,
taskIdentifier: dependency.taskRun.taskIdentifier,
projectId: dependency.taskRun.runtimeEnvironment.projectId,
environmentId: dependency.taskRun.runtimeEnvironment.id,
environmentType: dependency.taskRun.runtimeEnvironment.type,
},
(dependentRun.queueTimestamp ?? dependentRun.createdAt).getTime(),
"resume"
);
}
}
async #setDependencyToResumedOnce(dependency: TaskRunDependency) {
const result = await this._prisma.taskRunDependency.updateMany({
where: {
id: dependency.id,
resumedAt: null,
},
data: {
resumedAt: new Date(),
},
});
// Check if any records were updated
if (result.count > 0) {
// The status was changed, so we return true
return true;
} else {
return false;
}
}
static async enqueue(dependencyId: string, sourceTaskAttemptId: string, runAt?: Date) {
return await commonWorker.enqueue({
job: "v3.resumeTaskDependency",
payload: {
dependencyId,
sourceTaskAttemptId,
},
availableAt: runAt,
});
}
}