diff --git a/presto-native-execution/presto_cpp/main/PrestoMain.cpp b/presto-native-execution/presto_cpp/main/PrestoMain.cpp index bca2dab8dd1bd..526d42311cc8d 100644 --- a/presto-native-execution/presto_cpp/main/PrestoMain.cpp +++ b/presto-native-execution/presto_cpp/main/PrestoMain.cpp @@ -16,6 +16,7 @@ #include #include #include "presto_cpp/main/PrestoServer.h" +#include "presto_cpp/main/common/Exception.h" #include "presto_cpp/main/common/Utils.h" #include "velox/common/base/StatsReporter.h" @@ -37,3 +38,16 @@ folly::Singleton reporter([]() { return new facebook::velox::DummyStatsReporter(); }); #endif + +// Initialize singleton for the exception translator. +// NOTE: folly::Singleton enforces that only ONE registration per type can +// exist. If another file tries to register VeloxToPrestoExceptionTranslator +// again, the program will fail during static initialization with a duplicate +// registration error. Extended servers should register a DERIVED class instead: +// folly::Singleton customTranslator([]() { +// return new CustomExceptionTranslator(); // derived class +// }); +folly::Singleton + exceptionTranslator([]() { + return new facebook::presto::VeloxToPrestoExceptionTranslator(); + }); diff --git a/presto-native-execution/presto_cpp/main/PrestoTask.cpp b/presto-native-execution/presto_cpp/main/PrestoTask.cpp index 447625e533cd6..afdf9f4f82dc5 100644 --- a/presto-native-execution/presto_cpp/main/PrestoTask.cpp +++ b/presto-native-execution/presto_cpp/main/PrestoTask.cpp @@ -97,9 +97,9 @@ protocol::ExecutionFailureInfo toPrestoError(std::exception_ptr ex) { try { rethrow_exception(ex); } catch (const VeloxException& e) { - return VeloxToPrestoExceptionTranslator::translate(e); + return translateToPrestoException(e); } catch (const std::exception& e) { - return VeloxToPrestoExceptionTranslator::translate(e); + return translateToPrestoException(e); } } diff --git a/presto-native-execution/presto_cpp/main/common/Exception.cpp b/presto-native-execution/presto_cpp/main/common/Exception.cpp index 2afbb85d4c72e..9d15b13b63ae3 100644 --- a/presto-native-execution/presto_cpp/main/common/Exception.cpp +++ b/presto-native-execution/presto_cpp/main/common/Exception.cpp @@ -16,105 +16,149 @@ namespace facebook::presto { -std::unordered_map< - std::string, - std::unordered_map>& -VeloxToPrestoExceptionTranslator::translateMap() { - static std::unordered_map< - std::string, - std::unordered_map> - errorMap = { - {velox::error_source::kErrorSourceRuntime, - {{velox::error_code::kMemCapExceeded, - {0x00020007, - "EXCEEDED_LOCAL_MEMORY_LIMIT", - protocol::ErrorType::INSUFFICIENT_RESOURCES}}, - - {velox::error_code::kMemAborted, - {0x00020000, - "GENERIC_INSUFFICIENT_RESOURCES", - protocol::ErrorType::INSUFFICIENT_RESOURCES}}, - - {velox::error_code::kSpillLimitExceeded, - {0x00020006, - "EXCEEDED_SPILL_LIMIT", - protocol::ErrorType::INSUFFICIENT_RESOURCES}}, - - {velox::error_code::kMemArbitrationFailure, - {0x00020000, - "MEMORY_ARBITRATION_FAILURE", - protocol::ErrorType::INSUFFICIENT_RESOURCES}}, - - {velox::error_code::kMemArbitrationTimeout, - {0x00020000, - "GENERIC_INSUFFICIENT_RESOURCES", - protocol::ErrorType::INSUFFICIENT_RESOURCES}}, - - {velox::error_code::kMemAllocError, - {0x00020000, - "GENERIC_INSUFFICIENT_RESOURCES", - protocol::ErrorType::INSUFFICIENT_RESOURCES}}, - - {velox::error_code::kInvalidState, - {0x00010000, - "GENERIC_INTERNAL_ERROR", - protocol::ErrorType::INTERNAL_ERROR}}, - - {velox::error_code::kGenericSpillFailure, - {0x00010023, - "GENERIC_SPILL_FAILURE", - protocol::ErrorType::INTERNAL_ERROR}}, - - {velox::error_code::kUnreachableCode, - {0x00010000, - "GENERIC_INTERNAL_ERROR", - protocol::ErrorType::INTERNAL_ERROR}}, - - {velox::error_code::kNotImplemented, - {0x00010000, - "GENERIC_INTERNAL_ERROR", - protocol::ErrorType::INTERNAL_ERROR}}, - - {velox::error_code::kUnknown, - {0x00010000, - "GENERIC_INTERNAL_ERROR", - protocol::ErrorType::INTERNAL_ERROR}}, - - {presto::error_code::kExceededLocalBroadcastJoinMemoryLimit, - {0x0002000C, - "EXCEEDED_LOCAL_BROADCAST_JOIN_MEMORY_LIMIT", - protocol::ErrorType::INSUFFICIENT_RESOURCES}}}}, - - {velox::error_source::kErrorSourceUser, - {{velox::error_code::kInvalidArgument, - {0x00000000, - "GENERIC_USER_ERROR", - protocol::ErrorType::USER_ERROR}}, - {velox::error_code::kUnsupported, - {0x0000000D, "NOT_SUPPORTED", protocol::ErrorType::USER_ERROR}}, - {velox::error_code::kUnsupportedInputUncatchable, - {0x0000000D, "NOT_SUPPORTED", protocol::ErrorType::USER_ERROR}}, - {velox::error_code::kArithmeticError, - {0x00000000, - "GENERIC_USER_ERROR", - protocol::ErrorType::USER_ERROR}}, - {velox::error_code::kSchemaMismatch, - {0x00000000, - "GENERIC_USER_ERROR", - protocol::ErrorType::USER_ERROR}}, - {velox::error_code::kInvalidArgument, - {0x00000000, - "GENERIC_USER_ERROR", - protocol::ErrorType::USER_ERROR}}}}, - - {velox::error_source::kErrorSourceSystem, {}}}; - return errorMap; +VeloxToPrestoExceptionTranslator::VeloxToPrestoExceptionTranslator() { + // Register runtime errors + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kMemCapExceeded, + {.code = 0x00020007, + .name = "EXCEEDED_LOCAL_MEMORY_LIMIT", + .type = protocol::ErrorType::INSUFFICIENT_RESOURCES}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kMemAborted, + {.code = 0x00020000, + .name = "GENERIC_INSUFFICIENT_RESOURCES", + .type = protocol::ErrorType::INSUFFICIENT_RESOURCES}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kSpillLimitExceeded, + {.code = 0x00020006, + .name = "EXCEEDED_SPILL_LIMIT", + .type = protocol::ErrorType::INSUFFICIENT_RESOURCES}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kMemArbitrationFailure, + {.code = 0x00020000, + .name = "MEMORY_ARBITRATION_FAILURE", + .type = protocol::ErrorType::INSUFFICIENT_RESOURCES}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kMemArbitrationTimeout, + {.code = 0x00020000, + .name = "GENERIC_INSUFFICIENT_RESOURCES", + .type = protocol::ErrorType::INSUFFICIENT_RESOURCES}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kMemAllocError, + {.code = 0x00020000, + .name = "GENERIC_INSUFFICIENT_RESOURCES", + .type = protocol::ErrorType::INSUFFICIENT_RESOURCES}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kInvalidState, + {.code = 0x00010000, + .name = "GENERIC_INTERNAL_ERROR", + .type = protocol::ErrorType::INTERNAL_ERROR}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kGenericSpillFailure, + {.code = 0x00010023, + .name = "GENERIC_SPILL_FAILURE", + .type = protocol::ErrorType::INTERNAL_ERROR}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kUnreachableCode, + {.code = 0x00010000, + .name = "GENERIC_INTERNAL_ERROR", + .type = protocol::ErrorType::INTERNAL_ERROR}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kNotImplemented, + {.code = 0x00010000, + .name = "GENERIC_INTERNAL_ERROR", + .type = protocol::ErrorType::INTERNAL_ERROR}); + + registerError( + velox::error_source::kErrorSourceRuntime, + velox::error_code::kUnknown, + {.code = 0x00010000, + .name = "GENERIC_INTERNAL_ERROR", + .type = protocol::ErrorType::INTERNAL_ERROR}); + + registerError( + velox::error_source::kErrorSourceRuntime, + presto::error_code::kExceededLocalBroadcastJoinMemoryLimit, + {.code = 0x0002000C, + .name = "EXCEEDED_LOCAL_BROADCAST_JOIN_MEMORY_LIMIT", + .type = protocol::ErrorType::INSUFFICIENT_RESOURCES}); + + // Register user errors + registerError( + velox::error_source::kErrorSourceUser, + velox::error_code::kInvalidArgument, + {.code = 0x00000000, + .name = "GENERIC_USER_ERROR", + .type = protocol::ErrorType::USER_ERROR}); + + registerError( + velox::error_source::kErrorSourceUser, + velox::error_code::kUnsupported, + {.code = 0x0000000D, + .name = "NOT_SUPPORTED", + .type = protocol::ErrorType::USER_ERROR}); + + registerError( + velox::error_source::kErrorSourceUser, + velox::error_code::kUnsupportedInputUncatchable, + {.code = 0x0000000D, + .name = "NOT_SUPPORTED", + .type = protocol::ErrorType::USER_ERROR}); + + registerError( + velox::error_source::kErrorSourceUser, + velox::error_code::kArithmeticError, + {.code = 0x00000000, + .name = "GENERIC_USER_ERROR", + .type = protocol::ErrorType::USER_ERROR}); + + registerError( + velox::error_source::kErrorSourceUser, + velox::error_code::kSchemaMismatch, + {.code = 0x00000000, + .name = "GENERIC_USER_ERROR", + .type = protocol::ErrorType::USER_ERROR}); +} + +void VeloxToPrestoExceptionTranslator::registerError( + const std::string& errorSource, + const std::string& errorCode, + const protocol::ErrorCode& prestoErrorCode) { + auto& innerMap = errorMap_[errorSource]; + auto [it, inserted] = innerMap.emplace(errorCode, prestoErrorCode); + VELOX_CHECK( + inserted, + "Duplicate errorCode '{}' for errorSource '{}' is not allowed. " + "Existing mapping: [code={}, name={}, type={}]", + errorCode, + errorSource, + it->second.code, + it->second.name, + static_cast(it->second.type)); } protocol::ExecutionFailureInfo VeloxToPrestoExceptionTranslator::translate( - const velox::VeloxException& e) { + const velox::VeloxException& e) const { protocol::ExecutionFailureInfo error; - // Line number must be >= 1 error.errorLocation.lineNumber = e.line() >= 1 ? e.line() : 1; error.errorLocation.columnNumber = 1; error.type = e.exceptionName(); @@ -127,8 +171,6 @@ protocol::ExecutionFailureInfo VeloxToPrestoExceptionTranslator::translate( msg << " " << e.additionalContext(); } error.message = msg.str(); - // Stack trace may not be available if stack trace capturing is disabled or - // rate limited. if (e.stackTrace()) { error.stack = e.stackTrace()->toStrVector(); } @@ -136,8 +178,8 @@ protocol::ExecutionFailureInfo VeloxToPrestoExceptionTranslator::translate( const auto& errorSource = e.errorSource(); const auto& errorCode = e.errorCode(); - auto itrErrorCodesMap = translateMap().find(errorSource); - if (itrErrorCodesMap != translateMap().end()) { + auto itrErrorCodesMap = errorMap_.find(errorSource); + if (itrErrorCodesMap != errorMap_.end()) { auto itrErrorCode = itrErrorCodesMap->second.find(errorCode); if (itrErrorCode != itrErrorCodesMap->second.end()) { error.errorCode = itrErrorCode->second; @@ -151,7 +193,7 @@ protocol::ExecutionFailureInfo VeloxToPrestoExceptionTranslator::translate( } protocol::ExecutionFailureInfo VeloxToPrestoExceptionTranslator::translate( - const std::exception& e) { + const std::exception& e) const { protocol::ExecutionFailureInfo error; error.errorLocation.lineNumber = 1; error.errorLocation.columnNumber = 1; diff --git a/presto-native-execution/presto_cpp/main/common/Exception.h b/presto-native-execution/presto_cpp/main/common/Exception.h index cf2b954b90a98..fb6a484e07b08 100644 --- a/presto-native-execution/presto_cpp/main/common/Exception.h +++ b/presto-native-execution/presto_cpp/main/common/Exception.h @@ -13,6 +13,7 @@ */ #pragma once +#include #include #include "presto_cpp/presto_protocol/core/presto_protocol_core.h" #include "velox/common/base/VeloxException.h" @@ -35,20 +36,63 @@ inline constexpr auto kExceededLocalBroadcastJoinMemoryLimit = "EXCEEDED_LOCAL_BROADCAST_JOIN_MEMORY_LIMIT"_fs; } // namespace error_code +// Exception translator singleton for converting Velox exceptions to Presto +// errors. This follows the same pattern as velox/common/base/StatsReporter.h. +// +// IMPORTANT: folly::Singleton enforces single registration per type. +// - Only ONE registration of VeloxToPrestoExceptionTranslator can exist +// - Duplicate registrations will cause program to fail during static init +// - Extended servers must register a derived class class VeloxToPrestoExceptionTranslator { public: - // Translates to Presto error from Velox exceptions - static protocol::ExecutionFailureInfo translate( - const velox::VeloxException& e); + using ErrorCodeMap = std::unordered_map< + std::string, + std::unordered_map>; - // Translates to Presto error from std::exceptions - static protocol::ExecutionFailureInfo translate(const std::exception& e); + VeloxToPrestoExceptionTranslator(); - // Returns a reference to the error map containing mapping between - // velox error code and Presto errors defined in Presto protocol - static std::unordered_map< - std::string, - std::unordered_map>& - translateMap(); + virtual ~VeloxToPrestoExceptionTranslator() = default; + + virtual protocol::ExecutionFailureInfo translate( + const velox::VeloxException& e) const; + + virtual protocol::ExecutionFailureInfo translate( + const std::exception& e) const; + + // For testing purposes only - provides access to the error map + const ErrorCodeMap& testingErrorMap() const { + return errorMap_; + } + + protected: + void registerError( + const std::string& errorSource, + const std::string& errorCode, + const protocol::ErrorCode& prestoErrorCode); + + ErrorCodeMap errorMap_; }; + +// Global inline function APIs to translate exceptions (returns +// ExecutionFailureInfo) Similar pattern to StatsReporter, but returns a value +// instead of recording +inline protocol::ExecutionFailureInfo translateToPrestoException( + const velox::VeloxException& e) { + const auto translator = + folly::Singleton::try_get_fast(); + VELOX_CHECK_NOT_NULL( + translator, + "VeloxToPrestoExceptionTranslator singleton must be registered"); + return translator->translate(e); +} + +inline protocol::ExecutionFailureInfo translateToPrestoException( + const std::exception& e) { + const auto translator = + folly::Singleton::try_get_fast(); + VELOX_CHECK_NOT_NULL( + translator, + "VeloxToPrestoExceptionTranslator singleton must be registered"); + return translator->translate(e); +} } // namespace facebook::presto diff --git a/presto-native-execution/presto_cpp/main/common/tests/CMakeLists.txt b/presto-native-execution/presto_cpp/main/common/tests/CMakeLists.txt index 0c96e5270744b..5fcc6b0ca4681 100644 --- a/presto-native-execution/presto_cpp/main/common/tests/CMakeLists.txt +++ b/presto-native-execution/presto_cpp/main/common/tests/CMakeLists.txt @@ -31,7 +31,6 @@ target_link_libraries( velox_window ${RE2} GTest::gtest - GTest::gtest_main ) set_property(TARGET presto_common_test PROPERTY JOB_POOL_LINK presto_link_job_pool) diff --git a/presto-native-execution/presto_cpp/main/common/tests/CommonTest.cpp b/presto-native-execution/presto_cpp/main/common/tests/CommonTest.cpp index f35b13890c70b..23f08e048bf99 100644 --- a/presto-native-execution/presto_cpp/main/common/tests/CommonTest.cpp +++ b/presto-native-execution/presto_cpp/main/common/tests/CommonTest.cpp @@ -20,6 +20,13 @@ using namespace facebook; using namespace facebook::velox; using namespace facebook::presto; +namespace { +folly::Singleton + exceptionTranslatorSingleton([]() { + return new facebook::presto::VeloxToPrestoExceptionTranslator(); + }); +} // namespace + TEST(VeloxToPrestoExceptionTranslatorTest, exceptionTranslation) { FLAGS_velox_exception_user_stacktrace_enabled = true; for (const bool withContext : {false, true}) { @@ -58,7 +65,10 @@ TEST(VeloxToPrestoExceptionTranslatorTest, exceptionTranslation) { EXPECT_EQ(e.errorSource(), error_source::kErrorSourceUser); EXPECT_EQ(e.errorCode(), velox::error_code::kArithmeticError); - auto failureInfo = VeloxToPrestoExceptionTranslator::translate(e); + auto translator = + folly::Singleton::try_get(); + ASSERT_NE(translator, nullptr); + auto failureInfo = translateToPrestoException(e); EXPECT_EQ(failureInfo.type, e.exceptionName()); EXPECT_EQ(failureInfo.errorLocation.lineNumber, e.line()); EXPECT_EQ(failureInfo.errorCode.name, "GENERIC_USER_ERROR"); @@ -94,7 +104,10 @@ TEST(VeloxToPrestoExceptionTranslatorTest, exceptionTranslation) { EXPECT_EQ(e.errorSource(), error_source::kErrorSourceRuntime); EXPECT_EQ(e.errorCode(), velox::error_code::kInvalidState); - auto failureInfo = VeloxToPrestoExceptionTranslator::translate(e); + auto translator = + folly::Singleton::try_get(); + ASSERT_NE(translator, nullptr); + auto failureInfo = translateToPrestoException(e); EXPECT_EQ(failureInfo.type, e.exceptionName()); EXPECT_EQ(failureInfo.errorLocation.lineNumber, e.line()); EXPECT_EQ(failureInfo.errorCode.name, "GENERIC_INTERNAL_ERROR"); @@ -110,7 +123,10 @@ TEST(VeloxToPrestoExceptionTranslatorTest, exceptionTranslation) { EXPECT_EQ(e.errorSource(), error_source::kErrorSourceUser); EXPECT_EQ(e.errorCode(), velox::error_code::kInvalidArgument); - auto failureInfo = VeloxToPrestoExceptionTranslator::translate(e); + auto translator = + folly::Singleton::try_get(); + ASSERT_NE(translator, nullptr); + auto failureInfo = translateToPrestoException(e); EXPECT_EQ(failureInfo.type, e.exceptionName()); EXPECT_EQ(failureInfo.errorLocation.lineNumber, e.line()); EXPECT_EQ(failureInfo.errorCode.name, "GENERIC_USER_ERROR"); @@ -121,8 +137,10 @@ TEST(VeloxToPrestoExceptionTranslatorTest, exceptionTranslation) { } std::runtime_error stdRuntimeError("Test error message"); - auto failureInfo = - VeloxToPrestoExceptionTranslator::translate((stdRuntimeError)); + auto translator = + folly::Singleton::try_get(); + ASSERT_NE(translator, nullptr); + auto failureInfo = translateToPrestoException((stdRuntimeError)); EXPECT_EQ(failureInfo.type, "std::exception"); EXPECT_EQ(failureInfo.errorLocation.lineNumber, 1); EXPECT_EQ(failureInfo.errorCode.name, "GENERIC_INTERNAL_ERROR"); @@ -133,7 +151,10 @@ TEST(VeloxToPrestoExceptionTranslatorTest, exceptionTranslation) { TEST(VeloxToPrestoExceptionTranslatorTest, allErrorCodeTranslations) { // Test all error codes in the translation map to ensure they translate // correctly - const auto& translateMap = VeloxToPrestoExceptionTranslator::translateMap(); + auto translator = folly::Singleton< + facebook::presto::VeloxToPrestoExceptionTranslator>::try_get(); + ASSERT_NE(translator, nullptr); + const auto& translateMap = translator->testingErrorMap(); for (const auto& [errorSource, errorCodeMap] : translateMap) { for (const auto& [errorCode, expectedErrorCode] : errorCodeMap) { @@ -153,8 +174,7 @@ TEST(VeloxToPrestoExceptionTranslatorTest, allErrorCodeTranslations) { errorCode, false); - auto failureInfo = - VeloxToPrestoExceptionTranslator::translate(runtimeException); + auto failureInfo = translator->translate(runtimeException); EXPECT_EQ(failureInfo.errorCode.code, expectedErrorCode.code) << "Error code mismatch for " << errorCode; @@ -176,8 +196,7 @@ TEST(VeloxToPrestoExceptionTranslatorTest, allErrorCodeTranslations) { errorCode, false); - auto failureInfo = - VeloxToPrestoExceptionTranslator::translate(userException); + auto failureInfo = translator->translate(userException); EXPECT_EQ(failureInfo.errorCode.code, expectedErrorCode.code) << "Error code mismatch for " << errorCode; @@ -211,3 +230,9 @@ TEST(UtilsTest, extractMessageBody) { auto messageBody = util::extractMessageBody(body); EXPECT_EQ(messageBody, "body1body2body3body4body5"); } + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + folly::SingletonVault::singleton()->registrationComplete(); + return RUN_ALL_TESTS(); +} diff --git a/presto-native-execution/presto_cpp/main/tests/TaskManagerTest.cpp b/presto-native-execution/presto_cpp/main/tests/TaskManagerTest.cpp index c6b6313ac12cf..c4cae5c9eaf05 100644 --- a/presto-native-execution/presto_cpp/main/tests/TaskManagerTest.cpp +++ b/presto-native-execution/presto_cpp/main/tests/TaskManagerTest.cpp @@ -18,6 +18,7 @@ #include "folly/experimental/EventCount.h" #include "presto_cpp/main/PrestoExchangeSource.h" #include "presto_cpp/main/TaskResource.h" +#include "presto_cpp/main/common/Exception.h" #include "presto_cpp/main/common/tests/MutableConfigs.h" #include "presto_cpp/main/connectors/HivePrestoToVeloxConnector.h" #include "presto_cpp/main/connectors/PrestoToVeloxConnector.h" @@ -51,6 +52,11 @@ namespace facebook::presto { namespace { +folly::Singleton + exceptionTranslator([]() { + return new facebook::presto::VeloxToPrestoExceptionTranslator(); + }); + // Repeatedly calls for cleanOldTasks() for a while to ensure that we overcome a // potential race condition where we call cleanOldTasks() before some Tasks are // ready to be cleaned. diff --git a/presto-native-execution/presto_cpp/main/types/VeloxPlanConversion.cpp b/presto-native-execution/presto_cpp/main/types/VeloxPlanConversion.cpp index 116112c67af21..157a0e78b16d3 100644 --- a/presto-native-execution/presto_cpp/main/types/VeloxPlanConversion.cpp +++ b/presto-native-execution/presto_cpp/main/types/VeloxPlanConversion.cpp @@ -55,10 +55,10 @@ protocol::PlanConversionResponse prestoToVeloxPlanConversion( planValidator->validatePlanFragment(veloxPlan); } catch (const VeloxException& e) { response.failures.emplace_back( - copyFailureInfo(VeloxToPrestoExceptionTranslator::translate(e))); + copyFailureInfo(translateToPrestoException(e))); } catch (const std::exception& e) { response.failures.emplace_back( - copyFailureInfo(VeloxToPrestoExceptionTranslator::translate(e))); + copyFailureInfo(translateToPrestoException(e))); } return response;