Skip to content

Instantly share code, notes, and snippets.

@mq1n
Created August 20, 2024 14:25
Show Gist options
  • Select an option

  • Save mq1n/68657a9b6deba5585407dc1929e5982d to your computer and use it in GitHub Desktop.

Select an option

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
#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