Skip to content

enhance: classify segcore errors across producers and enforce classification end-to-end - #50768

Open
czs007 wants to merge 56 commits into
milvus-io:masterfrom
czs007:error_code_segcore
Open

enhance: classify segcore errors across producers and enforce classification end-to-end#50768
czs007 wants to merge 56 commits into
milvus-io:masterfrom
czs007:error_code_segcore

Conversation

@czs007

@czs007 czs007 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

What

Consume the producer-owned error classification at the segcore boundary and make the whole C++→Go classification drift-proof, so a segcore error is classified as input (caller's fault, non-retriable), transient (retriable) or permanent (non-retriable) instead of flattening to UnexpectedError(2001) or carrying the wrong retry default.

Design + tracking: #50903.

Changes

  • T1 — register the storage fallback pair in pkg/util/merr/segcore.go: StorageError(2044) non-retriable, StorageTransientError(2045) retriable.

  • T2KnowhereStatusToErrorCode → a switch with no default + -Werror=switch over the full knowhere::Status; add build-path variant KnowhereBuildStatusToErrorCode so a build-time OOM / disk read stays retriable instead of collapsing into a permanent IndexBuildError.

  • T3/T4ArrowStatusToErrorCode delegates to the producer's milvus_storage::ToSegcoreError (retires milvus's duplicate mapper); audited and routed 25 storage arrow-status sites that were collapsing to 2001 through the single mapper (extracted to storage/StatusToErrorCode.h), always preserving the arrow sub-code in the message.

  • T5 — unmapped-code observability: UnmappedSegcoreCodeTotal{code} counter + rate-limited WARN via an observer hook (merr is a leaf package); registered on QueryNode and DataNode. Unknown code degrades to non-retriable, never panics.

  • T6 — codegen + compile-time enforcement: a generated SegcoreCode type (from milvus-common's EasyAssert.h) + an exhaustive classForCode switch marked //exhaustive:enforce, with the exhaustive golangci-lint enabled opt-in — a new C++ code that is not classified fails lint (the C++→Go analog of -Werror=switch).

  • §3 B-tier — classify marisa and simdjson errors (build/load/parse) instead of collapsing to 2001, sub-code in the message; simdjson optional-access (NO_SUCH_FIELD/INCORRECT_TYPE) stays a benign skip; the loon_ffi FFI boundary is untouched.

  • Boundary hardening (adversarial self-review of this PR's own diff) — closed the escapes that would defeat the mapping above: a throw e; slicing rethrow in LoadWithStrategy that destroyed the very codes the columnar-read mapping attaches (bare throw; now), the same slice in MinioChunkManager::PreCheck; GetCoreMetrics / EstimateLoadIndexResource / init-and-config entry points that could let an exception cross the C ABI and terminate the process; and every remaining extern-C entry that caught only std::exception now ends in catch(...) via the shared CGoCatch.h macros.

  • Pin + semantics — bump milvus-storage_VERSION to 11f8a36 (the enhance: expose retryable storage errors through extend status and C FFI milvus-storage#574 merge, which also contains Add AVX flags when building faiss #575) and align the no-detail IOError expectation with the settled semantics: the producer tags every known-transient failure with a retryable ExtendStatusDetail, so a bare IOError with no detail is unclassified and deliberately falls back to permanent StorageError(2044) — a stripped-detail NotFound now degrades to non-retriable (safe) instead of retriable (retry storm on a permanent 404).

  • Wire pass-through (client-visible) — a segcore error now reaches the client with its ORIGINAL code (2009 stays 2009, 2024 stays 2024) instead of collapsing to the ErrSegcore(2000) umbrella with the real code buried in the message. Family identity for errors.Is is preserved via inner/Unwrap; input/system/retriable classification unchanged. Guardrails: only in-band (2000-2099) codes pass through (garbage still collapses to 2000); cross-family mappings (2046 → wire 110) keep their sentinel's code. ErrSegcoreUnsupported/ErrSegcorePretendFinished move to the C++ values they represent (2001→2003, 2002→2033) — their old numbers squatted on C++ UnexpectedError/NotImplemented and would false-match under code-based errors.Is. Verified end-to-end on a live standalone (ef<k reaches the client as 2042, unsupported tokenizer as 2001); the three e2e assertions pinning the old 2000 updated.

  • Remaining code-destroying sites — the three classes that still swallowed a producer's classification before the cgo boundary are now gone from internal/core/src and internal/core/thirdparty: status-consuming AssertInfo (104 → 0, incl. ~47 arrow builder paths whose commonest failure is OOM, now retriable MemAllocateFailed instead of a permanent 2001), bare throw std::runtime_error/logic_error/bad_alloc (68 → 0 — these were not SegcoreError, so they collapsed to 2001 and falsely fired the untyped-exception observer), and throw fmt::format(...) (12 → 0 — it throws a std::string, which catch (std::exception&) cannot see at all). tantivy's 73 AssertInfo(res.result_->success, ...) (plus 10 raw-RustResult stragglers found later) now classify the rust error — originally by its Display prefix, since replaced by a proper #[repr(i32)] discriminant carried in RustResult.error_code (see the Aug-10 update below). Typed ThrowInfo sites: 894 → 1081. The ~1500 genuine invariant asserts are untouched — 2001 is correct for them. The long-standing FIXME about err_code not surviving the nested LOON FFI boundary is also resolved, delegating to milvus_storage::ToSegcoreErrorCode rather than duplicating its table.

Verification

Verified in this PR:

  • Mapping correctness (unit-tested, in-process): test_knowhere_status_mapping.cpp / test_storage_error_code.cpp / test_exec.cpp cover every mapper branch (knowhere Status incl. the build variant, arrow/extend status incl. AwsErrorNotFound→ObjectNotExist(2017), permanent-S3 vs transient), plus FailureCStatus code preservation and both observer hooks firing.
  • Code projection to Go (one hop, unit-tested): segcore_test.go pins classForCode for every generated code and asserts merr.Status(err).GetRetriable() for transient codes; the T6 generator is idempotent and the exhaustive lint fails on an unclassified code.
  • Full C++ suite: 8213/8223 unit tests pass locally (10 skipped; Azure connectivity tests excluded), 8648 in CI, rebased on current master (one pre-existing, unrelated concurrency test excluded: GrowingConcurrentReopenTest deadlocks deterministically on current master with or without this PR — rwlock writer starvation in growing-segment reopen code this PR does not touch; reported separately).
  • Static audit (grep-verifiable): every storage arrow-status consumption site on the read path routes through ArrowStatusToErrorCode, and every extern-C boundary ends in a catch(...) tail.

Explicitly NOT verified here (follow-up):

  • Runtime fault injection. No S3 throttle / 404 / OOM / corrupt-file failure has been triggered end-to-end in a running cluster. Transient codes reach Go with retriable=true (unit-tested projection), but the downstream consumption — lb_policy replica reroute on merr.IsRetryableErr, index/analyze scheduler retry — is pre-existing logic from enhance: standardize error handling on merr + Sys/Input classification #50221 and has not been driven by a real segcore transient error in this PR. This PR preserves classification for observability and correct retry defaults; the retry behavior itself is exercised only by its own pre-existing tests.

Dependencies

Update (Aug 10) — full-population audit, LOON path, runtime observability

The originally deferred FFI/LOON path is now done on the milvus side, and the audit was extended from the three grep-able classes to the entire 2001-producing population:

  • Every remaining 2001 site read. All 1,517 AssertInfo (four sweeps: errno fingerprint, failure-keyword messages, condition morphology, and finally data provenance — does the guarded value come from disk/network?) and all 198 explicit ThrowInfo(UnexpectedError) sites. ~290 were externally-triggerable and now carry typed codes: file/remote IO -> FileOpen/Create/Read/WriteFailed (retriable), mmap/allocation -> MmapError/MemAllocateFailed (retriable), persisted-format damage (CRC/magic/parquet meta/index-meta keys) -> DataFormatBroken, deployment config -> ConfigInvalid, request content -> InvalidParameter, a cancel-race -> FollyCancel. The ~1,400 kept sites are genuine invariants or cgo contracts where 2001 is the correct report.
  • Two infinite-retry bugs. Statically-impossible conditions (index_type x metric blacklist, per-type metric allowlists, json/geometry index gates) threw 2001 -> generic retry -> the build task spun forever; they now throw Unsupported, which getStateFromError maps to a terminal JobStateFailed. Missing index_type/metric_type/min_gram/max_gram keys in persisted index meta had the same loop on the load path; they are DataFormatBroken now.
  • knowhere expected<> bypasses closed (8 sites in QueryResult.h/CachedSearchIterator): iterator failures went through AssertInfo and discarded the Status knowhere had already classified; they now route through KnowhereStatusToErrorCode, so an OOM/disk failure during search iteration stays retriable. Preflight rewraps in segment_c/boost_score similarly preserved the original SegcoreError code instead of flattening to 2001+string.
  • tantivy discriminant over the FFI. RustResult now carries error_code (#[repr(i32)] TantivyBindingErrorCode, cbindgen-exported); the C++ mapper switches on the enum instead of parsing the Display text, and the inner tantivy::TantivyError is discriminated too (IoError/Open*Error -> Io/retriable, DataCorruption/IncompatibleIndex -> DataCorruption). Wording changes on the rust side can no longer silently degrade classification.
  • LOON / FFI path (the deferred item), milvus side complete. The Go funnel HandleLoonFFIResult dropped err_code entirely and wrapped every failure as ErrLoonTransient — a 404/access-denied/corrupt-data retried as transient. It now classifies by the producer's own loon_ffi_is_retryable_errcode; permanent failures carry the new ErrLoonPermanent and terminate retry loops (pack_writer_v3 via retry.Unrecoverable; the external-refresh manager guard extended so behavior does not invert). On the C++ side LoonErrCodeToErrorCode is the single classification entry (low band -> hand table, extend band -> producer's ToSegcoreErrorCode, unknown -> producer's retryable probe), unifying the two previously-divergent ThrowIfFFIError helpers — LOON_FILE_NOT_FOUND(12) now converges to ObjectNotExist(2017) on both integration paths. Remaining LOON items (e.g. promoting FileNotFound into ExtendStatusCode) live in the milvus-storage repo.
  • Regression guards. scripts/check_segcore_error_boundaries.sh wired into make static-check: every throw in internal/core/src must carry a milvus ErrorCode (zero-tolerance; currently 0 violations); vendored fmindex:: is confined to its boundary files; knowhere/arrow/milvus_storage/tantivy are ratcheted by a checked-in file-set baseline (new consumer files fail the check; shrinking is free).
  • Runtime observability for what is left. milvus_cgo_unexpected_segcore_origin_total{origin="<file>:<line>"} counts every 2001 crossing the cgo boundary by its C++ source location (parsed from the at file:line suffix AssertInfo already emits, build paths collapsed to repo-relative). A site that fires in production names itself — reclassification becomes evidence-driven instead of re-reading ~1,400 asserts.

Site count for the 2001 family: 1,955 on master -> 1,525 on this branch; the delta is reclassification into actionable codes, not deletion of checks.

Deferred

  • milvus-storage-side LOON improvements: promote LOON_FILE_NOT_FOUND into ExtendStatusCode, category byte (design §4.7) — tracked in the storage repo.
  • knowhere-side: thin-delegate KnowhereStatusToErrorCode to knowhere's own ToSegcoreErrorCode, gated on a knowhere version bump.

issue: #50903

@sre-ci-robot sre-ci-robot added do-not-merge/work-in-progress Don't merge even CI passed. area/compilation area/test sig/testing size/XL Denotes a PR that changes 500-999 lines. approved labels Jun 25, 2026
@mergify mergify Bot added dco-passed DCO check passed. kind/enhancement Issues or changes related to enhancement labels Jun 25, 2026
@czs007

czs007 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

Re: the LoonResultToErrorCode build break (raised by @xiaocai)

Confirmed, and this is expected for the current draft state of this PR.

The break is real: this PR consumes LOON_IO_ERROR / LOON_DATA_ERROR, which are not defined in the pinned milvus-storage (df61720). They are added by milvus-io/milvus-storage#568. This PR is intentionally a draft blocked on #568 (see the banner at the top of the description) for exactly this reason.

On the two suggested fixes:

  • Bumping the pin (your first suggestion) is the intended resolution. Once [skip ci] (shards) format logging #568 merges I'll bump milvus-storage_VERSION in internal/core/thirdparty/milvus-storage/CMakeLists.txt:17 to its merge commit and CI will go green. Re: confirming result.err_code returns these — yes: [skip ci] (shards) format logging #568's ArrowStatusToLoonCode returns LOON_IO_ERROR / LOON_DATA_ERROR, and CreateFFIResult(code, ...) writes code into result.err_code.
  • Dropping the two cases would defeat the purpose of the change. Folding IO failures back into LOON_ARROW_ERROR → UnexpectedError(2001) loses their retriability (the main goal here), and routing data errors to default → UnexpectedError loses the permanent-vs-transient distinction. So I'd prefer to keep the cases and resolve via the pin bump.

Holding this PR as a draft until #568 lands.

@czs007

czs007 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator Author

Re: retry behavior change in lb_policy.go (raised by @yezh)

Confirmed — it's intended, bounded, and acceptable. One correction to the persistent-failure example.

  • Bounded: retryTimes = len(shardLeaders) + max(retryOnReplica, 1) (lb_policy.go:356) — a fixed extra count, not amplification.
  • Input errors still fast-fail: merr.GetErrorType(err) == InputError aborts immediately at :335 without retrying or touching the blacklist.
  • Correction on "real disk corruption": that surfaces as arrow Invalid → DataFormatBroken (2024), which is not marked retriable (segcore.go:142), so it still fast-fails / blacklists exactly as before. Only transient IO (FileReadFailed 2014) and OOM (MemAllocateFailed 2034) enter the retriable branch — i.e. precisely the cases where a replica reroute can plausibly succeed.
  • All-replica OOM: yes, this now takes up to retryTimes rounds before failing instead of failing fast. That's acceptable: OOM is genuinely transient (memory pressure may ease, another replica may have headroom), the request was going to fail regardless, and the extra cost is bounded.

Worth noting the prior behavior was arguably worse here: these transient failures collapsed to UnexpectedError (2001) and blacklisted the serving node — a persistent penalty on a healthy node for a transient blip. This PR stops that; a transient failure now only excludes the replica for the current request.

@sre-ci-robot sre-ci-robot added size/XXL Denotes a PR that changes 1000+ lines. and removed size/XL Denotes a PR that changes 500-999 lines. labels Jul 1, 2026
@czs007 czs007 changed the title enhance: preserve precise segcore error codes from knowhere/storage to the cgo boundary enhance: classify segcore errors across producers and enforce classification end-to-end Jul 1, 2026
@czs007
czs007 force-pushed the error_code_segcore branch from eed0a1b to 60abe54 Compare July 1, 2026 07:50
jiaqizho added a commit to jiaqizho/milvus-storage that referenced this pull request Jul 7, 2026
…milvus-io#575)

## What

Add the producer-owned mapping `ToSegcoreErrorCode` / `ToSegcoreError`
so milvus-storage classifies its own packed failures once, at the
source, and milvus can consume a typed segcore `ErrorCode` instead of a
generic Arrow status text.

> Note: this **builds on milvus-io#572** (jiaqizho — packed `ExtendStatusCode` +
`WrapExtendError`), which is not yet merged, so this branch currently
includes those commits. Once milvus-io#572 lands this PR should be rebased onto
it so it only carries the mapping delta. Happy to coordinate.

## Mapping (no-`default` switch + `-Werror=switch`)

| ExtendStatusCode | segcore ErrorCode | retry |
|---|---|---|
| PackedInvalidArgs | InvalidParameter (2042) | non-retriable (input) |
| PackedStorageIO | **StorageTransientError (2045)** | **retriable** |
| PackedMetadataCorrupted / PackedFileCorrupted | DataFormatBroken
(2024) | permanent |
| PackedArrowError / PackedUnexpected / AWS / Txn | StorageError (2044)
| permanent |

The retriable verdict is load-bearing: a transient storage IO failure
maps to the retriable `StorageTransientError(2045)` and must never
collapse into the non-retriable `StorageError(2044)`. The switch has no
`default` + a post-switch fallback under `-Werror=switch`, so a new
`ExtendStatusCode` breaks the build until classified. The no-detail path
keeps a coarse arrow mapping (IO→transient, OOM→mem-allocate,
Invalid/Type/Key→corruption).

## Dependencies

Depends on `milvus-common` `StorageTransientError(2045)`:
zilliztech/milvus-common#102. Bump the `milvus-common` conan pin to the
published 2045 revision before merge (the pin here points at a local
build for verification).

Design + tracking: milvus-io/milvus#50903. Consumed by
milvus-io/milvus#50768.

---------

Signed-off-by: zhenshan.cao <zhenshan.cao@zilliz.com>
Signed-off-by: jiaqizho <jiaqi.zhou@zilliz.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: jiaqizho <jiaqi.zhou@zilliz.com>
jiaqizho added a commit to jiaqizho/milvus-storage that referenced this pull request Jul 7, 2026
…milvus-io#575)

Add the producer-owned mapping `ToSegcoreErrorCode` / `ToSegcoreError`
so milvus-storage classifies its own packed failures once, at the
source, and milvus can consume a typed segcore `ErrorCode` instead of a
generic Arrow status text.

> Note: this **builds on milvus-io#572** (jiaqizho — packed `ExtendStatusCode` +
`WrapExtendError`), which is not yet merged, so this branch currently
includes those commits. Once milvus-io#572 lands this PR should be rebased onto
it so it only carries the mapping delta. Happy to coordinate.

| ExtendStatusCode | segcore ErrorCode | retry |
|---|---|---|
| PackedInvalidArgs | InvalidParameter (2042) | non-retriable (input) |
| PackedStorageIO | **StorageTransientError (2045)** | **retriable** |
| PackedMetadataCorrupted / PackedFileCorrupted | DataFormatBroken
(2024) | permanent |
| PackedArrowError / PackedUnexpected / AWS / Txn | StorageError (2044)
| permanent |

The retriable verdict is load-bearing: a transient storage IO failure
maps to the retriable `StorageTransientError(2045)` and must never
collapse into the non-retriable `StorageError(2044)`. The switch has no
`default` + a post-switch fallback under `-Werror=switch`, so a new
`ExtendStatusCode` breaks the build until classified. The no-detail path
keeps a coarse arrow mapping (IO→transient, OOM→mem-allocate,
Invalid/Type/Key→corruption).

Depends on `milvus-common` `StorageTransientError(2045)`:
zilliztech/milvus-common#102. Bump the `milvus-common` conan pin to the
published 2045 revision before merge (the pin here points at a local
build for verification).

Design + tracking: milvus-io/milvus#50903. Consumed by
milvus-io/milvus#50768.

---------

Signed-off-by: zhenshan.cao <zhenshan.cao@zilliz.com>
Signed-off-by: jiaqizho <jiaqi.zhou@zilliz.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: jiaqizho <jiaqi.zhou@zilliz.com>
(cherry picked from commit 9c28242)
jiaqizho added a commit to jiaqizho/milvus-storage that referenced this pull request Jul 7, 2026
…milvus-io#575)

Add the producer-owned mapping `ToSegcoreErrorCode` / `ToSegcoreError`
so milvus-storage classifies its own packed failures once, at the
source, and milvus can consume a typed segcore `ErrorCode` instead of a
generic Arrow status text.

> Note: this **builds on milvus-io#572** (jiaqizho — packed `ExtendStatusCode` +
`WrapExtendError`), which is not yet merged, so this branch currently
includes those commits. Once milvus-io#572 lands this PR should be rebased onto
it so it only carries the mapping delta. Happy to coordinate.

| ExtendStatusCode | segcore ErrorCode | retry |
|---|---|---|
| PackedInvalidArgs | InvalidParameter (2042) | non-retriable (input) |
| PackedStorageIO | **StorageTransientError (2045)** | **retriable** |
| PackedMetadataCorrupted / PackedFileCorrupted | DataFormatBroken
(2024) | permanent |
| PackedArrowError / PackedUnexpected / AWS / Txn | StorageError (2044)
| permanent |

The retriable verdict is load-bearing: a transient storage IO failure
maps to the retriable `StorageTransientError(2045)` and must never
collapse into the non-retriable `StorageError(2044)`. The switch has no
`default` + a post-switch fallback under `-Werror=switch`, so a new
`ExtendStatusCode` breaks the build until classified. The no-detail path
keeps a coarse arrow mapping (IO→transient, OOM→mem-allocate,
Invalid/Type/Key→corruption).

Depends on `milvus-common` `StorageTransientError(2045)`:
zilliztech/milvus-common#102. Bump the `milvus-common` conan pin to the
published 2045 revision before merge (the pin here points at a local
build for verification).

Design + tracking: milvus-io/milvus#50903. Consumed by
milvus-io/milvus#50768.

---------

Signed-off-by: zhenshan.cao <zhenshan.cao@zilliz.com>
Signed-off-by: jiaqizho <jiaqi.zhou@zilliz.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: jiaqizho <jiaqi.zhou@zilliz.com>
(cherry picked from commit 9c28242)
Signed-off-by: jiaqizho <jiaqi.zhou@zilliz.com>

@xiaofan-luan xiaofan-luan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes. I verified each of the four findings below against the current PR head, plus the merged milvus-storage#575 and milvus-common#102. All four reproduce; file:line references are corrected to the current tree. P1-2 and P1-3 mean the PR cannot reach green CI once the dependency pins are bumped, so I'd hold merge until they're resolved.


P1 — DataTypeInvalid / FieldIDInvalid / FieldAlreadyExist classified as InputError, but they carry mixed semantics

pkg/util/merr/segcore.go classForCode maps CodeDataTypeInvalid(2007), CodeFieldIDInvalid(2020), CodeFieldAlreadyExist(2021) to inputError: true unconditionally. A producer audit shows these are predominantly or exclusively thrown for internal conditions:

  • FieldIDInvalid(2020): 5 producer sites, all internal (load-order guards in load_field_data_c.cpp, "unsupported system field id" in SegmentInterface.cpp:725) — zero user-input sites.
  • FieldAlreadyExist(2021): 2 sites, both internalinternal/core/src/segcore/load_field_data_c.cpp:65 "append same field info multi times", internal/core/src/common/GroupChunk.h:51 "Field {} already exists in GroupChunk".
  • DataTypeInvalid(2007): ~90 production sites, ~90% internal default: / "logical error" guards (e.g. internal/core/src/segcore/SegmentGrowingImpl.cpp:1732 ThrowInfo(DataTypeInvalid, "logical error")); only ~7 are genuine request validation (JSON cast-type params, aggregation-over-type, search/metric mismatch).

Because they're InputError, internal/proxy/shardclient/lb_policy.go:334 aborts the cross-replica sweep:

if merr.GetErrorType(err) == merr.InputError {
    return false, err
}

So an internal/transient failure on one replica that surfaces as 2007/2020/2021 is never rerouted to a healthy replica — an availability regression.

Recommendation: default 2020 and 2021 to a non-retriable system error (they have no user-input producers at all); for 2007, either default to system error or apply WrapErrAsInputErrorWhen only at the ~7 boundaries where the value provably came from the request. Better still, split the mixed-semantics codes at the C++ source.


P1 — Plain Arrow IOError: test/comment claim retriable StorageTransientError(2045), but the delegated (merged) storage mapper returns permanent StorageError(2044)

internal/core/unittest/test_storage_error_code.cpp:54-55 asserts ArrowStatusToErrorCode(arrow::Status::IOError(...)) == StorageTransientError(2045) (retriable), and internal/core/src/storage/StatusToErrorCode.h comments the same. But ArrowStatusToErrorCode is a pure delegate to milvus_storage::ToSegcoreError, and the merged milvus-storage#575 (9c28242) maps it the other way, in the no-detail path:

// cpp/src/common/extend_status.cpp:203-204
} else if (status.IsIOError()) {
    code = milvus::StorageError;  // 2044, non-retriable
}

