Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions include/common/EasyAssert.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,32 @@ FailureCStatus(int code, const std::string& msg) {
return CStatus{code, strdup(msg.data())};
}

// Observability hook for the cgo boundary: fired whenever FailureCStatus
// receives an exception that is NOT a SegcoreError (so its typed error code is
// lost and the failure collapses to UnexpectedError). The consumer (milvus)
// registers an observer that bumps a metric / logs; a shrinking hit rate means
// explicit error classification coverage is improving. This header stays free
// of any metrics dependency -- same observer pattern as the Go-side
// RegisterUnmappedSegcoreCodeObserver.
using UntypedCgoExceptionObserver = void (*)(const char* what);

void
RegisterUntypedCgoExceptionObserver(UntypedCgoExceptionObserver observer);

namespace impl {
// Best-effort, noexcept: runs inside FailureCStatus's conversion path, so a
// throwing observer must never replace the original failure or escape the
// cgo boundary.
void
NotifyUntypedCgoException(const char* what) noexcept;
} // namespace impl

inline CStatus
FailureCStatus(const std::exception* ex) {
if (auto segcore_err = dynamic_cast<const SegcoreError*>(ex)) {
return CStatus{static_cast<int>(segcore_err->get_error_code()), strdup(segcore_err->what())};
}
impl::NotifyUntypedCgoException(ex->what());
return CStatus{static_cast<int>(UnexpectedError), strdup(ex->what())};
}

Expand Down
7 changes: 5 additions & 2 deletions src/cachinglayer/lrucache/ListNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,11 @@ ListNode::mark_unload(std::function<void()> cb) {
// NOTE: it should not happen, but if it does, we should not make the program deadlock.
auto promise = std::move(load_promise_);
lock.unlock();
promise->setException(folly::exception_wrapper(
std::runtime_error("ListNode destroyed while loading, this should not happen")));
// Wrap a typed SegcoreError (internal invariant, not a
// cancellation): a bare runtime_error rethrown from this
// promise cannot be recognized at the cgo boundary.
promise->setException(folly::exception_wrapper(milvus::SegcoreError(
milvus::ErrorCode::UnexpectedError, "ListNode destroyed while loading, this should not happen")));
}
break;
}
Expand Down
31 changes: 31 additions & 0 deletions src/common/EasyAssert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,43 @@

#include "common/EasyAssert.h"

#include <atomic>
#include <boost/stacktrace.hpp>
#include <iostream>
#include <sstream>

#include "fmt/format.h"

namespace milvus {

namespace {
std::atomic<UntypedCgoExceptionObserver> untyped_cgo_exception_observer{nullptr};
} // namespace

void
RegisterUntypedCgoExceptionObserver(UntypedCgoExceptionObserver observer) {
untyped_cgo_exception_observer.store(observer, std::memory_order_release);
}

namespace impl {
void
NotifyUntypedCgoException(const char* what) noexcept {
if (auto observer = untyped_cgo_exception_observer.load(std::memory_order_acquire)) {
try {
observer(what);
} catch (...) {
// The observer is metrics/logging only. It runs inside
// FailureCStatus's exception-to-CStatus conversion, so a throwing
// observer must never replace the original failure or let an
// exception escape the cgo boundary (which would terminate the
// process). Swallow and carry on with the conversion.
}
}
}
} // namespace impl

} // namespace milvus

namespace milvus::impl {

std::string
Expand Down
Loading