From 68f813d0d9eaa1c071533bd4c7fe74f42c3af54f Mon Sep 17 00:00:00 2001 From: Robert Markovski <5818108+Roshan931@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:15:43 +0200 Subject: [PATCH] fix: OTLP nanosecond timestamp overflow in webapp event repositor... --- .../app/v3/eventRepository/common.server.ts | 4 +-- .../app/v3/eventRepository/index.server.ts | 7 ++--- .../webapp/app/v3/runEngineHandlers.server.ts | 7 +++-- .../test/eventRepositoryNanoseconds.test.ts | 31 +++++++++++++++++++ 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 apps/webapp/test/eventRepositoryNanoseconds.test.ts diff --git a/apps/webapp/app/v3/eventRepository/common.server.ts b/apps/webapp/app/v3/eventRepository/common.server.ts index 8f7fef29ad4..12a53f29cd7 100644 --- a/apps/webapp/app/v3/eventRepository/common.server.ts +++ b/apps/webapp/app/v3/eventRepository/common.server.ts @@ -25,7 +25,7 @@ export function extractContextFromCarrier(carrier: Record) { } export function getNowInNanoseconds(): bigint { - return BigInt(new Date().getTime() * 1_000_000); + return convertDateToNanoseconds(new Date()); } export function getDateFromNanoseconds(nanoseconds: bigint): Date { @@ -39,7 +39,7 @@ export function calculateDurationFromStart( ) { const $endtime = typeof endTime === "string" ? new Date(endTime) : endTime; - const duration = Number(BigInt($endtime.getTime() * 1_000_000) - startTime); + const duration = Number(convertDateToNanoseconds($endtime) - startTime); if (minimumDuration && duration < minimumDuration) { return minimumDuration; diff --git a/apps/webapp/app/v3/eventRepository/index.server.ts b/apps/webapp/app/v3/eventRepository/index.server.ts index c599f4e6b96..9f2e57bb94c 100644 --- a/apps/webapp/app/v3/eventRepository/index.server.ts +++ b/apps/webapp/app/v3/eventRepository/index.server.ts @@ -9,10 +9,10 @@ import { FEATURE_FLAG } from "../featureFlags"; import { flag } from "../featureFlags.server"; import { getTaskEventStore } from "../taskEventStore.server"; import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server"; +import { convertDateToNanoseconds } from "./common.server"; export const EVENT_STORE_TYPES = { - POSTGRES: "postgres", - CLICKHOUSE: "clickhouse", + POSTGRES: "postgres", CLICKHOUSE: "clickhouse", CLICKHOUSE_V2: "clickhouse_v2", } as const; @@ -208,10 +208,9 @@ async function recordRunEvent( runId: foundRun.friendlyId, ...attributes, }, - startTime: BigInt((startTime?.getTime() ?? Date.now()) * 1_000_000), + startTime: convertDateToNanoseconds(startTime ?? new Date()), ...optionsRest, }); - return { success: true, }; diff --git a/apps/webapp/app/v3/runEngineHandlers.server.ts b/apps/webapp/app/v3/runEngineHandlers.server.ts index c44bcc54cec..6d8b716dfcb 100644 --- a/apps/webapp/app/v3/runEngineHandlers.server.ts +++ b/apps/webapp/app/v3/runEngineHandlers.server.ts @@ -24,7 +24,10 @@ import { MetadataTooLargeError } from "~/utils/packets"; import { QueueSizeLimitExceededError } from "~/v3/services/common.server"; import { TriggerTaskService } from "~/v3/services/triggerTask.server"; import { tracer } from "~/v3/tracer.server"; -import { createExceptionPropertiesFromError } from "./eventRepository/common.server"; +import { + convertDateToNanoseconds, + createExceptionPropertiesFromError, +} from "./eventRepository/common.server"; import { getEventRepositoryForStore, recordRunDebugLog } from "./eventRepository/index.server"; import { roomFromFriendlyRunId, socketIo } from "./handleSocketIo.server"; import { engine } from "./runEngine.server"; @@ -555,7 +558,7 @@ export function registerRunEngineEventBusHandlers() { ); await eventRepository.recordEvent(retryMessage, { - startTime: BigInt(time.getTime() * 1000000), + startTime: convertDateToNanoseconds(time), taskSlug: run.taskIdentifier, environment, attributes: { diff --git a/apps/webapp/test/eventRepositoryNanoseconds.test.ts b/apps/webapp/test/eventRepositoryNanoseconds.test.ts new file mode 100644 index 00000000000..7ee38d729ca --- /dev/null +++ b/apps/webapp/test/eventRepositoryNanoseconds.test.ts @@ -0,0 +1,31 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { + calculateDurationFromStart, + convertDateToNanoseconds, + getNowInNanoseconds, +} from "~/v3/eventRepository/common.server"; + +const EPOCH_MS = 1_782_994_600_413; + +describe("event repository nanosecond conversion", () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it("converts a date to nanoseconds without losing precision", () => { + expect(convertDateToNanoseconds(new Date(EPOCH_MS))).toBe(1_782_994_600_413_000_000n); + }); + + it("returns the current time in exact nanoseconds", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(EPOCH_MS)); + + expect(getNowInNanoseconds()).toBe(1_782_994_600_413_000_000n); + }); + + it("calculates an exact duration from a nanosecond start time", () => { + const startTime = convertDateToNanoseconds(new Date(EPOCH_MS)); + + expect(calculateDurationFromStart(startTime, new Date(EPOCH_MS + 2))).toBe(2_000_000); + }); +});