This is already an internal contradiction in this PR — the test is green only because CI still pins a pre-#575 storage build; bumping the pin to the merged revision breaks ArrowStatusToErrorCodeMapping. Net effect: the 25 plain-arrow sites stop collapsing to UnexpectedError(2001) (a real win), but plain IO lands on non-retriable 2044, so the T3/T4 retriable benefit for the live plain-arrow read path is over-stated. (#575 deliberately moved the transient/permanent split upstream into ExtendStatusDetail tags, so the no-detail path is always 2044 — there is no retriable-IO subset left here.)

Recommendation: #575's author already settled on plain-IO = permanent (the S3 SDK retry budget is already spent by the time the error propagates). So align this PR to that: change test_storage_error_code.cpp:55 to expect StorageError, fix the StatusToErrorCode.h comment, and adjust the PR description to "classification preserved; plain IO stays permanent (2044)." (If plain IO should be retriable, that's a change to #575, not here.)


P1 — RegisterUntypedCgoExceptionObserver is called but defined nowhere → link error

internal/core/src/segcore/segcore_init_c.cpp (SegcoreInit, hunk @@ -34,6 +36,17 @@) calls milvus::RegisterUntypedCgoExceptionObserver(...), but the symbol is not defined anywhere in this PR (only 3 call sites — 1 prod + 2 in test_storage_error_code.cpp), not in milvus-common master, and not in the merged milvus-common#102 (which only adds StorageTransientError = 2045). The ## Dependencies section doesn't list it and the conan pin isn't bumped in this diff. As it stands this fails to link (undefined reference) when building the segcore lib / all_tests, even after the #102/#575 pin bumps.

