This patch introduces throwing_assert(cond), a better and safer replacement for assert(cond) or SCYLLA_ASSERT(cond). It aims to eventually replace all assertions in Scylla and provide a real solution to issue #7871 ("exorcise assertions from Scylla"). throwing_assert() is based on the existing on_internal_error() and inherits all its benefits, but brings with it the *convenience* of assert() and SCYLLA_ASSERT(): No need for a separate if(), new strings, etc. For example, you can do write just one line of throwing_assert(): throwing_assert(p != nullptr); Instead of much more verbose on_internal_error: if (p == nullptr) { utils::on_internal_error("assertion failed: p != nullptr") } Like assert() and SCYLLA_ASSERT(), in our tests throwing_assert() dumps core on failure. But its advantage over the other assertion functions like becomes clear in production: * assert() is compiled-out in release builds. This means that the condition is not checked, and the code after the failed condition continues to run normally, potentially to disasterous consequences. In contrast, throwing_assert() continues to check the condition even in release builds, and if the condition is false it throws an exception. This ensures that the code following the condition doesn't run. * SCYLLA_ASSERT() in release builds checks the condition and *crashes* Scylla if the condition is not met. In contrast, throwing_assert() doesn't crash, but throws an exception. This means that the specific operation that encountered the error is aborted, instead of the entire server. It often also means that the user of this operation will see this error somehow and know which operation failed - instead of encountering a mysterious server (or even whole-cluster crash) without any indication which operation caused it. Another benefit of throwing_assert() is that it logs the error message (and also a backtrace!) to Scylla's usual logging mechanisms - not to stderr like assert and SCYLLA_ASSERT write, where users sometimes can't see what is written. Fixes #28308. Signed-off-by: Nadav Har'El <nyh@scylladb.com>
40 lines
2.2 KiB
C++
40 lines
2.2 KiB
C++
// Copyright 2024-present ScyllaDB
|
|
// SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
|
|
|
|
#pragma once
|
|
|
|
#include <cassert>
|
|
|
|
/// Like assert(), but independent of NDEBUG. Active in all build modes.
|
|
#define SCYLLA_ASSERT(x) do { if (!(x)) { __assert_fail(#x, __FILE__, __LINE__, __PRETTY_FUNCTION__); } } while (0)
|
|
|
|
/// throwing_assert() is like SCYLLA_ASSERT() - active in all build modes -
|
|
/// but uses __assert_fail_on_internal_error() instead of __assert_fail().
|
|
/// This gives throwing_assert() two important benefits over SCYLLA_ASSERT():
|
|
/// 1. As its name suggests, throwing_assert() throws an exception instead
|
|
/// of crashing the process on failure (except in tests, which deliberately
|
|
/// enable seastar::set_abort_on_internal_error()).
|
|
/// The exception still provides the guarantee that code won't proceed past
|
|
/// the assertion if the expression is false. But it will often kill just
|
|
/// one operation instead of the entire server, and usually this exception
|
|
/// gets reported cleanly to the end-user, who can avoid using the failing
|
|
/// operation until the bug is fixed - instead of losing the entire cluster
|
|
/// without knowing why.
|
|
/// 2. on_internal_error() logs the error to the regular log and also logs a
|
|
/// backtrace - unlike SCYLLA_ASSERT() which writes to stderr and doesn't
|
|
/// log a backtrace.
|
|
/// Notes:
|
|
/// * when throwing_assert() is used in a noexcept context, the exception
|
|
/// thrown by utils::on_internal_error() will still cause std::terminate()
|
|
/// to be called - so it behaves like SCYLLA_ASSERT() but with better
|
|
/// logging but also an uglier backtrace in the debugger.
|
|
/// * The type of exception thrown by throwing_assert() is determined by
|
|
/// seastar::on_internal_error. It is currently std::runtime_error, but this
|
|
/// is not guaranteed and should not be relied on.
|
|
#define throwing_assert(x) do { if (!(x)) { ::utils::__assert_fail_on_internal_error(#x, __FILE__, __LINE__, __PRETTY_FUNCTION__); } } while (0)
|
|
|
|
/// Used by the throwing_assert() macro to report assertion failures using
|
|
/// utils::on_internal_error().
|
|
namespace utils {
|
|
[[noreturn]] void __assert_fail_on_internal_error(const char* expr, const char* file, int line, const char* function);
|
|
} |