Unverified Commit d9aea3f7 authored by Hong Minhee's avatar Hong Minhee
Browse files

Fix race condition in #updateTraceSummary with CAS

Use compare-and-swap operation when available to ensure atomic updates
to trace summaries in concurrent/distributed environments.

https://github.com/fedify-dev/fedify/pull/502#discussion_r2642988002
https://github.com/fedify-dev/fedify/pull/502#discussion_r2642993304
parent 31edf2a9
Loading
Loading
Loading
Loading
+32 −10
Original line number Diff line number Diff line
@@ -445,20 +445,42 @@ export class FedifySpanExporter implements SpanExporter {
      record.traceId,
    ] as KvKey;

    const existing = await this.#kv.get<TraceSummary>(summaryKey);

    const summary: TraceSummary = existing ?? {
    const createOrUpdateSummary = (
      existing: TraceSummary | undefined,
    ): TraceSummary => {
      const summary: TraceSummary = existing != null
        ? {
          traceId: existing.traceId,
          timestamp: existing.timestamp,
          activityCount: existing.activityCount,
          activityTypes: [...existing.activityTypes],
        }
        : {
          traceId: record.traceId,
          timestamp: record.timestamp,
          activityCount: 0,
          activityTypes: [],
        };

      summary.activityCount += 1;
      if (!summary.activityTypes.includes(record.activityType)) {
        summary.activityTypes.push(record.activityType);
      }
      return summary;
    };

    if (this.#kv.cas != null) {
      for (let attempt = 0; attempt < 3; attempt++) {
        const existing = await this.#kv.get<TraceSummary>(summaryKey);
        const summary = createOrUpdateSummary(existing);
        if (await this.#kv.cas(summaryKey, existing, summary, options)) {
          return;
        }
      }
    }

    // Fallback to non-atomic set if CAS is not available or fails
    const existing = await this.#kv.get<TraceSummary>(summaryKey);
    const summary = createOrUpdateSummary(existing);
    await this.#kv.set(summaryKey, summary, options);
  }