Recommendation: land the milvus-common side first (the declaration + definition, and its invocation from FailureCStatus) and add it to ## Dependencies; or provide the definition within this PR.


P1 — T6 drift-proofing is not a closed CI loop

generate-segcore-codes (Makefile) is a manual-only target — it is not wired into verifiers, static-check, or any workflow. The exhaustive lint and TestSegcoreCodeTableCoverage both run against the committed snapshot pkg/util/merr/segcore_codes_gen.go. So if a milvus-common pin bump adds an ErrorCode but the file isn't regenerated, the const set is unchanged, the classForCode switch is still "exhaustive", and lint + tests pass — the .golangci.yml / segcore.go comments promising "a new C++ code cannot ship unclassified" don't hold at the point that actually matters (the pin bump).

Recommendation: this repo already has the exact pattern — .github/workflows/code-checker.yaml:63 runs make check-proto-product, which regenerates and git diff --exit-codes the .pb.go files. Add an analogous check-segcore-codes-product: generate-segcore-codes that runs git diff --exit-code -- pkg/util/merr/segcore_codes_gen.go, and chain it into that same CI step. Until the loop is wired, please soften the two in-code guarantee comments. (Runtime blast radius is contained — an unmapped code degrades to non-retriable ErrSegcore and fires the observer — so this is P1 on the false in-code guarantee, not a P0.)


