|
1 | | -import { RunEngine, type CompleteBatchResult } from "@internal/run-engine"; |
2 | | -import { BatchTaskRunStatus, Prisma } from "@trigger.dev/database"; |
3 | | -import { SpanKind } from "@opentelemetry/api"; |
| 1 | +import { RunEngine } from "@internal/run-engine"; |
4 | 2 | import { $replica, prisma } from "~/db.server"; |
5 | 3 | import { env } from "~/env.server"; |
6 | 4 | import { createBatchGlobalRateLimiter } from "~/runEngine/concerns/batchGlobalRateLimiter.server"; |
| 5 | +import { logger } from "~/services/logger.server"; |
7 | 6 | import { defaultMachine, getCurrentPlan } from "~/services/platform.v3.server"; |
8 | 7 | import { singleton } from "~/utils/singleton"; |
9 | 8 | import { allMachines } from "./machinePresets.server"; |
10 | 9 | import { meter, tracer } from "./tracer.server"; |
11 | | -import { logger } from "~/services/logger.server"; |
12 | | -import type { AuthenticatedEnvironment } from "~/services/apiAuth.server"; |
13 | | -import { TriggerTaskService } from "./services/triggerTask.server"; |
14 | 10 |
|
15 | 11 | export const engine = singleton("RunEngine", createRunEngine); |
16 | 12 |
|
@@ -188,202 +184,5 @@ function createRunEngine() { |
188 | 184 | }, |
189 | 185 | }); |
190 | 186 |
|
191 | | - // Set up BatchQueue callbacks if enabled |
192 | | - if (engine.isBatchQueueEnabled()) { |
193 | | - setupBatchQueueCallbacks(engine); |
194 | | - } |
195 | | - |
196 | 187 | return engine; |
197 | 188 | } |
198 | | - |
199 | | -/** |
200 | | - * Normalize the payload from BatchQueue. |
201 | | - * |
202 | | - * Handles different payload types: |
203 | | - * - "application/store": Already offloaded to R2, payload is the path - pass through as-is |
204 | | - * - "application/json": May be a pre-serialized JSON string - parse to avoid double-stringification |
205 | | - * - Other types: Pass through as-is |
206 | | - * |
207 | | - * @param payload - The raw payload from the batch item |
208 | | - * @param payloadType - The payload type (e.g., "application/json", "application/store") |
209 | | - */ |
210 | | -function normalizePayload(payload: unknown, payloadType?: string): unknown { |
211 | | - // For non-JSON payloads (including application/store for R2-offloaded payloads), |
212 | | - // return as-is - no normalization needed |
213 | | - if (payloadType !== "application/json" && payloadType !== undefined) { |
214 | | - return payload; |
215 | | - } |
216 | | - |
217 | | - // For JSON payloads, if payload is a string, try to parse it |
218 | | - // This handles pre-serialized JSON from the SDK |
219 | | - if (typeof payload === "string") { |
220 | | - try { |
221 | | - return JSON.parse(payload); |
222 | | - } catch { |
223 | | - // If it's not valid JSON, return as-is |
224 | | - return payload; |
225 | | - } |
226 | | - } |
227 | | - |
228 | | - return payload; |
229 | | -} |
230 | | - |
231 | | -/** |
232 | | - * Set up the BatchQueue processing callbacks. |
233 | | - * These handle creating runs from batch items and completing batches. |
234 | | - * |
235 | | - * Payload handling: |
236 | | - * - If payloadType is "application/store", the payload is an R2 path (already offloaded) |
237 | | - * - DefaultPayloadProcessor in TriggerTaskService will pass it through without re-offloading |
238 | | - * - The run engine will download from R2 when the task executes |
239 | | - */ |
240 | | -function setupBatchQueueCallbacks(engine: RunEngine) { |
241 | | - // Item processing callback - creates a run for each batch item |
242 | | - engine.setBatchProcessItemCallback(async ({ batchId, friendlyId, itemIndex, item, meta }) => { |
243 | | - return tracer.startActiveSpan( |
244 | | - "batch.processItem", |
245 | | - { |
246 | | - kind: SpanKind.INTERNAL, |
247 | | - attributes: { |
248 | | - "batch.id": friendlyId, |
249 | | - "batch.item_index": itemIndex, |
250 | | - "batch.task": item.task, |
251 | | - "batch.environment_id": meta.environmentId, |
252 | | - "batch.parent_run_id": meta.parentRunId ?? "", |
253 | | - }, |
254 | | - }, |
255 | | - async (span) => { |
256 | | - try { |
257 | | - const triggerTaskService = new TriggerTaskService(); |
258 | | - |
259 | | - // Normalize payload - for application/store (R2 paths), this passes through as-is |
260 | | - const payload = normalizePayload(item.payload, item.payloadType); |
261 | | - |
262 | | - const result = await triggerTaskService.call( |
263 | | - item.task, |
264 | | - { |
265 | | - id: meta.environmentId, |
266 | | - type: meta.environmentType, |
267 | | - organizationId: meta.organizationId, |
268 | | - projectId: meta.projectId, |
269 | | - organization: { id: meta.organizationId }, |
270 | | - project: { id: meta.projectId }, |
271 | | - } as AuthenticatedEnvironment, |
272 | | - { |
273 | | - payload, |
274 | | - options: { |
275 | | - ...(item.options as Record<string, unknown>), |
276 | | - payloadType: item.payloadType, |
277 | | - parentRunId: meta.parentRunId, |
278 | | - resumeParentOnCompletion: meta.resumeParentOnCompletion, |
279 | | - parentBatch: batchId, |
280 | | - }, |
281 | | - }, |
282 | | - { |
283 | | - triggerVersion: meta.triggerVersion, |
284 | | - traceContext: meta.traceContext as Record<string, unknown> | undefined, |
285 | | - spanParentAsLink: meta.spanParentAsLink, |
286 | | - batchId, |
287 | | - batchIndex: itemIndex, |
288 | | - skipChecks: true, // Already validated at batch level |
289 | | - realtimeStreamsVersion: meta.realtimeStreamsVersion, |
290 | | - }, |
291 | | - "V2" |
292 | | - ); |
293 | | - |
294 | | - if (result) { |
295 | | - span.setAttribute("batch.result.run_id", result.run.friendlyId); |
296 | | - span.end(); |
297 | | - return { success: true as const, runId: result.run.friendlyId }; |
298 | | - } else { |
299 | | - span.setAttribute("batch.result.error", "TriggerTaskService returned undefined"); |
300 | | - span.end(); |
301 | | - return { |
302 | | - success: false as const, |
303 | | - error: "TriggerTaskService returned undefined", |
304 | | - errorCode: "TRIGGER_FAILED", |
305 | | - }; |
306 | | - } |
307 | | - } catch (error) { |
308 | | - span.setAttribute( |
309 | | - "batch.result.error", |
310 | | - error instanceof Error ? error.message : String(error) |
311 | | - ); |
312 | | - span.recordException(error instanceof Error ? error : new Error(String(error))); |
313 | | - span.end(); |
314 | | - return { |
315 | | - success: false as const, |
316 | | - error: error instanceof Error ? error.message : String(error), |
317 | | - errorCode: "TRIGGER_ERROR", |
318 | | - }; |
319 | | - } |
320 | | - } |
321 | | - ); |
322 | | - }); |
323 | | - |
324 | | - // Batch completion callback - updates Postgres with results |
325 | | - engine.setBatchCompletionCallback(async (result: CompleteBatchResult) => { |
326 | | - const { batchId, runIds, successfulRunCount, failedRunCount, failures } = result; |
327 | | - |
328 | | - // Determine final status |
329 | | - let status: BatchTaskRunStatus; |
330 | | - if (failedRunCount > 0 && successfulRunCount === 0) { |
331 | | - status = "ABORTED"; |
332 | | - } else if (failedRunCount > 0) { |
333 | | - status = "PARTIAL_FAILED"; |
334 | | - } else { |
335 | | - status = "PENDING"; // All runs created, waiting for completion |
336 | | - } |
337 | | - |
338 | | - try { |
339 | | - // Update BatchTaskRun |
340 | | - await prisma.batchTaskRun.update({ |
341 | | - where: { id: batchId }, |
342 | | - data: { |
343 | | - status, |
344 | | - runIds, |
345 | | - successfulRunCount, |
346 | | - failedRunCount, |
347 | | - completedAt: status === "ABORTED" ? new Date() : undefined, |
348 | | - processingCompletedAt: new Date(), |
349 | | - }, |
350 | | - }); |
351 | | - |
352 | | - // Create error records if there were failures |
353 | | - if (failures.length > 0) { |
354 | | - for (const failure of failures) { |
355 | | - await prisma.batchTaskRunError.create({ |
356 | | - data: { |
357 | | - batchTaskRunId: batchId, |
358 | | - index: failure.index, |
359 | | - taskIdentifier: failure.taskIdentifier, |
360 | | - payload: failure.payload, |
361 | | - options: failure.options as Prisma.InputJsonValue | undefined, |
362 | | - error: failure.error, |
363 | | - errorCode: failure.errorCode, |
364 | | - }, |
365 | | - }); |
366 | | - } |
367 | | - } |
368 | | - |
369 | | - // Try to complete the batch (handles waitpoint completion if all runs are done) |
370 | | - if (status !== "ABORTED") { |
371 | | - await engine.tryCompleteBatch({ batchId }); |
372 | | - } |
373 | | - |
374 | | - logger.info("Batch completion handled", { |
375 | | - batchId, |
376 | | - status, |
377 | | - successfulRunCount, |
378 | | - failedRunCount, |
379 | | - }); |
380 | | - } catch (error) { |
381 | | - logger.error("Failed to handle batch completion", { |
382 | | - batchId, |
383 | | - error: error instanceof Error ? error.message : String(error), |
384 | | - }); |
385 | | - } |
386 | | - }); |
387 | | - |
388 | | - logger.info("BatchQueue callbacks configured"); |
389 | | -} |
0 commit comments