Created
August 20, 2024 14:25
-
-
Save mq1n/68657a9b6deba5585407dc1929e5982d to your computer and use it in GitHub Desktop.
A C++ utility for safe function execution with comprehensive exception handling. Supports both sync and async execution and cross platform compatible
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #pragma once | |
| #include <any> | |
| #include <functional> | |
| #include <optional> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <thread> | |
| #include <future> | |
| #include <iostream> | |
| #include <array> | |
| #include <string> | |
| #if defined(_WIN32) | |
| #include <Windows.h> | |
| #endif | |
| namespace SafeExecutor | |
| { | |
| static constexpr auto EXCEPTION_CPP_MAGIC = 0xE06D7363; | |
| template <typename T, size_t N> | |
| static constexpr auto StackString(const T(&str)[N]) | |
| { | |
| std::array <T, N> ret{}; | |
| std::copy(std::begin(str), std::end(str), ret.begin()); | |
| return ret; | |
| } | |
| // Enum class for possible error codes | |
| enum class ResultCode : uint8_t | |
| { | |
| Success, | |
| DivisionByZero, | |
| InvalidArgument, | |
| OutOfRange, | |
| LogicError, | |
| SystemError, | |
| RuntimeError, | |
| BadAlloc, | |
| CppException, | |
| UnknownException, | |
| SEHException, | |
| UnknownResult, | |
| FutureError | |
| }; | |
| // Convert result to string | |
| constexpr const char* ResultCodeToString(ResultCode code) | |
| { | |
| switch (code) | |
| { | |
| case ResultCode::Success: | |
| return "Success"; | |
| case ResultCode::DivisionByZero: | |
| return "DivisionByZero"; | |
| case ResultCode::InvalidArgument: | |
| return "InvalidArgument"; | |
| case ResultCode::OutOfRange: | |
| return "OutOfRange"; | |
| case ResultCode::LogicError: | |
| return "LogicError"; | |
| case ResultCode::SystemError: | |
| return "SystemError"; | |
| case ResultCode::RuntimeError: | |
| return "RuntimeError"; | |
| case ResultCode::BadAlloc: | |
| return "BadAlloc"; | |
| case ResultCode::CppException: | |
| return "CppException"; | |
| case ResultCode::UnknownException: | |
| return "UnknownException"; | |
| case ResultCode::SEHException: | |
| return "SEHException"; | |
| case ResultCode::UnknownResult: | |
| return "UnknownResult"; | |
| default: | |
| return "Unknown"; | |
| } | |
| } | |
| // Helper type trait to check if a type is void | |
| template <typename T> | |
| struct is_void_type : std::is_void<std::remove_cv_t<T>> {}; | |
| // Specialization of TResultOf for void return type | |
| template <typename Fn, typename... Args> | |
| using TResultOf = std::conditional_t< | |
| is_void_type<std::invoke_result_t<Fn, Args...>>::value, | |
| std::monostate, | |
| std::invoke_result_t<Fn, Args...> | |
| >; | |
| // Handler type of a function | |
| using TExceptionHandler = std::function<void(uint32_t, const char*)>; | |
| // Execute function as C++ exception protected code inside SEH handler function(execute_seh_safe) | |
| template <typename Fn, typename... Args> | |
| std::optional <TResultOf <Fn, Args...>> ExecuteCPPExceptionSafe(const TExceptionHandler& handler, Fn&& fn, Args&&... args) noexcept | |
| { | |
| using ResultType = TResultOf <Fn, Args...>; | |
| try | |
| { | |
| if constexpr (std::is_same_v <ResultType, void> || std::is_same_v <ResultType, std::monostate>) | |
| { | |
| std::invoke(std::forward<Fn>(fn), std::forward<Args>(args)...); | |
| return std::nullopt; | |
| } | |
| else | |
| { | |
| return std::make_optional(std::invoke(std::forward<Fn>(fn), std::forward<Args>(args)...)); | |
| } | |
| } | |
| catch (const std::invalid_argument& e) | |
| { | |
| handler(static_cast<uint32_t>(ResultCode::InvalidArgument), e.what()); | |
| return std::nullopt; | |
| } | |
| catch (const std::out_of_range& e) | |
| { | |
| handler(static_cast<uint32_t>(ResultCode::OutOfRange), e.what()); | |
| return std::nullopt; | |
| } | |
| catch (const std::logic_error& e) | |
| { | |
| handler(static_cast<uint32_t>(ResultCode::LogicError), e.what()); | |
| return std::nullopt; | |
| } | |
| catch (const std::system_error& e) | |
| { | |
| handler(static_cast<uint32_t>(ResultCode::SystemError), e.what()); | |
| return std::nullopt; | |
| } | |
| catch (const std::runtime_error& e) | |
| { | |
| handler(static_cast<uint32_t>(ResultCode::RuntimeError), e.what()); | |
| return std::nullopt; | |
| } | |
| catch (const std::bad_alloc& e) | |
| { | |
| handler(static_cast<uint32_t>(ResultCode::BadAlloc), e.what()); | |
| return std::nullopt; | |
| } | |
| catch (const std::exception& e) | |
| { | |
| handler(static_cast<uint32_t>(ResultCode::CppException), e.what()); | |
| return std::nullopt; | |
| } | |
| catch (...) | |
| { | |
| handler(static_cast<uint32_t>(ResultCode::UnknownException), StackString("Unhandled Exception").data()); | |
| return std::nullopt; | |
| } | |
| return std::nullopt; | |
| } | |
| inline uint32_t SEHExceptionParser(const uint32_t code, const EXCEPTION_POINTERS* pEP) | |
| { | |
| // TODO: Give more information about the exception | |
| return code == EXCEPTION_CPP_MAGIC ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER; | |
| } | |
| // Executes the given function and returns the result or an exception | |
| template <typename Fn, typename... Args> | |
| inline auto ExecuteSafeEx(TExceptionHandler handler, Fn&& fn, Args&&... args) -> std::pair <std::optional <TResultOf <Fn, Args...>>, ResultCode> | |
| { | |
| using ResultType = TResultOf <Fn, Args...>; | |
| std::pair <std::optional <ResultType>, ResultCode> result; | |
| std::optional <ResultType> optResult; | |
| auto ExecuteSafeWorkerImpl = [&]() -> void { | |
| optResult = ExecuteCPPExceptionSafe(handler, std::forward<Fn>(fn), std::forward<Args>(args)...); | |
| }; | |
| #ifdef _WIN32 | |
| // HACK: Bypass for 'Cannot use __try in functions that require object unwinding'. | |
| auto ExecuteSafeImpl = [&]() { | |
| __try | |
| { | |
| ExecuteSafeWorkerImpl(); | |
| if constexpr (std::is_same_v <ResultType, void> || std::is_same_v <ResultType, std::monostate>) | |
| result = std::make_pair(optResult, ResultCode::Success); | |
| else | |
| { | |
| if (!optResult) | |
| result = std::make_pair(std::nullopt, ResultCode::UnknownResult); | |
| else | |
| result = std::make_pair(optResult, ResultCode::Success); | |
| } | |
| } | |
| __except (SEHExceptionParser(GetExceptionCode(), GetExceptionInformation())) | |
| { | |
| handler(GetExceptionCode(), StackString("SEH Exception").data()); | |
| result = std::make_pair(std::nullopt, ResultCode::SEHException); | |
| } | |
| }; | |
| ExecuteSafeImpl(); | |
| #else | |
| ExecuteSafeWorkerImpl(); | |
| #endif | |
| return result; | |
| } | |
| template <typename Fn, typename... Args> | |
| inline auto ExecuteSafe(Fn&& fn, Args&&... args) -> std::pair <std::optional <TResultOf <Fn, Args...>>, ResultCode> | |
| { | |
| return ExecuteSafeEx([](uint32_t code, const char* name) -> void { | |
| std::cout << StackString("Error").data() << name << '(' << code << ')' << std::endl; | |
| }, std::forward<Fn>(fn), std::forward<Args>(args)...); | |
| } | |
| template <typename Fn, typename... Args> | |
| inline auto ExecuteSafeAsyncEx(TExceptionHandler handler, Fn&& fn, Args&&... args) -> std::future <std::pair <std::optional <TResultOf <Fn, Args...>>, ResultCode>> | |
| { | |
| try | |
| { | |
| return std::async(std::launch::async, [&]() { | |
| return ExecuteSafeEx(handler, std::forward<Fn>(fn), std::forward<Args>(args)...); | |
| }); | |
| } | |
| catch (const std::future_error&) | |
| { | |
| return std::async(std::launch::async, []() -> std::pair <std::optional <TResultOf <Fn, Args...>>, ResultCode> { | |
| return std::make_pair(std::nullopt, ResultCode::FutureError); | |
| }); | |
| } | |
| } | |
| template <typename Fn, typename... Args> | |
| inline auto ExecuteSafeAsync(Fn&& fn, Args&&... args) -> std::future <std::pair <std::optional <TResultOf <Fn, Args...>>, ResultCode>> | |
| { | |
| return ExecuteSafeAsyncEx([](uint32_t code, const char* name) -> void { | |
| std::cout << StackString("Future Error").data() << name << '(' << code << ')' << std::endl; | |
| }, std::forward<Fn>(fn), std::forward<Args>(args)...); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment