发布

  • [OPIK-7346] Fix ThreadDAO.findById multi-emit HTTP 500 (#7478)

    frostbyte_neo 发布于 2026-07-15 12:00:17 +00:00

    ThreadDAO.findById used Reactor's singleOrEmpty() on the by-id thread
    stream. Like single(), it is backed by MonoSingle and throws
    IndexOutOfBoundsException ("Source emitted more than one item") when its
    source emits more than one element, surfacing as an unmapped HTTP 500 on
    GET /v1/private/traces/threads/retrieve.

    SELECT_TRACES_THREAD_BY_ID can legitimately return more than one row for a
    single (workspace_id, project_id, thread_id). The trace_threads table is a
    ReplacingMergeTree whose dedup/sort key includes the internal
    thread_model_id (id), so multiple thread_model_ids can coexist for one
    user-facing thread_id. trace_threads_final keeps one row per id
    (LIMIT 1 BY id), and the final LEFT JOIN is on (workspace_id, project_id,
    thread_id) -- not on id -- so the single aggregated trace row fans out to
    one row per thread_model_id. Each row maps to one TraceThread, so
    singleOrEmpty() throws on any thread that has more than one model id.

    Replace singleOrEmpty() with a multi-emission-tolerant fold
    (reduce((existing, ignored) -> existing)) extracted as a
    @VisibleForTesting helper. Folding to the first emission tolerates the
    multi-row result while preserving the empty->empty (not-found) contract.
    Same defect class as #7454 (getProjectIds), applied to this sibling call
    site.

    Add ThreadDAOImplTest covering multi-emit collapse, single pass-through,
    and empty-stays-empty via StepVerifier.

    Co-authored-by: Claude Opus 4.8 (1M context) noreply@anthropic.com

    下载附件