cpp/celeborn/utils/Exceptions.h (330 lines of code) (raw):
/*
* Based on Exceptions.h from Facebook Velox
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <memory>
#include <sstream>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <glog/logging.h>
#include <folly/Conv.h>
#include <folly/Exception.h>
#include <folly/Preprocessor.h>
#include "celeborn/utils/CelebornException.h"
// TODO: maybe remove the file permanently...
// #include "celeborn/utils/FmtStdFormatters.h"
namespace celeborn {
namespace utils {
namespace detail {
struct CelebornCheckFailArgs {
const char* file;
size_t line;
const char* function;
const char* expression;
const char* errorSource;
const char* errorCode;
bool isRetriable;
};
struct CompileTimeEmptyString {
CompileTimeEmptyString() = default;
constexpr operator const char*() const {
return "";
}
constexpr operator std::string_view() const {
return {};
}
operator std::string() const {
return {};
}
};
// celebornCheckFail is defined as a separate helper function rather than
// a macro or inline `throw` expression to allow the compiler *not* to
// inline it when it is large. Having an out-of-line error path helps
// otherwise-small functions that call error-checking macros stay
// small and thus stay eligible for inlining.
template <typename Exception, typename StringType>
[[noreturn]] void celebornCheckFail(
const CelebornCheckFailArgs& args,
StringType s) {
static_assert(
!std::is_same_v<StringType, std::string>,
"BUG: we should not pass std::string by value to celebornCheckFail");
if constexpr (!std::is_same_v<Exception, CelebornUserError>) {
LOG(ERROR) << "Line: " << args.file << ":" << args.line
<< ", Function:" << args.function
<< ", Expression: " << args.expression << " " << s
<< ", Source: " << args.errorSource
<< ", ErrorCode: " << args.errorCode;
}
++threadNumCelebornThrow();
throw Exception(
args.file,
args.line,
args.function,
args.expression,
s,
args.errorSource,
args.errorCode,
args.isRetriable);
}
// CelebornCheckFailStringType helps us pass by reference to
// celebornCheckFail exactly when the string type is std::string.
template <typename T>
struct CelebornCheckFailStringType;
template <>
struct CelebornCheckFailStringType<CompileTimeEmptyString> {
using type = CompileTimeEmptyString;
};
template <>
struct CelebornCheckFailStringType<const char*> {
using type = const char*;
};
template <>
struct CelebornCheckFailStringType<std::string> {
using type = const std::string&;
};
// Declare explicit instantiations of celebornCheckFail for the given
// exceptionType. Just like normal function declarations (prototypes),
// this allows the compiler to assume that they are defined elsewhere
// and simply insert a function call for the linker to fix up, rather
// than emitting a definition of these templates into every
// translation unit they are used in.
#define CELEBORN_DECLARE_CHECK_FAIL_TEMPLATES(exception_type) \
namespace detail { \
extern template void \
celebornCheckFail<exception_type, CompileTimeEmptyString>( \
const CelebornCheckFailArgs& args, \
CompileTimeEmptyString); \
extern template void celebornCheckFail<exception_type, const char*>( \
const CelebornCheckFailArgs& args, \
const char*); \
extern template void celebornCheckFail<exception_type, const std::string&>( \
const CelebornCheckFailArgs& args, \
const std::string&); \
} // namespace detail
// Definitions corresponding to CELEBORN_DECLARE_CHECK_FAIL_TEMPLATES. Should
// only be used in Exceptions.cpp.
#define CELEBORN_DEFINE_CHECK_FAIL_TEMPLATES(exception_type) \
template void celebornCheckFail<exception_type, CompileTimeEmptyString>( \
const CelebornCheckFailArgs& args, CompileTimeEmptyString); \
template void celebornCheckFail<exception_type, const char*>( \
const CelebornCheckFailArgs& args, const char*); \
template void celebornCheckFail<exception_type, const std::string&>( \
const CelebornCheckFailArgs& args, const std::string&);
// When there is no message passed, we can statically detect this case
// and avoid passing even a single unnecessary argument pointer,
// minimizing size and thus maximizing eligibility for inlining.
inline CompileTimeEmptyString errorMessage() {
return {};
}
inline const char* errorMessage(const char* s) {
return s;
}
inline std::string errorMessage(const std::string& str) {
return str;
}
template <typename... Args>
std::string errorMessage(fmt::string_view fmt, const Args&... args) {
return fmt::vformat(fmt, fmt::make_format_args(args...));
}
} // namespace detail
#define _CELEBORN_THROW_IMPL( \
exception, exprStr, errorSource, errorCode, isRetriable, ...) \
{ \
/* GCC 9.2.1 doesn't accept this code with constexpr. */ \
static const ::celeborn::utils::detail::CelebornCheckFailArgs \
celebornCheckFailArgs = { \
__FILE__, \
__LINE__, \
__FUNCTION__, \
exprStr, \
errorSource, \
errorCode, \
isRetriable}; \
auto message = ::celeborn::utils::detail::errorMessage(__VA_ARGS__); \
::celeborn::utils::detail::celebornCheckFail< \
exception, \
typename ::celeborn::utils::detail::CelebornCheckFailStringType< \
decltype(message)>::type>(celebornCheckFailArgs, message); \
}
#define _CELEBORN_CHECK_AND_THROW_IMPL( \
expr, exprStr, exception, errorSource, errorCode, isRetriable, ...) \
if (UNLIKELY(!(expr))) { \
_CELEBORN_THROW_IMPL( \
exception, exprStr, errorSource, errorCode, isRetriable, __VA_ARGS__); \
}
#define _CELEBORN_THROW(exception, ...) \
_CELEBORN_THROW_IMPL(exception, "", ##__VA_ARGS__)
CELEBORN_DECLARE_CHECK_FAIL_TEMPLATES(::celeborn::utils::CelebornRuntimeError);
#define _CELEBORN_CHECK_IMPL(expr, exprStr, ...) \
_CELEBORN_CHECK_AND_THROW_IMPL( \
expr, \
exprStr, \
::celeborn::utils::CelebornRuntimeError, \
::celeborn::utils::error_source::kErrorSourceRuntime.c_str(), \
::celeborn::utils::error_code::kInvalidState.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
// If the caller passes a custom message (4 *or more* arguments), we
// have to construct a format string from ours ("({} vs. {})") plus
// theirs by adding a space and shuffling arguments. If they don't (exactly 3
// arguments), we can just pass our own format string and arguments straight
// through.
#define _CELEBORN_CHECK_OP_WITH_USER_FMT_HELPER( \
implmacro, expr1, expr2, op, user_fmt, ...) \
implmacro( \
(expr1)op(expr2), \
#expr1 " " #op " " #expr2, \
"({} vs. {}) " user_fmt, \
expr1, \
expr2, \
##__VA_ARGS__)
#define _CELEBORN_CHECK_OP_HELPER(implmacro, expr1, expr2, op, ...) \
if constexpr (FOLLY_PP_DETAIL_NARGS(__VA_ARGS__) > 0) { \
_CELEBORN_CHECK_OP_WITH_USER_FMT_HELPER( \
implmacro, expr1, expr2, op, __VA_ARGS__); \
} else { \
implmacro( \
(expr1)op(expr2), \
#expr1 " " #op " " #expr2, \
"({} vs. {})", \
expr1, \
expr2); \
}
#define _CELEBORN_CHECK_OP(expr1, expr2, op, ...) \
_CELEBORN_CHECK_OP_HELPER( \
_CELEBORN_CHECK_IMPL, expr1, expr2, op, ##__VA_ARGS__)
#define _CELEBORN_USER_CHECK_IMPL(expr, exprStr, ...) \
_CELEBORN_CHECK_AND_THROW_IMPL( \
expr, \
exprStr, \
::celeborn::utils::CelebornUserError, \
::celeborn::utils::error_source::kErrorSourceUser.c_str(), \
::celeborn::utils::error_code::kInvalidArgument.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
#define _CELEBORN_USER_CHECK_OP(expr1, expr2, op, ...) \
_CELEBORN_CHECK_OP_HELPER( \
_CELEBORN_USER_CHECK_IMPL, expr1, expr2, op, ##__VA_ARGS__)
// For all below macros, an additional message can be passed using a
// format string and arguments, as with `fmt::format`.
#define CELEBORN_CHECK(expr, ...) \
_CELEBORN_CHECK_IMPL(expr, #expr, ##__VA_ARGS__)
#define CELEBORN_CHECK_GT(e1, e2, ...) \
_CELEBORN_CHECK_OP(e1, e2, >, ##__VA_ARGS__)
#define CELEBORN_CHECK_GE(e1, e2, ...) \
_CELEBORN_CHECK_OP(e1, e2, >=, ##__VA_ARGS__)
#define CELEBORN_CHECK_LT(e1, e2, ...) \
_CELEBORN_CHECK_OP(e1, e2, <, ##__VA_ARGS__)
#define CELEBORN_CHECK_LE(e1, e2, ...) \
_CELEBORN_CHECK_OP(e1, e2, <=, ##__VA_ARGS__)
#define CELEBORN_CHECK_EQ(e1, e2, ...) \
_CELEBORN_CHECK_OP(e1, e2, ==, ##__VA_ARGS__)
#define CELEBORN_CHECK_NE(e1, e2, ...) \
_CELEBORN_CHECK_OP(e1, e2, !=, ##__VA_ARGS__)
#define CELEBORN_CHECK_NULL(e, ...) CELEBORN_CHECK(e == nullptr, ##__VA_ARGS__)
#define CELEBORN_CHECK_NOT_NULL(e, ...) \
CELEBORN_CHECK(e != nullptr, ##__VA_ARGS__)
#define CELEBORN_CHECK_OK(expr) \
do { \
::celeborn::utils::Status _s = (expr); \
_CELEBORN_CHECK_IMPL(_s.ok(), #expr, _s.toString()); \
} while (false)
#define CELEBORN_UNSUPPORTED(...) \
_CELEBORN_THROW( \
::celeborn::utils::CelebornUserError, \
::celeborn::utils::error_source::kErrorSourceUser.c_str(), \
::celeborn::utils::error_code::kUnsupported.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
#define CELEBORN_ARITHMETIC_ERROR(...) \
_CELEBORN_THROW( \
::celeborn::utils::CelebornUserError, \
::celeborn::utils::error_source::kErrorSourceUser.c_str(), \
::celeborn::utils::error_code::kArithmeticError.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
#define CELEBORN_SCHEMA_MISMATCH_ERROR(...) \
_CELEBORN_THROW( \
::celeborn::utils::CelebornUserError, \
::celeborn::utils::error_source::kErrorSourceUser.c_str(), \
::celeborn::utils::error_code::kSchemaMismatch.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
#define CELEBORN_FILE_NOT_FOUND_ERROR(...) \
_CELEBORN_THROW( \
::celeborn::utils::CelebornRuntimeError, \
::celeborn::utils::error_source::kErrorSourceRuntime.c_str(), \
::celeborn::utils::error_code::kFileNotFound.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
#define CELEBORN_UNREACHABLE(...) \
_CELEBORN_THROW( \
::celeborn::utils::CelebornRuntimeError, \
::celeborn::utils::error_source::kErrorSourceRuntime.c_str(), \
::celeborn::utils::error_code::kUnreachableCode.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
#ifndef NDEBUG
#define CELEBORN_DCHECK(expr, ...) CELEBORN_CHECK(expr, ##__VA_ARGS__)
#define CELEBORN_DCHECK_GT(e1, e2, ...) CELEBORN_CHECK_GT(e1, e2, ##__VA_ARGS__)
#define CELEBORN_DCHECK_GE(e1, e2, ...) CELEBORN_CHECK_GE(e1, e2, ##__VA_ARGS__)
#define CELEBORN_DCHECK_LT(e1, e2, ...) CELEBORN_CHECK_LT(e1, e2, ##__VA_ARGS__)
#define CELEBORN_DCHECK_LE(e1, e2, ...) CELEBORN_CHECK_LE(e1, e2, ##__VA_ARGS__)
#define CELEBORN_DCHECK_EQ(e1, e2, ...) CELEBORN_CHECK_EQ(e1, e2, ##__VA_ARGS__)
#define CELEBORN_DCHECK_NE(e1, e2, ...) CELEBORN_CHECK_NE(e1, e2, ##__VA_ARGS__)
#define CELEBORN_DCHECK_NULL(e, ...) CELEBORN_CHECK_NULL(e, ##__VA_ARGS__)
#define CELEBORN_DCHECK_NOT_NULL(e, ...) \
CELEBORN_CHECK_NOT_NULL(e, ##__VA_ARGS__)
#else
#define CELEBORN_DCHECK(expr, ...) CELEBORN_CHECK(true)
#define CELEBORN_DCHECK_GT(e1, e2, ...) CELEBORN_CHECK(true)
#define CELEBORN_DCHECK_GE(e1, e2, ...) CELEBORN_CHECK(true)
#define CELEBORN_DCHECK_LT(e1, e2, ...) CELEBORN_CHECK(true)
#define CELEBORN_DCHECK_LE(e1, e2, ...) CELEBORN_CHECK(true)
#define CELEBORN_DCHECK_EQ(e1, e2, ...) CELEBORN_CHECK(true)
#define CELEBORN_DCHECK_NE(e1, e2, ...) CELEBORN_CHECK(true)
#define CELEBORN_DCHECK_NULL(e, ...) CELEBORN_CHECK(true)
#define CELEBORN_DCHECK_NOT_NULL(e, ...) CELEBORN_CHECK(true)
#endif
#define CELEBORN_FAIL(...) \
_CELEBORN_THROW( \
::celeborn::utils::CelebornRuntimeError, \
::celeborn::utils::error_source::kErrorSourceRuntime.c_str(), \
::celeborn::utils::error_code::kInvalidState.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
CELEBORN_DECLARE_CHECK_FAIL_TEMPLATES(::celeborn::utils::CelebornUserError);
// For all below macros, an additional message can be passed using a
// format string and arguments, as with `fmt::format`.
#define CELEBORN_USER_CHECK(expr, ...) \
_CELEBORN_USER_CHECK_IMPL(expr, #expr, ##__VA_ARGS__)
#define CELEBORN_USER_CHECK_GT(e1, e2, ...) \
_CELEBORN_USER_CHECK_OP(e1, e2, >, ##__VA_ARGS__)
#define CELEBORN_USER_CHECK_GE(e1, e2, ...) \
_CELEBORN_USER_CHECK_OP(e1, e2, >=, ##__VA_ARGS__)
#define CELEBORN_USER_CHECK_LT(e1, e2, ...) \
_CELEBORN_USER_CHECK_OP(e1, e2, <, ##__VA_ARGS__)
#define CELEBORN_USER_CHECK_LE(e1, e2, ...) \
_CELEBORN_USER_CHECK_OP(e1, e2, <=, ##__VA_ARGS__)
#define CELEBORN_USER_CHECK_EQ(e1, e2, ...) \
_CELEBORN_USER_CHECK_OP(e1, e2, ==, ##__VA_ARGS__)
#define CELEBORN_USER_CHECK_NE(e1, e2, ...) \
_CELEBORN_USER_CHECK_OP(e1, e2, !=, ##__VA_ARGS__)
#define CELEBORN_USER_CHECK_NULL(e, ...) \
CELEBORN_USER_CHECK(e == nullptr, ##__VA_ARGS__)
#define CELEBORN_USER_CHECK_NOT_NULL(e, ...) \
CELEBORN_USER_CHECK(e != nullptr, ##__VA_ARGS__)
#ifndef NDEBUG
#define CELEBORN_USER_DCHECK(expr, ...) CELEBORN_USER_CHECK(expr, ##__VA_ARGS__)
#define CELEBORN_USER_DCHECK_GT(e1, e2, ...) \
CELEBORN_USER_CHECK_GT(e1, e2, ##__VA_ARGS__)
#define CELEBORN_USER_DCHECK_GE(e1, e2, ...) \
CELEBORN_USER_CHECK_GE(e1, e2, ##__VA_ARGS__)
#define CELEBORN_USER_DCHECK_LT(e1, e2, ...) \
CELEBORN_USER_CHECK_LT(e1, e2, ##__VA_ARGS__)
#define CELEBORN_USER_DCHECK_LE(e1, e2, ...) \
CELEBORN_USER_CHECK_LE(e1, e2, ##__VA_ARGS__)
#define CELEBORN_USER_DCHECK_EQ(e1, e2, ...) \
CELEBORN_USER_CHECK_EQ(e1, e2, ##__VA_ARGS__)
#define CELEBORN_USER_DCHECK_NE(e1, e2, ...) \
CELEBORN_USER_CHECK_NE(e1, e2, ##__VA_ARGS__)
#define CELEBORN_USER_DCHECK_NOT_NULL(e, ...) \
CELEBORN_USER_CHECK_NOT_NULL(e, ##__VA_ARGS__)
#define CELEBORN_USER_DCHECK_NULL(e, ...) \
CELEBORN_USER_CHECK_NULL(e, ##__VA_ARGS__)
#else
#define CELEBORN_USER_DCHECK(expr, ...) CELEBORN_USER_CHECK(true)
#define CELEBORN_USER_DCHECK_GT(e1, e2, ...) CELEBORN_USER_CHECK(true)
#define CELEBORN_USER_DCHECK_GE(e1, e2, ...) CELEBORN_USER_CHECK(true)
#define CELEBORN_USER_DCHECK_LT(e1, e2, ...) CELEBORN_USER_CHECK(true)
#define CELEBORN_USER_DCHECK_LE(e1, e2, ...) CELEBORN_USER_CHECK(true)
#define CELEBORN_USER_DCHECK_EQ(e1, e2, ...) CELEBORN_USER_CHECK(true)
#define CELEBORN_USER_DCHECK_NE(e1, e2, ...) CELEBORN_USER_CHECK(true)
#define CELEBORN_USER_DCHECK_NULL(e, ...) CELEBORN_USER_CHECK(true)
#define CELEBORN_USER_DCHECK_NOT_NULL(e, ...) CELEBORN_USER_CHECK(true)
#endif
#define CELEBORN_USER_FAIL(...) \
_CELEBORN_THROW( \
::celeborn::utils::CelebornUserError, \
::celeborn::utils::error_source::kErrorSourceUser.c_str(), \
::celeborn::utils::error_code::kInvalidArgument.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
#define CELEBORN_NYI(...) \
_CELEBORN_THROW( \
::celeborn::utils::CelebornRuntimeError, \
::celeborn::utils::error_source::kErrorSourceRuntime.c_str(), \
::celeborn::utils::error_code::kNotImplemented.c_str(), \
/* isRetriable */ false, \
##__VA_ARGS__)
} // namespace utils
} // namespace celeborn