…arch corruption
When concurrent segment search is active, multiple slice threads run
InnerHitsPhase.hitExecute on the same request-level InnerHitsContext. Each
InnerHitSubContext holds mutable per-hit state (docIdsToLoad, root id, root
SourceLookup) that is re-aimed for every hit. The shared SourceLookup caches a
single Lucene stored-fields merge instance, so interleaved reads decode from a
wrong byte offset: this surfaces as index_out_of_bounds / CorruptIndexException
against checksum-perfect files, and can escalate to
AssertionError: Unknown type flag: N, which kills the node.
Fix: InnerHitsPhase.getProcessor now deep-copies the inner-hits contexts for
each fetch, so every concurrent slice thread works on its own InnerHitSubContext
instance and never shares mutable per-hit state. InnerHitsContext gains copy();
InnerHitSubContext gains an abstract copy() implemented by the nested and
join-field sub-contexts; SubSearchContext gains copyFetchStateTo() to replicate
fetch configuration onto a fresh instance.
Signed-off-by: waterWang <672684719@qq.com>
Description
Fixes #22868
When concurrent segment search is active (
index.search.concurrent_segment_search.mode=all), a request carrying both a nestedinner_hitsand atop_hitsaggregation corrupts_sourcereads and can kill the node.Root cause: every slice thread runs
InnerHitsPhase.hitExecuteon the same request-levelInnerHitsContext.InnerHitSubContextholds mutable per-hit state (docIdsToLoad, rootid, rootSourceLookup) that is re-aimed for each hit (InnerHitsPhase.hitExecutelines ~91-93). Two slice threads interleave those writes, so one thread reads through another thread'sSourceLookup.SourceLookupis documented "Not thread safe" and lazily caches a single Lucene stored-fields merge instance (Lucene90CompressingStoredFieldsReader.serializedDocumentseeks and resets one shared block state without a lock). The corrupted reads surface asindex_out_of_bounds_exception,CorruptIndexExceptionagainst checksum-perfect files, or — for the illegal type flags 6/7 underTYPE_MASK—AssertionError: Unknown type flag: N, which escapes thecatch (Exception)inSourceLookupand kills the node viaOpenSearchUncaughtExceptionHandler.Fix:
InnerHitsPhase.getProcessornow deep-copies the inner-hits contexts for each fetch, so every concurrent slice thread works on its ownInnerHitSubContextinstance and never shares mutable per-hit state. This mirrors the rest of the fetch path, which is already per-slice by construction (SubSearchContextper slice,FetchContextper fetch). The inner-hits contexts were the only piece of per-hit state that escaped that isolation, becauseSubSearchContextinheritsinnerHits()fromFilteredSearchContext, which forwards to the request-level context.Changes:
InnerHitsContext: newcopy()returning a deep copy of the definition map; new abstractInnerHitSubContext.copy();copyTo()helper replicating fetch configuration (including recursive child inner hits).SubSearchContext: newcopyFetchStateTo()that copies fetch configuration (from/size/sort/query/highlight/script fields/fetch source/docvalues/fetch fields) onto a freshly constructed instance, while leaving result holders and per-fetch doc-IDs state untouched.NestedInnerHitSubContext: implementscopy().JoinFieldInnerHitSubContext: implementscopy().InnerHitsPhase:getProcessoruses the deep copy instead of the shared request-level map.Testing:
InnerHitsContextTestscovers the copy semantics (independent maps and definition instances, names preserved, empty context). The reproducible cluster-level scenario from the issue (nested inner_hits + terms→top_hits over high-cardinality field,mode=all,max_slice_count=8) no longer fails.Signed-off-by: waterWang 672684719@qq.com