-
[OPIK-7346] Fix ThreadDAO.findById multi-emit HTTP 500 (#7478)
发布于
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
下载附件