import type { Context, Span, SpanOptions } from "@opentelemetry/api"; import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, trace } from "@opentelemetry/api"; import type { AckCallbackResult, MachinePreset, V3ProdTaskRunExecution, V3ProdTaskRunExecutionPayload, TaskRunError, TaskRunExecution, TaskRunExecutionLazyAttemptPayload, TaskRunExecutionResult, TaskRunFailedExecutionResult, TaskRunSuccessfulExecutionResult, serverWebsocketMessages, } from "@trigger.dev/core/v3"; import { TaskRunErrorCodes, parsePacket, SemanticInternalAttributes } from "@trigger.dev/core/v3"; import type { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler"; import type { BackgroundWorker, BackgroundWorkerTask, Prisma, TaskRunStatus, } from "@trigger.dev/database"; import { z } from "zod"; import { $replica, prisma } from "~/db.server"; import { env } from "~/env.server"; import { findEnvironmentById } from "~/models/runtimeEnvironment.server"; import { findQueueInEnvironment, sanitizeQueueName } from "~/models/taskQueue.server"; import { generateJWTTokenForEnvironment } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; import { singleton } from "~/utils/singleton"; import { marqs } from "~/v3/marqs/index.server"; import type { RuntimeEnvironmentForEnvRepo } from "../environmentVariables/environmentVariablesRepository.server"; import { RuntimeEnvironmentForEnvRepoPayload, resolveVariablesForEnvironment, } from "../environmentVariables/environmentVariablesRepository.server"; import type { EnvironmentVariable } from "../environmentVariables/repository"; import { FailedTaskRunService } from "../failedTaskRun.server"; import { generateFriendlyId } from "../friendlyIdentifiers"; import { socketIo } from "../handleSocketIo.server"; import { machinePresetFromConfig, machinePresetFromRun } from "../machinePresets.server"; import { findCurrentWorkerDeployment, getWorkerDeploymentFromWorker, getWorkerDeploymentFromWorkerTask, } from "../models/workerDeployment.server"; import { CrashTaskRunService } from "../services/crashTaskRun.server"; import { CreateTaskRunAttemptService } from "../services/createTaskRunAttempt.server"; import { RestoreCheckpointService } from "../services/restoreCheckpoint.server"; import { FINAL_ATTEMPT_STATUSES, FINAL_RUN_STATUSES, isFinalAttemptStatus, isFinalRunStatus, } from "../taskStatus"; import { tracer } from "../tracer.server"; import { getMaxDuration } from "../utils/maxDuration"; import type { MessagePayload } from "./types"; const WithTraceContext = z.object({ traceparent: z.string().optional(), tracestate: z.string().optional(), }); export const SharedQueueExecuteMessageBody = WithTraceContext.extend({ type: z.literal("EXECUTE"), taskIdentifier: z.string(), checkpointEventId: z.string().optional(), retryCheckpointsDisabled: z.boolean().optional(), }); export type SharedQueueExecuteMessageBody = z.infer; export const SharedQueueResumeMessageBody = WithTraceContext.extend({ type: z.literal("RESUME"), completedAttemptIds: z.string().array(), resumableAttemptId: z.string(), checkpointEventId: z.string().optional(), }); export type SharedQueueResumeMessageBody = z.infer; export const SharedQueueResumeAfterDurationMessageBody = WithTraceContext.extend({ type: z.literal("RESUME_AFTER_DURATION"), resumableAttemptId: z.string(), checkpointEventId: z.string(), }); export type SharedQueueResumeAfterDurationMessageBody = z.infer< typeof SharedQueueResumeAfterDurationMessageBody >; export const SharedQueueFailMessageBody = WithTraceContext.extend({ type: z.literal("FAIL"), reason: z.string(), }); export type SharedQueueFailMessageBody = z.infer; export const SharedQueueMessageBody = z.discriminatedUnion("type", [ SharedQueueExecuteMessageBody, SharedQueueResumeMessageBody, SharedQueueResumeAfterDurationMessageBody, SharedQueueFailMessageBody, ]); export type SharedQueueMessageBody = z.infer; type BackgroundWorkerWithTasks = BackgroundWorker & { tasks: BackgroundWorkerTask[] }; export type SharedQueueConsumerOptions = { maximumItemsPerTrace?: number; traceTimeoutSeconds?: number; nextTickInterval?: number; interval?: number; }; type HandleMessageAction = "ack_and_do_more_work" | "nack" | "nack_and_do_more_work" | "noop"; type DoWorkInternalResult = { reason: string; outcome: "execution" | "retry_with_nack" | "fail_with_ack" | "noop"; attrs?: Record; error?: Error | string; interval?: number; action?: HandleMessageAction; }; type HandleMessageResult = { action: HandleMessageAction; interval?: number; retryInMs?: number; reason?: string; attrs?: Record; error?: Error | string; }; export class SharedQueueConsumer { private _backgroundWorkers: Map = new Map(); private _deprecatedWorkers: Map = new Map(); private _enabled = false; private _options: Required; private _perTraceCountdown: number | undefined; private _traceStartedAt: Date | undefined; private _currentSpanContext: Context | undefined; private _reasonStats: Record = {}; private _actionStats: Record = {}; private _outcomeStats: Record = {}; private _currentSpan: Span | undefined; private _endSpanInNextIteration = false; private _tasks = sharedQueueTasks; private _id: string; private _connectedAt: Date; private _iterationsCount = 0; private _totalIterationsCount = 0; private _runningDurationInMs = 0; private _currentMessage: MessagePayload | undefined; private _currentMessageData: SharedQueueMessageBody | undefined; private _stopWorkerQueueConsumer?: () => void; constructor( private _providerSender: ZodMessageSender, options: SharedQueueConsumerOptions = {} ) { this._options = { maximumItemsPerTrace: options.maximumItemsPerTrace ?? 500, traceTimeoutSeconds: options.traceTimeoutSeconds ?? 1, nextTickInterval: options.nextTickInterval ?? 1000, // 1 second interval: options.interval ?? 100, // 100ms }; this._id = generateFriendlyId("shared-queue", 6); this._connectedAt = new Date(); } // This method is called when a background worker is deprecated and will no longer be used unless a run is locked to it public async deprecateBackgroundWorker(id: string) { const backgroundWorker = this._backgroundWorkers.get(id); if (!backgroundWorker) { return; } this._deprecatedWorkers.set(id, backgroundWorker); this._backgroundWorkers.delete(id); } public async registerBackgroundWorker(id: string, envId?: string) { if (!envId) { logger.error("Environment ID is required for background worker registration", { backgroundWorkerId: id, }); return; } const backgroundWorker = await prisma.backgroundWorker.findFirst({ where: { friendlyId: id, runtimeEnvironmentId: envId, }, include: { tasks: true, }, }); if (!backgroundWorker) { return; } this._backgroundWorkers.set(backgroundWorker.id, backgroundWorker); logger.debug("Registered background worker", { backgroundWorker: backgroundWorker.id }); // Start reading from the queue if we haven't already this.#enable(); } public async start() { this.#enable(); } public async stop(reason: string = "Provider disconnected") { if (!this._enabled) { return; } console.log("❌ Stopping the SharedQueueConsumer"); this._stopWorkerQueueConsumer?.(); logger.debug("Stopping shared queue consumer"); this._enabled = false; if (this._currentSpan) { this._currentSpan.end(); } } #enable() { if (this._enabled) { return; } this._enabled = true; this._perTraceCountdown = this._options.maximumItemsPerTrace; this._traceStartedAt = new Date(); this._reasonStats = {}; this._actionStats = {}; this._outcomeStats = {}; this._stopWorkerQueueConsumer = marqs?.startSharedWorkerQueueConsumer(this._id); console.log("✅ Started the SharedQueueConsumer"); this.#doWork().finally(() => {}); } #endCurrentSpan() { if (this._currentSpan) { for (const [reason, count] of Object.entries(this._reasonStats)) { this._currentSpan.setAttribute(`reasons_${reason}`, count); } for (const [action, count] of Object.entries(this._actionStats)) { this._currentSpan.setAttribute(`actions_${action}`, count); } for (const [outcome, count] of Object.entries(this._outcomeStats)) { this._currentSpan.setAttribute(`outcomes_${outcome}`, count); } this._currentSpan.end(); } } async #doWork() { if (!this._enabled) { this.#endCurrentSpan(); return; } // Check if the trace has expired if ( this._perTraceCountdown === 0 || Date.now() - this._traceStartedAt!.getTime() > this._options.traceTimeoutSeconds * 1000 || this._currentSpanContext === undefined || this._endSpanInNextIteration ) { this.#endCurrentSpan(); const traceDurationInMs = this._traceStartedAt ? Date.now() - this._traceStartedAt.getTime() : undefined; const iterationsPerSecond = traceDurationInMs ? this._iterationsCount / (traceDurationInMs / 1000) : undefined; // Create a new trace this._currentSpan = tracer.startSpan( "SharedQueueConsumer.doWork()", { kind: SpanKind.CONSUMER, attributes: { id: this._id, iterations: this._iterationsCount, total_iterations: this._totalIterationsCount, options_maximumItemsPerTrace: this._options.maximumItemsPerTrace, options_nextTickInterval: this._options.nextTickInterval, options_interval: this._options.interval, connected_at: this._connectedAt.toISOString(), consumer_age_in_seconds: (Date.now() - this._connectedAt.getTime()) / 1000, do_work_internal_per_second: this._iterationsCount / (this._runningDurationInMs / 1000), running_duration_ms: this._runningDurationInMs, trace_timeout_in_seconds: this._options.traceTimeoutSeconds, trace_duration_ms: traceDurationInMs, iterations_per_second: iterationsPerSecond, iterations_per_minute: iterationsPerSecond ? iterationsPerSecond * 60 : undefined, }, }, ROOT_CONTEXT ); logger.debug("SharedQueueConsumer starting new trace", { reasonStats: this._reasonStats, actionStats: this._actionStats, outcomeStats: this._outcomeStats, iterationCount: this._iterationsCount, consumerId: this._id, }); // Get the span trace context this._currentSpanContext = trace.setSpan(ROOT_CONTEXT, this._currentSpan); this._perTraceCountdown = this._options.maximumItemsPerTrace; this._traceStartedAt = new Date(); this._reasonStats = {}; this._actionStats = {}; this._outcomeStats = {}; this._iterationsCount = 0; this._runningDurationInMs = 0; this._endSpanInNextIteration = false; } return context.with(this._currentSpanContext ?? ROOT_CONTEXT, async () => { await tracer.startActiveSpan("doWorkInternal()", async (span) => { let nextInterval = this._options.interval; span.setAttributes({ id: this._id, total_iterations: this._totalIterationsCount, iterations: this._iterationsCount, }); const startAt = performance.now(); try { const result = await this.#doWorkInternal(); if (result.reason !== "no_message_dequeued") { logger.debug("SharedQueueConsumer doWorkInternal result", { result }); } this._reasonStats[result.reason] = (this._reasonStats[result.reason] ?? 0) + 1; this._outcomeStats[result.outcome] = (this._outcomeStats[result.outcome] ?? 0) + 1; if (result.action) { this._actionStats[result.action] = (this._actionStats[result.action] ?? 0) + 1; } span.setAttribute("reason", result.reason); if (result.attrs) { for (const [key, value] of Object.entries(result.attrs)) { if (value) { span.setAttribute(key, value); } } } if (result.error) { span.recordException(result.error); span.setStatus({ code: SpanStatusCode.ERROR }); this._currentSpan?.recordException(result.error); this._currentSpan?.setStatus({ code: SpanStatusCode.ERROR }); this._endSpanInNextIteration = true; } if (typeof result.interval === "number") { nextInterval = Math.max(result.interval, 0); // Cannot be negative } span.setAttribute("nextInterval", nextInterval); } catch (error) { if (error instanceof Error) { this._currentSpan?.recordException(error); } else { this._currentSpan?.recordException(new Error(String(error))); } this._endSpanInNextIteration = true; } finally { this._runningDurationInMs = this._runningDurationInMs + (performance.now() - startAt); this._iterationsCount++; this._totalIterationsCount++; this._perTraceCountdown = this._perTraceCountdown! - 1; span.end(); setTimeout(() => { this.#doWork().finally(() => {}); }, nextInterval); } }); }); } async #doWorkInternal(): Promise { // Attempt to dequeue a message from the shared queue // If no message is available, reschedule the worker to run again in 1 second // If a message is available, find the BackgroundWorkerTask that matches the message's taskIdentifier // If no matching task is found, nack the message and reschedule the worker to run again in 1 second // If the matching task is found, create the task attempt and lock the task run, then send the task run to the client // Store the message as a processing message // If the websocket connection disconnects before the task run is completed, nack the message // When the task run completes, ack the message // Using a heartbeat mechanism, if the client keeps responding with a heartbeat, we'll keep the message processing and increase the visibility timeout. this._currentMessage = undefined; this._currentMessageData = undefined; const message = await marqs?.dequeueMessageFromSharedWorkerQueue(this._id); if (!message) { return { reason: "no_message_dequeued", outcome: "noop", interval: this._options.nextTickInterval, }; } const dequeuedAt = new Date(); logger.log("dequeueMessageInSharedQueue()", { queueMessage: message }); const messageBody = SharedQueueMessageBody.safeParse(message.data); if (!messageBody.success) { logger.error("Failed to parse message", { queueMessage: message.data, error: messageBody.error, }); await this.#ack(message.messageId); return { reason: "failed_to_parse_message", outcome: "fail_with_ack", attrs: { message_id: message.messageId, message_version: message.version }, error: messageBody.error, }; } const hydrateAttributes = (attrs: Record) => { return { ...attrs, message_id: message.messageId, message_version: message.version, run_id: message.messageId, message_type: messageBody.data.type, }; }; this._currentMessage = message; this._currentMessageData = messageBody.data; const messageResult = await this.#handleMessage(message, messageBody.data, dequeuedAt); switch (messageResult.action) { case "noop": { return { reason: messageResult.reason ?? "none_specified", outcome: "execution", attrs: hydrateAttributes(messageResult.attrs ?? {}), error: messageResult.error, interval: messageResult.interval, action: "noop", }; } case "ack_and_do_more_work": { await this.#ack(message.messageId); return { reason: messageResult.reason ?? "none_specified", outcome: "fail_with_ack", attrs: hydrateAttributes(messageResult.attrs ?? {}), error: messageResult.error, interval: messageResult.interval, action: "ack_and_do_more_work", }; } case "nack_and_do_more_work": { await this.#nack(message.messageId, messageResult.retryInMs); return { reason: messageResult.reason ?? "none_specified", outcome: "retry_with_nack", attrs: hydrateAttributes(messageResult.attrs ?? {}), error: messageResult.error, interval: messageResult.interval, action: "nack_and_do_more_work", }; } case "nack": { await marqs?.nackMessage(message.messageId); return { reason: messageResult.reason ?? "none_specified", outcome: "retry_with_nack", attrs: hydrateAttributes(messageResult.attrs ?? {}), error: messageResult.error, action: "nack", }; } } } async #handleMessage( message: MessagePayload, data: SharedQueueMessageBody, dequeuedAt: Date ): Promise { return await this.#startActiveSpan("handleMessage()", async (span) => { // TODO: For every ACK, decide what should be done with the existing run and attempts. Make sure to check the current statuses first. switch (data.type) { // MARK: EXECUTE case "EXECUTE": { return await this.#handleExecuteMessage(message, data, dequeuedAt); } // MARK: DEP RESUME // Resume after dependency completed with no remaining retries case "RESUME": { return await this.#handleResumeMessage(message, data, dequeuedAt); } // MARK: DURATION RESUME // Resume after duration-based wait case "RESUME_AFTER_DURATION": { return await this.#handleResumeAfterDurationMessage(message, data, dequeuedAt); } // MARK: FAIL // Fail for whatever reason, usually runs that have been resumed but stopped heartbeating case "FAIL": { return await this.#handleFailMessage(message, data, dequeuedAt); } } }); } async #handleExecuteMessage( message: MessagePayload, data: SharedQueueExecuteMessageBody, dequeuedAt: Date ): Promise { const existingTaskRun = await prisma.taskRun.findFirst({ where: { id: message.messageId, }, }); if (!existingTaskRun) { logger.error("No existing task run", { queueMessage: message.data, messageId: message.messageId, }); // INFO: There used to be a race condition where tasks could be triggered, but execute messages could be dequeued before the run finished being created in the DB // This should not be happening anymore. In case it does, consider reqeueuing here with a brief delay while limiting total retries. return { action: "ack_and_do_more_work", reason: "no_existing_task_run" }; } const retryingFromCheckpoint = !!data.checkpointEventId; const EXECUTABLE_RUN_STATUSES = { fromCheckpoint: ["WAITING_TO_RESUME"] satisfies TaskRunStatus[], withoutCheckpoint: ["PENDING", "RETRYING_AFTER_FAILURE"] satisfies TaskRunStatus[], }; if ( (retryingFromCheckpoint && !EXECUTABLE_RUN_STATUSES.fromCheckpoint.includes(existingTaskRun.status)) || (!retryingFromCheckpoint && !EXECUTABLE_RUN_STATUSES.withoutCheckpoint.includes(existingTaskRun.status)) ) { logger.warn("Task run has invalid status for execution. Going to ack", { queueMessage: message.data, messageId: message.messageId, taskRun: existingTaskRun.id, status: existingTaskRun.status, retryingFromCheckpoint, }); return { action: "ack_and_do_more_work", reason: "invalid_run_status", attrs: { status: existingTaskRun.status, retryingFromCheckpoint }, }; } // Check if the task run is locked to a specific worker, if not, use the current worker deployment const deployment = await this.#startActiveSpan("findCurrentWorkerDeployment", async (span) => { return existingTaskRun.lockedById ? await getWorkerDeploymentFromWorkerTask(existingTaskRun.lockedById) : existingTaskRun.lockedToVersionId ? await getWorkerDeploymentFromWorker(existingTaskRun.lockedToVersionId) : await findCurrentWorkerDeployment({ environmentId: existingTaskRun.runtimeEnvironmentId, type: "V1", }); }); const worker = deployment?.worker; if (!deployment || !worker) { // This happens when a run is "WAITING_FOR_DEPLOY" and is expected logger.info("No matching deployment found for task run", { queueMessage: message.data, messageId: message.messageId, }); await this.#markRunAsWaitingForDeploy(existingTaskRun.id); return { action: "ack_and_do_more_work", reason: "no_matching_deployment", attrs: { run_id: existingTaskRun.id, locked_by_id: existingTaskRun.lockedById ?? undefined, locked_to_version_id: existingTaskRun.lockedToVersionId ?? undefined, environment_id: existingTaskRun.runtimeEnvironmentId, }, }; } const imageReference = deployment.imageReference; if (!imageReference) { logger.error("Deployment is missing an image reference", { queueMessage: message.data, messageId: message.messageId, deployment: deployment.id, }); await this.#markRunAsWaitingForDeploy(existingTaskRun.id); return { action: "ack_and_do_more_work", reason: "missing_image_reference", attrs: { deployment_id: deployment.id, }, }; } const backgroundTask = worker.tasks.find( (task) => task.slug === existingTaskRun.taskIdentifier ); if (!backgroundTask) { const nonCurrentTask = await prisma.backgroundWorkerTask.findFirst({ where: { slug: existingTaskRun.taskIdentifier, projectId: existingTaskRun.projectId, runtimeEnvironmentId: existingTaskRun.runtimeEnvironmentId, }, include: { worker: { include: { deployment: { include: {}, }, }, }, }, }); if (nonCurrentTask) { logger.warn("Task for this run exists but is not part of the current deploy", { taskRun: existingTaskRun.id, taskIdentifier: existingTaskRun.taskIdentifier, }); } else { logger.warn("Task for this run has never been deployed", { taskRun: existingTaskRun.id, taskIdentifier: existingTaskRun.taskIdentifier, }); } await this.#markRunAsWaitingForDeploy(existingTaskRun.id); // If this task is ever deployed, a new message will be enqueued after successful indexing return { action: "ack_and_do_more_work", reason: "task_not_deployed", attrs: { run_id: existingTaskRun.id, task_identifier: existingTaskRun.taskIdentifier, }, }; } const lockedAt = new Date(); const machinePreset = existingTaskRun.machinePreset ?? machinePresetFromConfig(backgroundTask.machineConfig ?? {}).name; const maxDurationInSeconds = getMaxDuration( existingTaskRun.maxDurationInSeconds, backgroundTask.maxDurationInSeconds ); const startedAt = existingTaskRun.startedAt ?? dequeuedAt; const baseCostInCents = env.CENTS_PER_RUN; const lockedTaskRun = await prisma.taskRun.update({ where: { id: message.messageId, }, data: { lockedAt, lockedById: backgroundTask.id, lockedToVersionId: worker.id, taskVersion: worker.version, sdkVersion: worker.sdkVersion, cliVersion: worker.cliVersion, startedAt: startedAt, baseCostInCents: baseCostInCents, machinePreset: machinePreset, maxDurationInSeconds, }, include: { runtimeEnvironment: true, attempts: { take: 1, orderBy: { number: "desc" }, }, checkpoints: { take: 1, orderBy: { createdAt: "desc", }, }, lockedBy: true, }, }); if (!lockedTaskRun) { logger.warn("Failed to lock task run", { taskRun: existingTaskRun.id, taskIdentifier: existingTaskRun.taskIdentifier, deployment: deployment.id, backgroundWorker: worker.id, messageId: message.messageId, }); return { action: "ack_and_do_more_work", reason: "failed_to_lock_task_run", attrs: { run_id: existingTaskRun.id, task_identifier: existingTaskRun.taskIdentifier, deployment_id: deployment.id, background_worker_id: worker.id, message_id: message.messageId, }, }; } const queue = await findQueueInEnvironment( lockedTaskRun.queue, lockedTaskRun.runtimeEnvironmentId, lockedTaskRun.lockedById ?? undefined, backgroundTask ); if (!queue) { logger.debug("SharedQueueConsumer queue not found, so acking message", { queueMessage: message, taskRunQueue: lockedTaskRun.queue, runtimeEnvironmentId: lockedTaskRun.runtimeEnvironmentId, }); return { action: "ack_and_do_more_work", reason: "queue_not_found", attrs: { queue_name: sanitizeQueueName(lockedTaskRun.queue), runtime_environment_id: lockedTaskRun.runtimeEnvironmentId, }, interval: this._options.nextTickInterval, }; } if (!this._enabled) { logger.debug("SharedQueueConsumer not enabled, so nacking message", { queueMessage: message, }); return { action: "nack", reason: "not_enabled", attrs: { message_id: message.messageId, }, }; } const nextAttemptNumber = lockedTaskRun.attempts[0] ? lockedTaskRun.attempts[0].number + 1 : 1; const isRetry = nextAttemptNumber > 1 && (lockedTaskRun.status === "WAITING_TO_RESUME" || lockedTaskRun.status === "RETRYING_AFTER_FAILURE"); try { if (data.checkpointEventId) { const restoreService = new RestoreCheckpointService(); const checkpoint = await restoreService.call({ eventId: data.checkpointEventId, isRetry, }); if (!checkpoint) { logger.error("Failed to restore checkpoint", { queueMessage: message.data, messageId: message.messageId, runStatus: lockedTaskRun.status, isRetry, }); return { action: "ack_and_do_more_work", reason: "failed_to_restore_checkpoint", attrs: { run_status: lockedTaskRun.status, is_retry: isRetry, checkpoint_event_id: data.checkpointEventId, }, }; } return { action: "noop", reason: "restored_checkpoint", attrs: { checkpoint_event_id: data.checkpointEventId, }, }; } if (!worker.supportsLazyAttempts) { try { const service = new CreateTaskRunAttemptService(); await service.call({ runId: lockedTaskRun.id, setToExecuting: false, }); } catch (error) { logger.error("Failed to create task run attempt for outdated worker", { error, taskRun: lockedTaskRun.id, }); const service = new CrashTaskRunService(); await service.call(lockedTaskRun.id, { errorCode: TaskRunErrorCodes.OUTDATED_SDK_VERSION, }); return { action: "ack_and_do_more_work", reason: "failed_to_create_attempt_for_outdated_worker", attrs: { message_id: message.messageId, run_id: lockedTaskRun.id, }, error: error instanceof Error ? error : String(error), }; } } if (isRetry && !data.retryCheckpointsDisabled) { socketIo.coordinatorNamespace.emit("READY_FOR_RETRY", { version: "v1", runId: lockedTaskRun.id, }); // Retries for workers with disabled retry checkpoints will be handled just like normal attempts return { action: "noop", reason: "retry_checkpoints_disabled", }; } const machine = machinePresetFromRun(lockedTaskRun) ?? machinePresetFromConfig(lockedTaskRun.lockedBy?.machineConfig ?? {}); return await this.#startActiveSpan("scheduleAttemptOnProvider", async (span) => { span.setAttributes({ run_id: lockedTaskRun.id, }); if (await this._providerSender.validateCanSendMessage()) { await this._providerSender.send("BACKGROUND_WORKER_MESSAGE", { backgroundWorkerId: worker.friendlyId, data: { type: "SCHEDULE_ATTEMPT", image: imageReference, version: deployment.version, machine, nextAttemptNumber, // identifiers id: "placeholder", // TODO: Remove this completely in a future release envId: lockedTaskRun.runtimeEnvironment.id, envType: lockedTaskRun.runtimeEnvironment.type, orgId: lockedTaskRun.runtimeEnvironment.organizationId, projectId: lockedTaskRun.runtimeEnvironment.projectId, runId: lockedTaskRun.id, dequeuedAt: dequeuedAt.getTime(), }, }); return { action: "noop", reason: "scheduled_attempt", attrs: { next_attempt_number: nextAttemptNumber, }, }; } else { return { action: "nack_and_do_more_work", reason: "provider_not_connected", attrs: { run_id: lockedTaskRun.id, }, interval: this._options.nextTickInterval, retryInMs: 5_000, }; } }); } catch (e) { // We now need to unlock the task run and delete the task run attempt await prisma.$transaction([ prisma.taskRun.update({ where: { id: lockedTaskRun.id, }, data: { lockedAt: null, lockedById: null, status: lockedTaskRun.status, startedAt: existingTaskRun.startedAt, }, }), ]); logger.error("SharedQueueConsumer errored, so nacking message", { queueMessage: message, error: e instanceof Error ? { name: e.name, message: e.message, stack: e.stack } : e, }); return { action: "nack_and_do_more_work", reason: "failed_to_schedule_attempt", error: e instanceof Error ? e : String(e), interval: this._options.nextTickInterval, retryInMs: 5_000, }; } } async #handleResumeMessage( message: MessagePayload, data: SharedQueueResumeMessageBody, dequeuedAt: Date ): Promise { if (data.checkpointEventId) { try { const restoreService = new RestoreCheckpointService(); const checkpoint = await restoreService.call({ eventId: data.checkpointEventId, }); if (!checkpoint) { logger.error("Failed to restore checkpoint", { queueMessage: message.data, messageId: message.messageId, }); return { action: "ack_and_do_more_work", reason: "failed_to_restore_checkpoint", attrs: { checkpoint_event_id: data.checkpointEventId, }, }; } return { action: "noop", reason: "restored_checkpoint", attrs: { checkpoint_event_id: data.checkpointEventId, }, }; } catch (e) { return { action: "nack_and_do_more_work", reason: "failed_to_restore_checkpoint", error: e instanceof Error ? e : String(e), }; } } const resumableRun = await prisma.taskRun.findFirst({ where: { id: message.messageId, status: { notIn: FINAL_RUN_STATUSES, }, }, }); if (!resumableRun) { logger.error("Resumable run not found", { queueMessage: message.data, messageId: message.messageId, }); return { action: "ack_and_do_more_work", reason: "run_not_found", }; } if (resumableRun.status !== "EXECUTING") { logger.warn("Run is not executing, will try to resume anyway", { queueMessage: message.data, messageId: message.messageId, runStatus: resumableRun.status, }); } const resumableAttempt = await prisma.taskRunAttempt.findFirst({ where: { id: data.resumableAttemptId, }, include: { checkpoints: { take: 1, orderBy: { createdAt: "desc", }, }, }, }); if (!resumableAttempt) { logger.error("Resumable attempt not found", { queueMessage: message.data, messageId: message.messageId, }); return { action: "ack_and_do_more_work", reason: "resumable_attempt_not_found", attrs: { attempt_id: data.resumableAttemptId, }, }; } const queue = await findQueueInEnvironment( resumableRun.queue, resumableRun.runtimeEnvironmentId, resumableRun.lockedById ?? undefined ); if (!queue) { logger.debug("SharedQueueConsumer queue not found, so nacking message", { queueName: sanitizeQueueName(resumableRun.queue), attempt: resumableAttempt, }); return { action: "ack_and_do_more_work", reason: "queue_not_found", attrs: { queue_name: sanitizeQueueName(resumableRun.queue), }, interval: this._options.nextTickInterval, }; } if (!this._enabled) { return { action: "nack", reason: "not_enabled", attrs: { message_id: message.messageId, }, }; } try { const { completions, executions } = await this.#resolveCompletedAttemptsForResumeMessage( data.completedAttemptIds ); const resumeMessage = { version: "v1" as const, runId: resumableAttempt.taskRunId, attemptId: resumableAttempt.id, attemptFriendlyId: resumableAttempt.friendlyId, completions, executions, }; logger.debug("Broadcasting RESUME_AFTER_DEPENDENCY_WITH_ACK", { resumeMessage, message, resumableRun, }); // The attempt should still be running so we can broadcast to all coordinators to resume immediately const responses = await this.#startActiveSpan( "emitResumeAfterDependencyWithAck", async (span) => { try { span.setAttribute("attempt_id", resumableAttempt.id); span.setAttribute( "timeout_in_ms", env.SHARED_QUEUE_CONSUMER_EMIT_RESUME_DEPENDENCY_TIMEOUT_MS ); const responses = await socketIo.coordinatorNamespace .timeout(env.SHARED_QUEUE_CONSUMER_EMIT_RESUME_DEPENDENCY_TIMEOUT_MS) .emitWithAck("RESUME_AFTER_DEPENDENCY_WITH_ACK", resumeMessage); span.setAttribute("response_count", responses.length); const hasSuccess = responses.some((response) => response.success); span.setAttribute("has_success", hasSuccess); span.setAttribute("is_timeout", false); return responses; } catch (e) { if (e instanceof Error && "responses" in e && Array.isArray(e.responses)) { span.setAttribute("is_timeout", false); const responses = e.responses as AckCallbackResult[]; span.setAttribute("response_count", responses.length); const hasSuccess = responses.some( (response) => "success" in response && response.success ); span.setAttribute("has_success", hasSuccess); return responses; } throw e; } } ); logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK received", { resumeMessage, responses, message, }); if (responses.length === 0) { logger.error("RESUME_AFTER_DEPENDENCY_WITH_ACK no response", { resumeMessage, message, }); return { action: "nack_and_do_more_work", reason: "resume_after_dependency_with_ack_no_response", attrs: { resume_message: "RESUME_AFTER_DEPENDENCY_WITH_ACK", }, interval: this._options.nextTickInterval, retryInMs: 5_000, }; } const hasSuccess = responses.some((response) => response.success); if (hasSuccess) { return { action: "noop", reason: "resume_after_dependency_with_ack_success", }; } // No coordinator was able to resume the run logger.warn("RESUME_AFTER_DEPENDENCY_WITH_ACK failed", { resumeMessage, responses, message, }); // Let's check if the run is frozen if (resumableRun.status === "WAITING_TO_RESUME") { logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK run is waiting to be restored", { queueMessage: message.data, messageId: message.messageId, }); try { const restoreService = new RestoreCheckpointService(); const checkpointEvent = await restoreService.getLastCheckpointEventIfUnrestored( resumableRun.id ); if (checkpointEvent) { // The last checkpoint hasn't been restored yet, so restore it const checkpoint = await restoreService.call({ eventId: checkpointEvent.id, }); if (!checkpoint) { logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK failed to restore checkpoint", { queueMessage: message.data, messageId: message.messageId, }); return { action: "ack_and_do_more_work", reason: "failed_to_restore_checkpoint", attrs: { checkpoint_event_id: checkpointEvent.id, }, }; } logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK restored checkpoint", { queueMessage: message.data, messageId: message.messageId, checkpoint, }); return { action: "noop", reason: "restored_checkpoint", attrs: { checkpoint_event_id: data.checkpointEventId, }, }; } else { logger.debug( "RESUME_AFTER_DEPENDENCY_WITH_ACK run is frozen without last checkpoint event", { queueMessage: message.data, messageId: message.messageId, } ); return { action: "noop", reason: "resume_after_dependency_with_ack_frozen", }; } } catch (e) { return { action: "nack_and_do_more_work", reason: "waiting_to_resume_threw", error: e instanceof Error ? e : String(e), interval: this._options.nextTickInterval, retryInMs: 5_000, }; } } logger.debug("RESUME_AFTER_DEPENDENCY_WITH_ACK retrying", { queueMessage: message.data, messageId: message.messageId, }); return { action: "nack_and_do_more_work", reason: "resume_after_dependency_with_ack_retrying", attrs: { message_id: message.messageId, }, interval: this._options.nextTickInterval, retryInMs: 5_000, }; } catch (e) { if (e instanceof ResumePayloadAttemptsNotFoundError) { return { action: "ack_and_do_more_work", reason: "failed_to_get_resume_payloads_for_attempts", attrs: { attempt_ids: e.attemptIds.join(","), }, }; } else if (e instanceof ResumePayloadExecutionNotFoundError) { return { action: "ack_and_do_more_work", reason: "failed_to_get_resume_payloads_missing_execution", attrs: { attempt_id: e.attemptId, }, }; } else if (e instanceof ResumePayloadCompletionNotFoundError) { return { action: "ack_and_do_more_work", reason: "failed_to_get_resume_payloads_missing_completion", attrs: { attempt_id: e.attemptId, }, }; } logger.error("RESUME_AFTER_DEPENDENCY_WITH_ACK threw, nacking with delay", { message, error: e, }); return { action: "nack_and_do_more_work", reason: "resume_after_dependency_with_ack_threw", error: e instanceof Error ? e : String(e), interval: this._options.nextTickInterval, retryInMs: 5_000, }; } } async #handleResumeAfterDurationMessage( message: MessagePayload, data: SharedQueueResumeAfterDurationMessageBody, dequeuedAt: Date ): Promise { try { const restoreService = new RestoreCheckpointService(); const checkpoint = await restoreService.call({ eventId: data.checkpointEventId, }); if (!checkpoint) { logger.error("Failed to restore checkpoint", { queueMessage: message.data, messageId: message.messageId, }); return { action: "ack_and_do_more_work", reason: "failed_to_restore_checkpoint", attrs: { checkpoint_event_id: data.checkpointEventId, }, }; } return { action: "noop", reason: "restored_checkpoint", attrs: { checkpoint_event_id: data.checkpointEventId, }, }; } catch (e) { return { action: "nack_and_do_more_work", reason: "restoring_checkpoint_threw", error: e instanceof Error ? e : String(e), }; } } async #handleFailMessage( message: MessagePayload, data: SharedQueueFailMessageBody, dequeuedAt: Date ): Promise { const existingTaskRun = await prisma.taskRun.findFirst({ where: { id: message.messageId, }, }); if (!existingTaskRun) { logger.error("No existing task run to fail", { queueMessage: data, messageId: message.messageId, }); return { action: "ack_and_do_more_work", reason: "no_existing_task_run", }; } // TODO: Consider failing the attempt and retrying instead. This may not be a good idea, as dequeued FAIL messages tend to point towards critical, persistent errors. const service = new CrashTaskRunService(); await service.call(existingTaskRun.id, { crashAttempts: true, reason: data.reason, }); return { action: "ack_and_do_more_work", reason: "message_failed", }; } async #ack(messageId: string) { await marqs?.acknowledgeMessage(messageId, "Acking and doing more work in SharedQueueConsumer"); } async #nack(messageId: string, nackRetryInMs?: number) { const retryAt = nackRetryInMs ? Date.now() + nackRetryInMs : undefined; await marqs?.nackMessage(messageId, retryAt); } async #markRunAsWaitingForDeploy(runId: string) { logger.debug("Marking run as waiting for deploy", { runId }); const _run = await prisma.taskRun.update({ where: { id: runId, }, data: { status: "WAITING_FOR_DEPLOY", }, }); } async #resolveCompletedAttemptsForResumeMessage( completedAttemptIds: string[] ): Promise<{ completions: TaskRunExecutionResult[]; executions: TaskRunExecution[] }> { return await this.#startActiveSpan("resolveCompletedAttemptsForResumeMessage", async (span) => { span.setAttribute("completed_attempt_count", completedAttemptIds.length); // Chunk the completedAttemptIds into chunks of 10 const chunkedCompletedAttemptIds = chunk( completedAttemptIds, env.SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE ); span.setAttribute("chunk_count", chunkedCompletedAttemptIds.length); span.setAttribute("chunk_size", env.SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE); const allResumePayloads = await this.#startActiveSpan( "resolveAllResumePayloads", async (span) => { span.setAttribute("chunk_count", chunkedCompletedAttemptIds.length); span.setAttribute("chunk_size", env.SHARED_QUEUE_CONSUMER_RESOLVE_PAYLOADS_BATCH_SIZE); span.setAttribute("completed_attempt_count", completedAttemptIds.length); return await Promise.all( chunkedCompletedAttemptIds.map(async (attemptIds) => { const payloads = await this.#startActiveSpan("getResumePayloads", async (span) => { span.setAttribute("attempt_ids", attemptIds.join(",")); span.setAttribute("attempt_count", attemptIds.length); const payloads = await this._tasks.getResumePayloads(attemptIds); span.setAttribute("payload_count", payloads.length); return payloads; }); return { completions: payloads.map((payload) => payload.completion), executions: payloads.map((payload) => payload.execution), }; }) ); } ); return { completions: allResumePayloads.flatMap((payload) => payload.completions), executions: allResumePayloads.flatMap((payload) => payload.executions), }; }); } async #startActiveSpan( name: string, fn: (span: Span) => Promise, options?: SpanOptions ): Promise { return await tracer.startActiveSpan(name, options ?? {}, async (span) => { if (this._currentMessage) { span.setAttribute("message_id", this._currentMessage.messageId); span.setAttribute("run_id", this._currentMessage.messageId); span.setAttribute("message_version", this._currentMessage.version); } if (this._currentMessageData) { span.setAttribute("message_type", this._currentMessageData.type); } try { return await fn(span); } catch (error) { if (error instanceof Error) { span.recordException(error); } else { span.recordException(String(error)); } span.setStatus({ code: SpanStatusCode.ERROR }); throw error; } finally { span.end(); } }); } } class ResumePayloadAttemptsNotFoundError extends Error { constructor(public readonly attemptIds: string[]) { super(`Resume payload attempts not found for attempts ${attemptIds.join(", ")}`); } } class ResumePayloadExecutionNotFoundError extends Error { constructor(public readonly attemptId: string) { super(`Resume payload execution not found for attempt ${attemptId}`); } } class ResumePayloadCompletionNotFoundError extends Error { constructor(public readonly attemptId: string) { super(`Resume payload completion not found for attempt ${attemptId}`); } } function chunk(arr: T[], chunkSize: number): T[][] { return Array.from({ length: Math.ceil(arr.length / chunkSize) }, (_, i) => arr.slice(i * chunkSize, i * chunkSize + chunkSize) ); } export const AttemptForCompletionGetPayload = { select: { status: true, output: true, outputType: true, error: true, taskRun: { select: { taskIdentifier: true, friendlyId: true, }, }, }, } as const; type AttemptForCompletion = Prisma.TaskRunAttemptGetPayload; export const AttemptForExecutionGetPayload = { select: { id: true, friendlyId: true, taskRunId: true, number: true, startedAt: true, createdAt: true, backgroundWorkerId: true, backgroundWorkerTaskId: true, backgroundWorker: { select: { contentHash: true, version: true, }, }, backgroundWorkerTask: { select: { machineConfig: true, slug: true, filePath: true, exportName: true, }, }, status: true, runtimeEnvironment: { select: { ...RuntimeEnvironmentForEnvRepoPayload.select, organization: { select: { id: true, slug: true, title: true, }, }, project: { select: { id: true, externalRef: true, slug: true, name: true, }, }, }, }, taskRun: { select: { id: true, status: true, traceContext: true, machinePreset: true, friendlyId: true, payload: true, payloadType: true, context: true, createdAt: true, startedAt: true, isTest: true, replayedFromTaskRunFriendlyId: true, metadata: true, metadataType: true, idempotencyKey: true, usageDurationMs: true, costInCents: true, baseCostInCents: true, maxDurationInSeconds: true, runTags: true, taskEventStore: true, }, }, queue: { select: { name: true, friendlyId: true, }, }, }, } as const; type AttemptForExecution = Prisma.TaskRunAttemptGetPayload; class SharedQueueTasks { private _completionPayloadFromAttempt(attempt: AttemptForCompletion): TaskRunExecutionResult { const ok = attempt.status === "COMPLETED"; if (ok) { const success: TaskRunSuccessfulExecutionResult = { ok, id: attempt.taskRun.friendlyId, output: attempt.output ?? undefined, outputType: attempt.outputType, taskIdentifier: attempt.taskRun.taskIdentifier, }; return success; } else { const failure: TaskRunFailedExecutionResult = { ok, id: attempt.taskRun.friendlyId, error: attempt.error as TaskRunError, taskIdentifier: attempt.taskRun.taskIdentifier, }; return failure; } } private async _executionFromAttempt( attempt: AttemptForExecution, machinePreset?: MachinePreset ): Promise { const { backgroundWorkerTask, taskRun, queue } = attempt; if (!machinePreset) { machinePreset = machinePresetFromRun(attempt.taskRun) ?? machinePresetFromConfig(backgroundWorkerTask.machineConfig ?? {}); } const metadata = await parsePacket({ data: taskRun.metadata ?? undefined, dataType: taskRun.metadataType, }); const execution: V3ProdTaskRunExecution = { task: { id: backgroundWorkerTask.slug, filePath: backgroundWorkerTask.filePath, exportName: backgroundWorkerTask.exportName ?? backgroundWorkerTask.slug, }, attempt: { id: attempt.friendlyId, number: attempt.number, startedAt: attempt.startedAt ?? attempt.createdAt, backgroundWorkerId: attempt.backgroundWorkerId, backgroundWorkerTaskId: attempt.backgroundWorkerTaskId, status: "EXECUTING" as const, }, run: { id: taskRun.friendlyId, payload: taskRun.payload, payloadType: taskRun.payloadType, context: taskRun.context, createdAt: taskRun.createdAt, startedAt: taskRun.startedAt ?? taskRun.createdAt, tags: taskRun.runTags ?? [], isTest: taskRun.isTest, isReplay: !!taskRun.replayedFromTaskRunFriendlyId, idempotencyKey: taskRun.idempotencyKey ?? undefined, durationMs: taskRun.usageDurationMs, costInCents: taskRun.costInCents, baseCostInCents: taskRun.baseCostInCents, metadata, maxDuration: taskRun.maxDurationInSeconds ?? undefined, }, queue: { id: queue.friendlyId, name: queue.name, }, environment: { id: attempt.runtimeEnvironment.id, slug: attempt.runtimeEnvironment.slug, type: attempt.runtimeEnvironment.type, }, organization: { id: attempt.runtimeEnvironment.organization.id, slug: attempt.runtimeEnvironment.organization.slug, name: attempt.runtimeEnvironment.organization.title, }, project: { id: attempt.runtimeEnvironment.project.id, ref: attempt.runtimeEnvironment.project.externalRef, slug: attempt.runtimeEnvironment.project.slug, name: attempt.runtimeEnvironment.project.name, }, batch: undefined, // TODO: Removing this for now until we can do it more efficiently worker: { id: attempt.backgroundWorkerId, contentHash: attempt.backgroundWorker.contentHash, version: attempt.backgroundWorker.version, }, machine: machinePreset, }; return execution; } async getCompletionPayloadFromAttempt(id: string): Promise { const attempt = await prisma.taskRunAttempt.findFirst({ where: { id, status: { in: FINAL_ATTEMPT_STATUSES, }, }, ...AttemptForCompletionGetPayload, }); if (!attempt) { logger.error("No completed attempt found", { id }); return; } return this._completionPayloadFromAttempt(attempt); } async getExecutionPayloadFromAttempt({ id, setToExecuting, isRetrying, skipStatusChecks, }: { id: string; setToExecuting?: boolean; isRetrying?: boolean; skipStatusChecks?: boolean; }): Promise { const attempt = await prisma.taskRunAttempt.findFirst({ where: { id, }, ...AttemptForExecutionGetPayload, }); if (!attempt) { logger.error("getExecutionPayloadFromAttempt: No attempt found", { id }); return; } if (!skipStatusChecks) { switch (attempt.status) { case "CANCELED": case "EXECUTING": { logger.error( "getExecutionPayloadFromAttempt: Invalid attempt status for execution payload retrieval", { attemptId: id, status: attempt.status, } ); return; } } switch (attempt.taskRun.status) { case "CANCELED": case "EXECUTING": case "INTERRUPTED": { logger.error( "getExecutionPayloadFromAttempt: Invalid run status for execution payload retrieval", { attemptId: id, runId: attempt.taskRunId, status: attempt.taskRun.status, } ); return; } } } if (setToExecuting) { if (isFinalAttemptStatus(attempt.status) || isFinalRunStatus(attempt.taskRun.status)) { logger.error("getExecutionPayloadFromAttempt: Status already in final state", { attempt: { id: attempt.id, status: attempt.status, }, run: { id: attempt.taskRunId, status: attempt.taskRun.status, }, }); return; } await prisma.taskRunAttempt.update({ where: { id, }, data: { status: "EXECUTING", taskRun: { update: { data: { status: isRetrying ? "RETRYING_AFTER_FAILURE" : "EXECUTING", }, }, }, }, }); } const { backgroundWorkerTask, taskRun } = attempt; const machinePreset = machinePresetFromRun(attempt.taskRun) ?? machinePresetFromConfig(backgroundWorkerTask.machineConfig ?? {}); const execution = await this._executionFromAttempt(attempt, machinePreset); const variables = await this.#buildEnvironmentVariables( attempt.runtimeEnvironment, taskRun.id, machinePreset, taskRun.taskEventStore ?? undefined ); const payload: V3ProdTaskRunExecutionPayload = { execution, traceContext: taskRun.traceContext as Record, environment: variables.reduce((acc: Record, curr) => { acc[curr.key] = curr.value; return acc; }, {}), }; return payload; } async getResumePayload(attemptId: string): Promise< | { execution: V3ProdTaskRunExecution; completion: TaskRunExecutionResult; } | undefined > { const attempt = await prisma.taskRunAttempt.findFirst({ where: { id: attemptId, }, select: { ...AttemptForExecutionGetPayload.select, error: true, output: true, outputType: true, taskRun: { select: { ...AttemptForExecutionGetPayload.select.taskRun.select, taskIdentifier: true, }, }, }, }); if (!attempt) { logger.error("getResumePayload: No attempt found", { id: attemptId }); return; } const execution = await this._executionFromAttempt(attempt); const completion = this._completionPayloadFromAttempt(attempt); return { execution, completion, }; } async getResumePayloads(attemptIds: string[]): Promise< Array<{ execution: V3ProdTaskRunExecution; completion: TaskRunExecutionResult; }> > { const attempts = await prisma.taskRunAttempt.findMany({ where: { id: { in: attemptIds, }, }, select: { ...AttemptForExecutionGetPayload.select, error: true, output: true, outputType: true, taskRun: { select: { ...AttemptForExecutionGetPayload.select.taskRun.select, taskIdentifier: true, }, }, }, }); if (attempts.length !== attemptIds.length) { logger.error("getResumePayloads: Not all attempts found", { attemptIds }); throw new ResumePayloadAttemptsNotFoundError(attemptIds); } const payloads = await Promise.all( attempts.map(async (attempt) => { const execution = await this._executionFromAttempt(attempt); if (!execution) { throw new ResumePayloadExecutionNotFoundError(attempt.id); } const completion = this._completionPayloadFromAttempt(attempt); if (!completion) { throw new ResumePayloadCompletionNotFoundError(attempt.id); } return { execution, completion, }; }) ); return payloads; } async getLatestExecutionPayloadFromRun( id: string, setToExecuting?: boolean, isRetrying?: boolean ): Promise { const run = await prisma.taskRun.findFirst({ where: { id, }, include: { attempts: { take: 1, orderBy: { createdAt: "desc", }, }, }, }); const latestAttempt = run?.attempts[0]; if (!latestAttempt) { logger.error("No attempts for run", { id }); return; } return this.getExecutionPayloadFromAttempt({ id: latestAttempt.id, setToExecuting, isRetrying, }); } async getLazyAttemptPayload( envId: string, runId: string ): Promise { const environment = await findEnvironmentById(envId); if (!environment) { logger.error("getLazyAttemptPayload: Environment not found", { runId, envId }); return; } const run = await prisma.taskRun.findFirst({ where: { id: runId, }, select: { id: true, traceContext: true, friendlyId: true, isTest: true, replayedFromTaskRunFriendlyId: true, lockedBy: { select: { machineConfig: true, }, }, machinePreset: true, taskEventStore: true, }, }); if (!run) { logger.error("getLazyAttemptPayload: Run not found", { runId, envId }); return; } const attemptCount = await prisma.taskRunAttempt.count({ where: { taskRunId: run.id, }, }); logger.debug("getLazyAttemptPayload: Getting lazy attempt payload for run", { run, attemptCount, }); const machinePreset = machinePresetFromRun(run) ?? machinePresetFromConfig(run.lockedBy?.machineConfig ?? {}); const variables = await this.#buildEnvironmentVariables( environment, run.id, machinePreset, run.taskEventStore ?? undefined ); return { traceContext: run.traceContext as Record, environment: variables.reduce((acc: Record, curr) => { acc[curr.key] = curr.value; return acc; }, {}), runId: run.friendlyId, messageId: run.id, isTest: run.isTest, isReplay: !!run.replayedFromTaskRunFriendlyId, attemptCount, metrics: [], } satisfies TaskRunExecutionLazyAttemptPayload; } async taskHeartbeat(attemptFriendlyId: string) { logger.debug("[SharedQueueConsumer] taskHeartbeat()", { id: attemptFriendlyId }); const taskRunAttempt = await prisma.taskRunAttempt.findFirst({ where: { friendlyId: attemptFriendlyId }, }); if (!taskRunAttempt) { return; } await this.#heartbeat(taskRunAttempt.taskRunId); } async taskRunHeartbeat(runId: string) { logger.debug("[SharedQueueConsumer] taskRunHeartbeat()", { runId }); await this.#heartbeat(runId); } public async taskRunFailed(completion: TaskRunFailedExecutionResult) { logger.debug("[SharedQueueConsumer] taskRunFailed()", { completion }); const service = new FailedTaskRunService(); await service.call(completion.id, completion); } async #heartbeat(runId: string) { await marqs?.heartbeatMessage(runId); try { // There can be a lot of calls per minute and the data doesn't have to be accurate, so use the read replica const taskRun = await $replica.taskRun.findFirst({ where: { id: runId, }, select: { id: true, status: true, runtimeEnvironment: { select: { type: true, }, }, lockedToVersion: { select: { supportsLazyAttempts: true, }, }, }, }); if (!taskRun) { logger.error("SharedQueueTasks.#heartbeat: Task run not found", { runId, }); return; } if (taskRun.runtimeEnvironment.type === "DEVELOPMENT") { return; } if (isFinalRunStatus(taskRun.status)) { logger.debug("SharedQueueTasks.#heartbeat: Task run is in final status", { runId, status: taskRun.status, }); // Signal to exit any leftover containers socketIo.coordinatorNamespace.emit("REQUEST_RUN_CANCELLATION", { version: "v1", runId: taskRun.id, // Give the run a few seconds to exit to complete any flushing etc delayInMs: taskRun.lockedToVersion?.supportsLazyAttempts ? 5_000 : undefined, }); return; } } catch (error) { logger.error("SharedQueueTasks.#heartbeat: Error signaling run cancellation", { runId, error: error instanceof Error ? error.message : error, }); } } async #buildEnvironmentVariables( environment: RuntimeEnvironmentForEnvRepo, runId: string, machinePreset: MachinePreset, taskEventStore?: string ): Promise> { const variables = await resolveVariablesForEnvironment(environment); const jwt = await generateJWTTokenForEnvironment(environment, { run_id: runId, machine_preset: machinePreset.name, }); if (taskEventStore) { const resourceAttributes = JSON.stringify({ [SemanticInternalAttributes.TASK_EVENT_STORE]: taskEventStore, }); variables.push(...[{ key: "OTEL_RESOURCE_ATTRIBUTES", value: resourceAttributes }]); } return [ ...variables, ...[ { key: "TRIGGER_JWT", value: jwt }, { key: "TRIGGER_RUN_ID", value: runId }, { key: "TRIGGER_MACHINE_PRESET", value: machinePreset.name, }, ], ]; } } export const sharedQueueTasks = singleton("sharedQueueTasks", () => new SharedQueueTasks());