Overall: solid direction (producer-owns-classification + compile-time exhaustiveness is the right shape), but P1-2/P1-3 block a green build after the pin bumps and P1-1 is a live retry-correctness regression. Happy to pair on any of these.

@czs007

czs007 commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author

@xiaofan-luan Thanks for the thorough pass — all four findings verified and addressed. Point-by-point (note your review raced my rebase push, so two were already fixed in flight):

P1-1 (mixed-semantics InputError → lb_policy stops rerouting): fixed in b141748. Re-ran the producer audit and it matches yours: 2020 (4 sites) and 2021 (2 sites) have zero user-input producers; 2007 is overwhelmingly internal default:/"logical error" guards. All three now default to plain non-retriable system errors (cross-replica sweep keeps going), with a comment documenting the audit and a regression test (mixed_semantics_codes_stay_system) locking them as SystemError so re-classification has to be a conscious decision. The handful of genuine request-validation sites for 2007 can be tagged at the request boundary as a follow-up.

P1-2 (plain Arrow IOError 2045 vs merged #575's 2044): already fixed in dd43e1b-lineage (now 9330aef after rebase) — the test expects StorageError(2044), StatusToErrorCode.h's comment now documents the settled semantics (producer tags known-transients with a retryable ExtendStatusDetail; bare no-detail IOError = unclassified → conservative permanent), and the PR description's Verification section states "classification preserved; no-detail IO stays permanent". Verified against the merged pin: the branch is rebased onto master's e658197 storage bump and the full unittest suite is green with it.

P1-3 (RegisterUntypedCgoExceptionObserver undefined): the definition is zilliztech/milvus-common#112 (declaration + definition + the FailureCStatus invocation), now listed in ## Dependencies. The conan pin moves to the official package once it merges — exactly your option A; the PR stays draft until then.

P1-4 (T6 not a closed CI loop): fixed in b141748 — added check-segcore-codes-product (regenerate + git diff --exit-code on segcore_codes_gen.go, mirroring check-proto-product) and chained it into the code-checker workflow step you pointed at. Verified locally: the generator resolves the pinned header and exits 0 on no drift.

One related note from the rebase: master's kCollectionSchemaVersionNotReady (2046) is minted in ChunkedSegmentSealedImpl.cpp outside milvus-common's ErrorCode enum, so the generator can't see it — it's classified via an explicit documented special-case next to the exhaustive switch, and folding it into EasyAssert.h is flagged as a follow-up. It's a live specimen of exactly the drift P1-4's gate now catches.

@xiaofan-luan xiaofan-luan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two additional P1 classification issues found in the current error buckets.

Comment thread pkg/util/merr/segcore.go Outdated
2003: {sentinel: ErrSegcoreUnsupported},
2033: {sentinel: ErrSegcorePretendFinished, signal: true},
// Caller-input errors -> InputError (non-retriable by construction).
case CodeOpTypeInvalid,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep mixed-semantics native codes as SystemError

OpTypeInvalid(2022) and DataIsEmpty(2023) are not unambiguous caller-input errors. For example, PlanProto.cpp throws OpTypeInvalid while decoding an internally generated plan, where an unknown enum can indicate an internal protocol or rolling-upgrade mismatch; index builders throw DataIsEmpty when an internally scheduled build task yields zero rows. Globally setting inputError: true makes these failures reach lb_policy as InputError, which immediately aborts the cross-replica sweep. Please classify both codes as non-retriable SystemError by default. A boundary that can prove the value came directly from the user may explicitly mark that specific error as input, but the global table should remain system-classified.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3fe5bae. Producer audit confirms both: 2022 is thrown from PlanProto/expr op switches decoding internally generated plans, 2023 from index builders on internally scheduled zero/null-row builds. Both moved to the mixed-semantics system group (comment documents the audit) and the mixed_semantics_codes_stay_system regression test now locks all five codes (2007/2020/2021/2022/2023).

Comment thread internal/core/src/common/Utils.h Outdated
case knowhere::Status::out_of_range_in_json:
case knowhere::Status::type_conflict_in_json:
case knowhere::Status::invalid_metric_type:
case knowhere::Status::empty_index:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep index-state Knowhere statuses as SystemError

The pinned Knowhere producers use empty_index, index_not_trained, and index_already_trained for server-side index lifecycle/state failures, while invalid_binary_set is primarily produced when deserializing a persisted index. Mapping them all to InvalidParameter(2042) incorrectly blames the request and causes lb_policy to stop replica failover. Please map the three lifecycle statuses to a non-retriable SystemError bucket (for example KnowhereError) and map invalid_binary_set to DataFormatBroken; both mappings should remain system-classified.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3fe5bae. Verified against the pinned knowhere producers (empty_index = "DiskANN not loaded" — pure server-side index state): empty_index/index_not_trained/index_already_trainedKnowhereError (system, non-retriable), invalid_binary_setDataFormatBroken. The mapping test pins all four so a future regrouping is explicit.

@xiaofan-luan xiaofan-luan left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One additional P1 was found in the StorageV2 manifest error paths.

index,
chunk_reader_result.status().ToString());
if (!chunk_reader_result.ok()) {
ThrowInfo(milvus::storage::ArrowStatusToErrorCode(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve structured StorageV2 errors on every manifest path

This overload correctly uses ArrowStatusToErrorCode, but the sibling LoadColumnGroup overload at lines 8380–8386 still checks get_chunk_reader with AssertInfo. In addition, ManifestGroupTranslator.cpp:133–145 converts failures from get_chunk_size and get_chunk_rows into std::runtime_error. Those paths erase the structured Arrow/storage status before it reaches the C ABI catch, so transient storage errors such as 2045, OOM, and corruption classifications collapse to UnexpectedError(2001). Please route all three failures through ThrowInfo(milvus::storage::ArrowStatusToErrorCode(status), ...), consistent with this overload.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3fe5bae. Both the sibling LoadColumnGroup overload (AssertInfo → ThrowInfo(ArrowStatusToErrorCode(...))) and the two ManifestGroupTranslator sites (bare std::runtime_error → same). These came in with the rebase onto current master and the earlier sweep missed them — thanks for the catch. error_code suites 10/10 after a full rebuild.

@czs007
czs007 force-pushed the error_code_segcore branch from 3fe5bae to ccdeb73 Compare July 28, 2026 05:08
auto metadata = field->metadata();
if (metadata->Contains(DIM_KEY)) {
auto dim_str = metadata->Get(DIM_KEY).ValueOrDie();
dim_ = std::stoi(dim_str);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new try/catch that wraps PayloadReader::init (lines 57-215) was added to classify malformed-file exceptions as DataFormatBroken instead of collapsing to 2001, but std::stoi on the stored dim/elementType metadata (lines 115, 165, 172) throws std::invalid_argument/std::out_of_range, which is not a parquet::ParquetException — so a corrupt dim string escapes the sole catch (line 215) and still surfaces as UnexpectedError(2001) at the cgo boundary. Widen the catch (or wrap the stoi) so a malformed dim maps to DataFormatBroken like the rest of the block. Impact is limited to error labeling/observability since both 2001 and 2024 are permanent, non-retriable codes; framing it as unrelated/pre-existing understates that the PR now owns this try/catch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9be7068 — the three bare std::stoi calls now go through a ParseMetadataInt helper that classifies a malformed dim/elementType as DataFormatBroken; the catch itself stays narrow so unrelated failures in the 160-line block are not mislabeled as data corruption.

Also swept the rest of the PR for the same class (narrow catches whose try block can throw uncovered types, std parser additions, callee-internal parsers on the covered read paths): the eight marisa catches each wrap a single trie call, the two remaining parquet catches wrap only parquet/arrow calls, and no other std parser exists in the diff — this was the only instance. Thanks for the catch.

Zack and others added 18 commits August 12, 2026 16:07
Rebasing onto master brought three new status-consuming AssertInfo calls
(ChunkedSegmentSealedImpl .cpp/.h, JsonKeyStats) that swallow the
arrow/storage classification the same way the ones this PR already
converted did; they now route through ArrowStatusToErrorCode.

Two boundary fixes on top:

- CGO_CATCH_AND_RETURN_CSTATUS gains a std::bad_alloc arm ahead of
  std::exception. master had added that mapping by hand at the sites this
  PR replaced with the macro, and it is worth keeping everywhere: a
  bad_alloc is not a SegcoreError, so FailureCStatus labelled an
  out-of-memory failure UnexpectedError(2001) -- permanent -- when it is
  retriable.

- FMIndex.cpp gets a catch(...) tail. fm-index-lite is a vendored library
  with its own LICENSE/NOTICE and no dependency on milvus error codes, so
  its bare throws stay where they are and this Ring-2 consumer boundary is
  where they get classified; without catch(...) a non-std exception from
  it would escape untyped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
FMIndex::LoadEntries called into the vendored library with no classifying
catch, while the sibling build path had one. The library's parse path
self-classifies every std::exception into a false return value (which
!fm_.valid() already turned into DataFormatBroken) but deliberately RETHROWS
std::bad_alloc, so an out-of-memory during index load escaped untyped and
FailureCStatus reported it as UnexpectedError(2001). 2001 is non-retriable in
merr's classForCode and MemAllocateFailed(2034) is retriable, so a transient
OOM was reported as a permanent failure and the load was never retried.

The two catch ladders are now one GuardFMIndexLibrary, parameterised by the
phase's fallback code (IndexBuildError vs DataFormatBroken). bad_alloc stays
out of that fallback on purpose. All three entry points -- Build, LoadView,
Deserialize -- go through it.

Also adds the static guard that makes this class of defect visible, wired
into make static-check:

  RULE 1  every throw in internal/core/src carries a milvus ErrorCode
          (already true: 28 typed, 3 folly control-flow, 2 rethrows, 0
          untyped), so it runs zero-tolerance rather than on a ratchet.

  RULE 2  a vendored library's symbols stay inside its declared boundary
          files. Counting throws would not have caught this bug: the throw
          was in thirdparty/, where it belongs -- the library has no
          dependency on milvus-common and is synced from a pinned upstream
          revision -- and the consumption point did hold a classifying catch,
          just not on every path. Confining the library to a small file set
          turns a per-branch obligation into a per-file one. fmindex is
          confined today (2 files).

  RULE 3  the spread-out libraries (knowhere 72 files, arrow 53,
          milvus_storage 41, tantivy 24) are ratcheted: the current file set
          is frozen in scripts/segcore_error_boundary_baseline.txt, a new
          file referencing one of them fails the check, and shrinking is
          free (UPDATE_SEGCORE_ERROR_BASELINE=1 regenerates). Growing the
          baseline is thereby a reviewed decision that the new consumer owes
          a classifying catch or status mapper on every path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
…he node

Second full sweep over internal/core after the fm-index fix, this time with
fingerprints the first pass missed:

- tantivy-wrapper.h, 10 sites (json_term_query / json_terms_query): the raw C
  RustResult API with AssertInfo(res.success) collapsed every tantivy error to
  UnexpectedError(2001), and the throw skipped free_rust_result, leaking the
  error string. The first sweep matched the wrapper fingerprint
  (res.result_->success) and never saw the raw one. All converted to
  RustResultWrapper + AssertTantivyOk (RAII free + classification).

- 5 sites of arrow::ImportSchema(schema).ValueOrDie() in packed_reader_c /
  packed_writer_c, and one unguarded batch read in ChunkWriter's
  read_single_column_batches: an error Result does not throw there --
  ValueOrDie aborts the whole process. A malformed C-ABI schema or a corrupt
  file killed the node instead of returning a classified status. Now guarded
  with ok() + ArrowStatusToErrorCode.

- DefaultValueChunkTranslator: builder->Finish().ValueOrDie(), same abort on
  allocation failure; now classified (retriable).

The rest of the sweep came back clean and is worth recording: all 45
non-test catch(...) blocks are legitimate (first-exception capture for
parallel tasks, drain-during-unwind, or logging at void cgo boundaries), the
remaining ~65 ValueOrDie sites are ok()-guarded on the value they consume
(verified variable-matched, not proximity-matched), and src has zero untyped
throws left.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
…as 2001

Third sweep, this time over the 1540 remaining AssertInfo sites with a sharper
criterion than message keywords: is the guarded condition externally
triggerable? The strerror/errno fingerprint finds the syscall wrappers, and
every one of them was reporting an environmental failure -- disk full,
permission denied, fd exhaustion, mmap failure -- as UnexpectedError(2001),
non-retriable, indistinguishable from an internal bug. 14 sites, all
converted to typed ThrowInfo with retriable codes:

  open/fdopen/create  File.h x2, IndexEntryEncryptedLocalWriter,
                      TextLobSpillover        -> FileOpenFailed/FileCreateFailed
  read                IndexEntryDirectStreamWriter,
                      IndexEntryEncryptedLocalWriter -> FileReadFailed
  write/fsync/ftruncate  RemoteInputStream x2, IndexEntryReader,
                      the V3 magic-number write -> FileWriteFailed
  mmap                ChunkTarget.cpp/.h, BitmapIndex -> MmapError

One pre-existing bug fell out of the sweep: BitmapIndex::UnmapIndexData held
AssertInfo(true, ...) -- an assert that can never fire -- where it meant to
report a munmap failure. It is a teardown path, so it now logs a warning
instead of throwing.

The ~1516 AssertInfo left after this pass guard null checks, bounds, type
switches and logic invariants; for those, firing means a Milvus bug and 2001
is the correct report. Any misjudged stragglers will surface on the 2001
metric curve post-merge and can be reclassified from their messages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
… by a full audit

Fourth sweep: a site-by-site audit of the 1517 remaining AssertInfo calls,
under two independent criteria applied to every site -- condition morphology
(null / bounds / state-machine / type-switch forms) and failure-keyword
messages. Every flagged site was read by hand; 48 guard externally
triggerable conditions and are converted to typed ThrowInfo:

  file/remote IO (open, create, read, write, short read, upload)
      IndexEntryReader x13, IndexEntryEncryptedLocalWriter x5,
      IndexFactory x3, ScalarIndex x3, StringIndexMarisa x2,
      InvertedIndexTantivy, RemoteInputStream, RemoteOutputStream,
      storage/Util, JsonKeyStats upload
          -> FileOpenFailed / FileCreateFailed / FileReadFailed /
             FileWriteFailed   (all retriable)

  persisted-format damage (V3 magic/footer/CRC, directory JSON, old binlog
  layout, parquet schema, sparse-row payload)
      IndexEntryReader x8, storage/Util, DataCodec, FieldData,
      HybridScalarIndex, JsonKeyStats, common/Utils.h x5
          -> DataFormatBroken

  mmap-backed allocation
      mmap/ChunkData.h -> MmapError (retriable)

The sparse-row checks classify as DataFormatBroken (System), not
InputError: CopyAndWrapSparseRow is fed by both the insert path and binlog
load, and blaming the request would stop lb_policy's replica sweep on a
corrupt-binlog load.

Kept as 2001 after reading them: the cachinglayer "cache is corrupted"
family (process-internal state, not persisted data), GEOS reader/writer
construction, cgo null-argument contracts, and internal state guards. The
~1400 sites whose condition and message match neither criterion remain
invariant-style asserts; stragglers will surface on the 2001 metric curve
post-merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
…lly-triggerable sites

Final pass over the audit's remaining bucket: the 559 AssertInfo sites whose
condition and message matched neither the invariant morphology nor the
failure-keyword fingerprints. Read one by one; 105 guard externally
triggerable conditions and are converted to typed ThrowInfo, 454 are genuine
invariants and keep 2001.

  persisted-format damage -> DataFormatBroken (67)
      loaded-parquet type/width mismatches (FieldData, FieldDataInterface,
      storage/Util, PayloadReader), V3 package CRC/version/size/entry-not-
      found (IndexEntryReader x13), marisa CSR sizes/version x8, index slice
      lost/len-inconsistent (index/Utils x7, VectorMemIndex x3), manifest row
      mismatches, bson terminator, jsmn token checks on stored json, missing
      tantivy index dirs

  file IO -> FileOpenFailed / FileReadFailed / FileWriteFailed, retriable (9)
      marisa/rtree open, kmeans download short-read x2, AddFile-style upload
      failures x4

  request content -> InvalidParameter, InputError (4)
      range-search radius/range_filter contradiction x2, re2 compile failure
      of a user-supplied pattern x2 -- deterministic failures where a
      cross-replica retry cannot succeed

  deployment config -> ConfigInvalid (25)
      s3/oss/cos/obs credential presence checks x20, external_source URL
      scheme/host, segcore yaml node checks

This closes the audit: all 1517 AssertInfo sites in internal/core have now
been individually read across the four sweeps; 167 total were reclassified
and every kept site is an internal invariant or cgo contract for which 2001
is the correct report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
The previous sweeps bucketed AssertInfo sites by the shape of the guarded
condition (null / bounds / type / state-machine) and read only the leftovers
by hand. That criterion is wrong on its own: `size == expected` is an
invariant when both sides are computed in-process and a corruption detector
when one side was read off disk. What decides the class is where the value
came from.

Re-filtering the ~970 shape-bucketed sites by whether they sit on a
load/deserialize path leaves 168, read one by one here. 49 are externally
triggerable:

  retriable resource failures, previously reported as permanent 2001 --
  the highest-value group in this pass:
      IndexEntryReader pwrite x2                    -> FileWriteFailed
          a full disk during index download reported as an internal bug
      MmapChunkManager block allocation x4          -> MmapError
      FileWriter aligned_alloc, arrow builder
          Append() x4 (allocation failures)         -> MemAllocateFailed
      RemoteInputStream over-read                   -> FileReadFailed

  persisted-format damage (39) -> DataFormatBroken
      V3 footer too small / zero-size directory table / encrypted slice
      overruns / decrypted-size mismatches x5, parquet dim and metadata
      (PayloadReader x3), binlog FieldData casts (Event, DiskFileManagerImpl),
      loaded-chunk row-count mismatches (SystemIndexTranslator x2),
      downloaded-file-count vs meta (MemFileManagerImpl x2), emb_list offset
      layout, arrow element-type and int32-offset overflow checks, empty
      remote file lists

  deployment config (2) -> ConfigInvalid
      cipher plugin absent for an encrypted file / V3 index

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
…ppened

Four sweeps reclassified 234 sites that were reporting external failures as
UnexpectedError(2001). What is left is ~1500 sites judged to be genuine
invariants -- but a judgement is a hypothesis, and re-reading code is a poor
way to find the ones that are wrong. A site that fires in production
falsifies its own judgement, so make it say so.

2001 is the only segcore code that does not name its failure; every other one
does. The actionable part is therefore the source location, which
AssertInfo/ThrowInfo already append to the message as " at <file>:<line>" --
the one carrier that survives the cgo boundary. classifySegcoreError now
extracts it and hands it to an observer, wired (same injection pattern as the
unmapped-code observer, since merr cannot import metrics) to:

  milvus_cgo_unexpected_segcore_origin_total{origin="internal/core/src/..."}

plus a rate-limited WARN. Series appear only when a site actually fires, so
cardinality is bounded by real failures rather than by assert count.

A site showing up is either a Milvus bug or a misclassification -- a
condition really driven by external input (corrupt file, full disk, OOM) that
should carry a specific retriable code. Both are worth acting on, which is
why the counter is per-origin instead of a single total.

Absolute build paths collapse to repo-relative so one site is one series
across CI images; a message with no parsable location is bucketed as
"unknown" rather than dropped, since a large unknown share would itself mean
the location plumbing broke.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
… parsing text

The C++ classifier for rust tantivy errors matched prefixes of the Display
text ("InvalidArgument:", "IOError:", ...). Display is a human-facing format,
not a contract: a wording change silently degrades every classification back
to UnexpectedError(2001), and nothing would notice.

The binding already has the type -- TantivyBindingError with six variants --
so the discriminant now crosses the FFI explicitly:

  rust: #[repr(i32)] TantivyBindingErrorCode { Ok, InvalidArgument, Io,
        DataCorruption, Json, Tantivy, Internal }, a RustResult.error_code
        field set by every constructor, and TantivyBindingError::code().
        The inner tantivy::TantivyError is discriminated too: IoError/
        OpenDirectoryError/OpenReadError -> Io, DataCorruption/
        IncompatibleIndex -> DataCorruption -- previously the whole
        "TantivyError:" family collapsed to 2001, so an engine-level I/O
        failure was reported permanent; now it classifies to FileReadFailed
        and stays retriable.

  c++:  TantivyErrorToErrorCode switches on the cbindgen-generated enum
        (compile-time checked); the string parser is gone. All 84
        AssertTantivyOk call sites are unchanged -- the macro reads
        result_->error_code now -- and the full Display text still flows
        into every thrown message, so diagnostics lose nothing.

The 8 from_error(e.to_string()) sites whose error really is a
TantivyBindingError now construct through from_binding_error(&e) to keep
their discriminant; the two Utf8Error sites keep the Internal default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
… as 2001

Two families of AssertInfo report conditions that can never become true by
retrying, yet their 2001 lands in the generic ErrSegcore bucket, which the
index scheduler and load path treat as retriable -- the task spins forever.

Static capability checks -> Unsupported(2003), 7 sites: the index_type x
metric_type blacklist (VectorMemIndex x2), the per-element-type metric
allowlists (index/Utils.h x3), and the json/geometry index-type gates
(IndexFactory x2). getStateFromError maps ErrSegcoreUnsupported to
JobStateFailed, so an impossible build now fails once instead of
rebuilding forever. These are unreachable through the proxy (its
allowlists cover the C++ blacklist); they fire on legacy or corrupt
persisted meta and on internal callers.

Missing keys in persisted index meta -> DataFormatBroken, 10 sites:
index_type/metric_type/min_gram/max_gram lookups in
FieldIndexInfo.IndexParams (load_index_c x2, V1SealedIndexTranslator x3,
ChunkedSegmentSealedImpl, segcore/Utils x4). These params come from meta
written at index-build time and shipped at load -- the Go side of the same
path already treats the missing key as a reachable error -- so a hole in
them is persisted-format damage, not a cgo contract violation, and
retrying the load cannot repair it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
…ified

The four AssertInfo sweeps left the explicit ThrowInfo(UnexpectedError)
population unread. All 198 production sites are now read one by one; 171
are correct (switch-defaults over plan-derived enums, internal casts and
state machines, cgo null contracts) and 27 change:

  knowhere expected<> bypasses (8): QueryResult.h and CachedSearchIterator
      asserted has_value() and threw 2001, discarding the Status knowhere
      had already classified. Both merge loops and both batch loops now
      route through KnowhereStatusToErrorCode, so an OOM or disk-read
      failure during search iteration stays retriable.

  mmap failures (9): the five mmap()-with-strerror sites in
      ScalarIndexSort/StringIndexMarisa/StringIndexSort and the four
      MmapChunkManager::Allocate consumers in mmap/ChunkData.h ->
      MmapError, retriable.

  file IO (2): a raw ::read loop -> FileReadFailed; the R-Tree meta/file
      write path -> FileWriteFailed.

  persisted-data damage (10): R-Tree load, emb-list offsets file missing,
      LOB reference size, WKB geometry construction, JsonStatsMeta
      deserialization x2, user-JSON parse during stats build x3, unknown
      file in a loaded segment -> DataFormatBroken.

  config (1): external_spec (a user-set collection property) parse failure
      -> ConfigInvalid, consistent with the other external_source checks.

  cancellation (1): the AcquireUntil cancel-race path threw 2001 with a
      "cancelled" message; it now throws FollyCancel like the
      ThrowIfCancelled call two lines above it.

  preflight rewraps (3+1): segment_c and boost_score caught the preflight
      exception, flattened it to a string and re-threw 2001 inside the
      async future, destroying the original code. They now preserve the
      SegcoreError code and keep the "preflight failed" context the
      existing tests pin; the catch-all branch re-throws the original
      exception object.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
The 2026-07-29 audit of the milvus <-> milvus-storage integration found
three classification gaps; the nested-FFI collapse (milvus_table_c
flattening every failure to LOON_GOT_EXCEPTION) was fixed earlier on this
branch. This closes the other two on the milvus side.

Go path (gap 3.1): HandleLoonFFIResult -- the single funnel for all 11
cgo-to-loon files under storagev2/packed -- dropped RustResult err_code
entirely and wrapped every failure as ErrLoonTransient, so a 404, an
access-denied and corrupt data all retried as if transient. It now reads
err_code and asks the producer's own loon_ffi_is_retryable_errcode:
transient failures keep ErrLoonTransient, everything else carries the new
ErrLoonPermanent, and the message carries the code. Consumers:

  - pack_writer_v3's classifyLoonErr needs no code change: permanent
    failures now fall through to retry.Unrecoverable, which is exactly the
    "narrow the retryable set once codes survive end-to-end" its comment
    was waiting for (comment updated);
  - external_collection_refresh_manager treats loon errors as a
    non-retriable job failure and matched only ErrLoonTransient; it now
    matches ErrLoonPermanent too, so the classification change cannot
    invert its behavior.

  The exttable test pinned the old behavior (nonexistent directory ->
  transient); it now asserts code 12 -> ErrLoonPermanent.

C++ path (gap 3.3): the two ThrowIfFFIError helpers classified the same
code differently -- milvus_table_c through LoonErrCodeToErrorCode's
low-band table, external_utils_c through ExtendStatusCodeFromInt plus the
retryable probe, so LOON_FILE_NOT_FOUND(12) surfaced as ObjectNotExist on
one path and StorageError on the other. LoonErrCodeToErrorCode is now the
single entry (low band -> hand table, extend band -> the producer's
ToSegcoreErrorCode, unknown -> the producer's retryable probe), and
external_utils_c routes through it: both paths now agree, and the
audit's divergence (jiaqizho's "converges at the segcore boundary" which
measurement showed it did not) actually converges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
…he rethrow rule, sync the wire docs

Three review findings, all applied:

ExprInvalid(2028) producer split: a full audit of the 29 production sites
shows 9 are internal invariants, not request content -- ElementFilterExpr
reaching ParseExprs (x2), the unset/unknown expr oneof (incl. rolling-
upgrade mismatch), the post-parser boolean type check, the function
arity/type guards (Empty/StartsWith/CheckVarcharOrStringType) and the
aggregate-input guards, all guaranteed by internal builders. They now throw
UnexpectedError, so a translator/executor bug is no longer blamed on the
request and lb_policy keeps failing over. Request-driven sites (regex/LIKE
syntax, div-mod-by-zero, bloom blob and field-type checks, unknown function
name) keep ExprInvalid. Regression test PlanProto.InternalExprGuards-
AreNotInputErrors pins two internal producers at UnexpectedError.

throw e; is no longer whitelisted: the lint rule matched the catch variable
name, letting the exact slicing rethrow this PR fixes elsewhere pass CI.
Only bare `throw;` passes now, and the two remaining production sites
(SegcoreConfig.cpp, futures/Future.h) are converted to bare rethrows.
Negative control verified: a `catch (std::exception&) { throw e; }` probe
fails the check.

Wire-contract docs synced: the casebook section that still documented the
old collapse-to-2000 projection now describes the pass-through mapping
(original in-band code on the wire, unknown in-band codes included,
out-of-band collapses to 2000, cross-family keeps the sentinel's code) and
carries an explicit compatibility note for the 2001->2003 / 2002->2033
sentinel renumbering. A new named_sentinel_wire_transitions test pins the
transitions and asserts the vacated numbers do not resurrect the old
sentinel identities.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
…me it

Review round 2: the classification now survives to Go, but four consumer
paths still dropped or inverted it. All four fixed:

Compaction result carries the typed failure. CompactionPlanResult gains
`common.Status fail_status`; the datanode executor fills it from
merr.Status(err) instead of reducing a failure to completeTask(nil), and
completeTask takes the state from the result so the failure survives to the
DataCoord query. All four DataCoord consumers persist the code/retriable/
reason into the task's fail_reason; the clustering task additionally
requeues a retryable failure (OOM, transient storage) through its EXISTING
retry budget -- back to pipelining on a fresh node with RetryTimes+1 --
instead of terminating. mix/L0 have no retry budget machinery; they gain
the diagnosable fail_reason, and retry wiring there is scheduler work
tracked separately.

A failed clustering submission stays pipelining. doCompact rolled the
metadata back but fell through and overwrote it as executing on the node
that had just rejected the plan; the next poll for the never-created worker
task synthesized a terminal failure, bypassing retryOnError entirely. It
now returns the original CreateCompaction error after the rollback, so the
retry budget and node reselection apply.

The two QueryNode delete paths decide by retriability, in the same
direction. DeleteBatchResponse gains per-segment `failed_statuses`
(index-aligned with failed_ids); the worker fills them from the typed
segment.Delete errors. The delegator's batch path offlines a segment only
on a permanent failure and retries the retryable subset (the request
shrinks to the failed ids); the legacy path stops burning ten attempts on a
permanent error (retry.Handle now returns merr.IsRetryableErr(err)).

UpdateSchema retries only transient failures: the retry.Do call gains
retry.RetryErr(merr.IsRetryableErr), so a permanent typed schema error no
longer consumes the bounded retry budget against the same worker.

The executor test now asserts the full chain: a SegcoreError(2034) from
Compact() reaches the stored result as a FailStatus that reconstructs to a
retryable error via merr.Error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
…boundaries

Six findings from review, all confirmed and fixed. Three of them are defects
this PR's own previous rounds introduced or failed to verify.

Delete no longer goes missing (delta_forward). When every failed segment was
classified retryable, the batch path narrowed the request and returned an
error to drive another attempt -- but once retry.Attempts(10) is spent,
conc.AwaitAll discards that error and applyDeleteBatch returns only
offlineSegments. Those segments were neither offlined nor updated, so the
delete was silently dropped and the segment kept serving deleted rows; master
offlined on the first failure, which forced a reload that reapplied it. The
still-failing ids are now offlined before giving up.

UpdateSchema retries transport failures again. merr.IsRetryableErr is an
allowlist over milvusError, and CheckRPCCall returns the raw grpc error when
the transport fails, so the predicate added last round made
connection-refused/reset/EOF abort on the first attempt -- and since
delete_node panics on an UpdateSchema error, a QueryNode restarting during a
schema change would crash the delegator's node instead of being waited out.
The predicate now stops only on a typed error explicitly classified
non-retriable.

The LOON err_code really survives the nested boundary now. milvus_table_c.cpp
is itself a C-ABI export layer whose tail funnels every exception through
RETURN_EXCEPTION, which is hardcoded to LOON_GOT_EXCEPTION(5) -- so the code
ThrowIfFFIError attached was discarded regardless, the comment claiming
otherwise was wrong, and combined with the new ErrLoonPermanent every failure
of that entry point became permanent. A LoonFFIError (deriving from
SegcoreError) now carries the original loon code and the tail returns it
unchanged. Regression test test_loon_ffi_error_passthrough.cpp drives the real
exported entry point with a missing source manifest and asserts the code is
not 5; negative-controlled by restoring the old tail, which makes it fail.

Parquet status exceptions keep their arrow code. The catch on
parquet::ParquetException also captured ParquetStatusException -- what
PARQUET_THROW_NOT_OK raises for any non-ok arrow Status, including
OutOfMemory -- and stamped DataFormatBroken on all of them, reporting a
retriable allocation failure as permanent corruption. Its status now goes
through ArrowStatusToErrorCode, the same mapper the ok() checks in that
function already use.

Pass-through errors stop repeating themselves. The relabel keeps the family
sentinel as inner so errors.Is still matches the umbrella, but both carry the
same msg, so every pass-through segcore error rendered as "...: segcore
error[segcoreCode=2028]: segcore error" in Status.Reason and Detail.
milvusError.Error() now skips exactly that shape.

A requeued clustering compaction is dropped on the worker first. Resetting
the task to pipelining without DropCompactionPlan left the DataNode's failed
entry in place; the scheduler re-pushes a pipelining task without calling
DropTaskOnWorker, so re-submitting the same planID to the same node is
rejected with ErrDuplicatedCompactionTask and the task never leaves the
pending queue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
LoonFFIError derives from SegcoreError and additionally carries the
producer's raw LOON code so milvus's own C-ABI export tail can return it
unchanged; it is a typed throw, but the guard's allowlist did not know the
name and flagged it. Negative control re-verified: an untyped
throw std::runtime_error still fails the check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
xiaofanluan and others added 2 commits August 12, 2026 19:36
Three boundaries were still letting exceptions cross the C ABI or
swallowing them into a wrong success:

1. create_token_stream: std::string(text, len)/make_unique can throw on a
   user-supplied RunAnalyzer placeholder and would rethrow straight
   through cgo, terminating the QueryNode. Returning null instead is not
   safe either (Go would call Advance() on it), so the entry point now
   returns CStatus with a CTokenStream out-param and the analyzer
   interface propagates the error to the caller.

2. EstimateLoadIndexResource: an estimation exception (including the
   DataFormatBroken missing index_type) was swallowed into a zero
   LoadResourceRequest, letting the segment pass load admission with no
   memory/disk reservation. It now returns CStatus with an out-param and
   the segment loader aborts the load on failure.

3. segcore_init_c QueryNode entry points: SegcoreSetSimdType and the
   Knowhere thread-pool setters ThrowInfo(ConfigInvalid) on bad values
   (common.simdType has no paramtable validator) and had no catch, unlike
   their IndexBuilder counterparts. They now mirror IndexBuilderInit /
   IndexBuilderSetSimdType: swallow, log loudly, keep the default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017iV265R68zUfpn34rFoTCM
Signed-off-by: xiaofanluan <xf@hjjaq.com>
FailureCStatus strdups error_msg; leaving it unfreed on a failing
estimation would let LSan kill all_tests and mask the real assertion
(the PR milvus-io#51680 failure mode).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017iV265R68zUfpn34rFoTCM
Signed-off-by: xiaofanluan <xf@hjjaq.com>
@xiaofan-luan

Copy link
Copy Markdown
Collaborator

Reviewed the boundary layer once more after the last push. Five findings — the first three are places where this PR's own contract isn't met yet, the last two are pre-existing but in the same "exception crosses the C ABI and kills the QueryNode" class this PR exists to close, so flagging them here rather than in a separate issue.

1. [P1] Analyzer FFI still hardcodes Internal, losing the discriminant this PR carries over the FFI
internal/core/thirdparty/tantivy/tantivy-binding/src/tokenizer_c.rs:23,41,67 and index_writer_text_c.rs:48

These sites convert failures via RustResult::from_error(String), which hardcodes error_code = Internal (array.rs). So a user-supplied bad analyzer_params reaches AssertTantivyOk as Internal and collapses to UnexpectedError(2001) — on the analyzer path, which is exactly where the bulk of the InvalidArgument classification in this PR lives. index_reader_c.rs already does it right via .into(); the same underlying error currently gets different codes depending on entry point. Suggest a from_binding_error_msg(&TantivyBindingError, String) (keep error.code(), allow the contextual message) and switching these four sites.

2. [P2] Four extern-C entries still catch only std::exception
internal/core/src/segcore/boost_score.cpp:277,344, segment_c.cpp:1752,1902

The catch-hardening commit message states every remaining extern-C entry that caught only std::exception now ends in catch (...), but these four were missed — a non-std exception (folly) still escapes and terminates. The two segment_c getters can just switch to CGO_CATCH_AND_RETURN_CSTATUS (also picks up the bad_alloc arm); the async boost_score preflight needs a hand-written catch (...) mirroring its existing error-future construction.

3. [P2] Delete: proto parse sits outside the try
internal/core/src/segcore/segment_c.cpp:791

make_unique<IDs>(), ParseFromArray and AssertInfo(suc, ...) run before the try, so a malformed IDs blob throws a SegcoreError straight out of a CStatus-returning function. Insert and LoadDeletedRecord parse inside their try. Fix is moving the try up three lines.

4. [P1, pre-existing] GetNumOfQueries: an empty placeholder group kills the QueryNode
internal/core/src/segcore/plan_c.cpp:121

No try/catch, and the impl is group->at(0)std::out_of_range on an empty group crosses the C ABI. ParsePlaceholderGroup returns an empty group for a valid zero-placeholder protobuf (the loop body just never runs), and the Go side calls this entry right after a successful parse. The only guard today is proxy-side nq>0 validation; the querynode itself is one malformed internal RPC away from dying. The invariant's owner is ParsePlaceholderGroup — throw InvalidParameter there when placeholders_size() == 0 (that path is inside a CStatus channel), which fixes every downstream consumer at once (AsyncSearch currently re-guards this case by hand inside its future lambda).

5. [P1, pre-existing] HasRawData: schema-evolution race throws across a bool boundary
internal/core/src/segcore/segment_c.cpp:729

ChunkedSegmentSealedImpl::HasRawData does snapshot->schema->operator[](fieldID), which AssertInfo-throws for a field not in the published schema, plus two more reachable AssertInfos ("vector index is not ready" / "interim index is not ready"); the growing impl has the same shape. Querying a just-added field before the segment's published schema catches up (the #51062/#51814 race family) terminates the process — no malformed data needed, just normal schema-evolution timing. HasFieldData right next to it handles this via field_exists_in_schema. Either return false on schema miss in the impls, or wrap the entry with CGO_CATCH_AND_LOG + return false — "no raw data" is the safe fallback (the caller just loads the field data).

Storage-side classification comments (PayloadWriter / IndexEntryReader / loon_ffi) intentionally withheld for now — those should follow the storage error-taxonomy rework rather than being settled piecemeal here.

…analyzer discriminant

Five findings from the boundary re-review, all applied.

The analyzer FFI carries its discriminant now. tokenizer_c.rs (x3) and
index_writer_text_c.rs built their failures with
RustResult::from_error(String), which hardcodes error_code = Internal -- so a
user-supplied bad analyzer_params reached AssertTantivyOk as Internal and
collapsed to UnexpectedError(2001), on the very path where most of this PR's
InvalidArgument classification lives, while index_reader_c.rs (using .into())
classified the same error correctly. Added
RustResult::from_binding_error_msg(&err, msg), which keeps error.code() while
allowing the contextual message, and switched the four sites.

Four extern-C entries caught only std::exception, contrary to what the
catch-hardening commit claimed: segment_c's two getters and boost_score's
synchronous entry now use CGO_CATCH_AND_RETURN_CSTATUS (which also adds the
bad_alloc arm), and boost_score's async preflight gets a hand-written
catch(...) mirroring its error-future construction.

Delete parsed the IDs blob before its try, so a malformed blob left the
function as a live exception across the C ABI; the parse moves inside, as
Insert and LoadDeletedRecord already do.

An empty placeholder group no longer kills the QueryNode. GetNumOfQueries is
group->at(0) behind a plain int64_t return, and a valid zero-placeholder
protobuf parses into exactly that. The invariant belongs to
ParsePlaceholderGroup, which returns through a CStatus, so the rejection
(InvalidParameter) happens there and covers every downstream consumer at once.

HasRawData absorbs the schema-evolution race. It returns a plain bool with no
channel for an error, while ChunkedSegmentSealedImpl::HasRawDataFromState
looks the field up in the PUBLISHED schema and AssertInfo-throws when the
snapshot has not caught up -- normal timing, no malformed data needed. The
entry now logs and returns false, which is the safe answer since the caller
just loads the field data. (The growing impl never consults the schema and
cannot throw here, so the sealed path is the only one that could.)

test_cabi_exception_containment.cpp covers the last two; both were
negative-controlled by reverting the fixes, which makes them fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Zack <noreply@zilliz.com>
@sre-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
To complete the pull request process, please ask for approval from czs007 after the PR has been reviewed.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@czs007

czs007 commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

All five addressed in 0e802b7 — rebased on top of your two commits (ee3e1e0, 028a1f3), which touch a disjoint file set, so nothing of yours was overwritten.

1. Analyzer FFI discriminant — added RustResult::from_binding_error_msg(&err, msg) (keeps error.code(), allows the contextual message) and switched all four sites: tokenizer_c.rs x3 and index_writer_text_c.rs. A bad analyzer_params now reaches AssertTantivyOk as InvalidArgument -> InvalidParameter instead of Internal -> 2001, matching what index_reader_c.rs already did.

2. Four extern-C entriessegment_c's two getters and boost_score's synchronous entry now use CGO_CATCH_AND_RETURN_CSTATUS (picks up the bad_alloc arm too); the async preflight got a hand-written catch (...) mirroring its error-future construction. You were right that the earlier commit message overclaimed. Swept the rest of segcore afterwards: plan_c/packed_writer_c/tokenizer_c already end in catch (...).

3. Delete parses inside the try now — moved as suggested.

4. Empty placeholder group — fixed at the invariant's owner as you proposed: ParsePlaceholderGroup throws InvalidParameter when placeholders_size() == 0, inside a CStatus channel, so every downstream consumer (including the AsyncSearch hand-guard) is covered at once.

5. HasRawData — entry now logs and returns false. One correction to the finding: only the sealed impl can throw here. SegmentGrowingImpl::HasRawData never consults the schema (it checks indexing_record_/insert_record_ and otherwise returns true), so the schema-miss throw is ChunkedSegmentSealedImpl::HasRawDataFromState only — my first regression test used a growing segment and passed vacuously until I noticed. The fix covers both anyway, and the test now uses a sealed segment.

Verification: new test_cabi_exception_containment.cpp covers 4 and 5, both negative-controlled by reverting the fixes (they fail without them); full C++ suite 8222 passed / 0 failed; Go analyzer/function/querynodev2-segments suites pass. TestQueryNode/TestBasic fails on this branch and on pristine master alike (wal_selector.go:56, "mq rocksmq is only valid in standalone mode") — pre-existing, unrelated to this PR.

Agreed on holding the storage-side classification (PayloadWriter / IndexEntryReader / loon_ffi) for the taxonomy rework.

@sre-ci-robot

Copy link
Copy Markdown
Contributor

✅ CI Loop Results 028a1f3

Stage Result Duration Tests
✅ Build SUCCESS 16.2min -
✅ Code-Check SUCCESS 7.9min -
✅ UT-Integration SUCCESS 26.0min -
✅ UT-GO SUCCESS 23.4min -
✅ UT-CPP-Cov SUCCESS 60.7min 8655 total, 8655 passed, 0 failed

Total: 85min | Pipeline | Artifacts

Overall Coverage: 74.0%
Diff Coverage: CPP 41.8% (671 hit, 934 miss, 1605 measurable lines, 2994 unmeasured) | Go 44.5% (101 hit, 126 miss, 227 measurable lines, 4572 unmeasured)
Diff Coverage HTML: view changed lines
Go Patch Warning: WARNING: Go patch coverage is partial; 4572 changed lines were unmeasured.
Total Patch Coverage: 42.1% (772/1832 measurable lines, 7566 unmeasured)

@sre-ci-robot

Copy link
Copy Markdown
Contributor

✅ CI Loop Results 0e802b7

Stage Result Duration Tests
✅ Build SUCCESS 16.0min -
✅ Code-Check SUCCESS 7.3min -
✅ UT-Integration SUCCESS 26.1min -
✅ UT-GO SUCCESS 23.7min -
✅ UT-CPP-Cov SUCCESS 61.2min 8657 total, 8657 passed, 0 failed

Total: 85min | Pipeline | Artifacts

Overall Coverage: 74.0%
Diff Coverage: CPP 41.9% (684 hit, 947 miss, 1631 measurable lines, 3116 unmeasured) | Go 44.5% (101 hit, 126 miss, 227 measurable lines, 4572 unmeasured)
Diff Coverage HTML: view changed lines
Go Patch Warning: WARNING: Go patch coverage is partial; 4572 changed lines were unmeasured.
Total Patch Coverage: 42.2% (785/1858 measurable lines, 7688 unmeasured)

@mergify mergify Bot added the ci-passed label Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compilation area/internal-api area/test ci-passed dco-passed DCO check passed. kind/enhancement Issues or changes related to enhancement sig/testing size/XXL Denotes a PR that changes 1000+ lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants