diff options
author | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2018-06-24 19:29:12 +0300 |
---|---|---|
committer | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2018-06-24 19:29:12 +0300 |
commit | 4547cc6a15ddb63e0dbe337d166ed9a817c65a92 (patch) | |
tree | 41357672515636f80f74dd4ecdcbbdcf724c53fe | |
parent | 7b50437109eab2007b6e3cd2b41bb6b0cdd49488 (diff) |
Broke logging and status_code out into their own headers.
-rw-r--r-- | cmake/headers.cmake | 2 | ||||
-rw-r--r-- | include/afio/revision.hpp | 6 | ||||
-rw-r--r-- | include/afio/v2.0/config.hpp | 753 | ||||
-rw-r--r-- | include/afio/v2.0/handle.hpp | 1 | ||||
-rw-r--r-- | include/afio/v2.0/logging.hpp | 334 | ||||
-rw-r--r-- | include/afio/v2.0/status_code.hpp | 464 |
6 files changed, 805 insertions, 755 deletions
diff --git a/cmake/headers.cmake b/cmake/headers.cmake index a5137105..9199c39f 100644 --- a/cmake/headers.cmake +++ b/cmake/headers.cmake @@ -26,6 +26,7 @@ set(afio_HEADERS "include/afio/v2.0/handle.hpp" "include/afio/v2.0/io_handle.hpp" "include/afio/v2.0/io_service.hpp" + "include/afio/v2.0/logging.hpp" "include/afio/v2.0/map_handle.hpp" "include/afio/v2.0/mapped_file_handle.hpp" "include/afio/v2.0/native_handle_type.hpp" @@ -34,6 +35,7 @@ set(afio_HEADERS "include/afio/v2.0/path_view.hpp" "include/afio/v2.0/stat.hpp" "include/afio/v2.0/statfs.hpp" + "include/afio/v2.0/status_code.hpp" "include/afio/v2.0/storage_profile.hpp" "include/afio/v2.0/utils.hpp" "include/afio/version.hpp" diff --git a/include/afio/revision.hpp b/include/afio/revision.hpp index e9b61413..215c0b0c 100644 --- a/include/afio/revision.hpp +++ b/include/afio/revision.hpp @@ -1,4 +1,4 @@ // Note the second line of this file must ALWAYS be the git SHA, third line ALWAYS the git SHA update time -#define AFIO_PREVIOUS_COMMIT_REF c95d49034e7c24eda5c6d3dea04fcd7463ff8457 -#define AFIO_PREVIOUS_COMMIT_DATE "2018-06-22 17:56:38 +00:00" -#define AFIO_PREVIOUS_COMMIT_UNIQUE c95d4903 +#define AFIO_PREVIOUS_COMMIT_REF 7b50437109eab2007b6e3cd2b41bb6b0cdd49488 +#define AFIO_PREVIOUS_COMMIT_DATE "2018-06-23 23:19:32 +00:00" +#define AFIO_PREVIOUS_COMMIT_UNIQUE 7b504371 diff --git a/include/afio/v2.0/config.hpp b/include/afio/v2.0/config.hpp index afd3c636..c6f08c9d 100644 --- a/include/afio/v2.0/config.hpp +++ b/include/afio/v2.0/config.hpp @@ -253,6 +253,7 @@ struct path_hasher size_t operator()(const filesystem::path &p) const { return std::hash<filesystem::path::string_type>()(p.native()); } }; AFIO_V2_NAMESPACE_END +#include <ctime> // for struct timespec // Configure AFIO_DECL @@ -359,758 +360,6 @@ namespace detail AFIO_V2_NAMESPACE_END -/* The SG14 status code implementation is quite profoundly different to the -error code implementation. In the error code implementation, std::error_code -is fixed by the standard library, so we wrap it with extra metadata into -an error_info type. error_info then constructs off a code and a code domain -tag. - -Status code, on the other hand, is templated and is designed for custom -domains which can set arbitrary payloads. So we define custom domains and -status codes for AFIO with these combinations: - -- win32_error{ DWORD } -- ntkernel_error{ LONG } -- posix_error{ int } -- generic_error{ errc } - -Each of these is a separate AFIO custom status code domain. We also define -an erased form of these custom domains, and that is typedefed to -error_domain<intptr_t>::value_type. - -This design ensure that AFIO can be configured into either std-based error -handling or SG14 experimental status code handling. It defaults to the latter -as that (a) enables safe header only AFIO on Windows (b) produces better codegen -(c) drags in far fewer STL headers. -*/ - -#if AFIO_EXPERIMENTAL_STATUS_CODE - -// Bring in a result implementation based on status_code -#include "outcome/include/outcome/experimental/status_result.hpp" -#include "outcome/include/outcome/try.hpp" - -AFIO_V2_NAMESPACE_BEGIN - -#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO - -namespace detail -{ - template <class T> struct error_domain_value_type - { - //! \brief The type of code - T sc{}; - - // The id of the thread where this failure occurred - uint32_t _thread_id{0}; - // The TLS path store entry - uint16_t _tls_path_id1{static_cast<uint16_t>(-1)}, _tls_path_id2{static_cast<uint16_t>(-1)}; - // The id of the relevant log entry in the AFIO log (if logging enabled) - size_t _log_id{static_cast<size_t>(-1)}; - - //! Default construction - error_domain_value_type() = default; - - //! Implicitly constructs an instance - constexpr inline error_domain_value_type(T _sc) - : sc(_sc) - { - } // NOLINT - - //! Compares to a T - constexpr bool operator==(const T &b) const noexcept { return sc == b; } - }; -} - -/*! \class error_domain -\brief The SG14 status code domain for errors in AFIO. -*/ -template <class BaseStatusCodeDomain> class error_domain : public BaseStatusCodeDomain -{ - friend class SYSTEM_ERROR2_NAMESPACE::status_code<error_domain>; - using _base = BaseStatusCodeDomain; - -public: - using string_ref = typename BaseStatusCodeDomain::string_ref; - using atomic_refcounted_string_ref = typename BaseStatusCodeDomain::atomic_refcounted_string_ref; - - //! \brief The value type of errors in AFIO - using value_type = detail::error_domain_value_type<typename _base::value_type>; - - error_domain() = default; - error_domain(const error_domain &) = default; - error_domain(error_domain &&) = default; - error_domain &operator=(const error_domain &) = default; - error_domain &operator=(error_domain &&) = default; - ~error_domain() = default; - -protected: - virtual inline string_ref _do_message(const SYSTEM_ERROR2_NAMESPACE::status_code<void> &code) const noexcept override final; -}; -#else -template <class BaseStatusCodeDomain> using error_domain = BaseStatusCodeDomain; -#endif - -namespace detail -{ - using error_domain_value_system_code = error_domain_value_type<SYSTEM_ERROR2_NAMESPACE::system_code::value_type>; -} - -//! An erased status code -using error_code = SYSTEM_ERROR2_NAMESPACE::status_code<SYSTEM_ERROR2_NAMESPACE::erased<detail::error_domain_value_system_code>>; - - -template <class T> using result = OUTCOME_V2_NAMESPACE::experimental::erased_result<T, error_code>; -using OUTCOME_V2_NAMESPACE::success; -using OUTCOME_V2_NAMESPACE::failure; -using OUTCOME_V2_NAMESPACE::in_place_type; - -//! Choose an errc implementation -using SYSTEM_ERROR2_NAMESPACE::errc; - -//! Helper for constructing an error code from an errc -inline error_code generic_error(errc c); -#ifndef _WIN32 -//! Helper for constructing an error code from a POSIX errno -inline error_code posix_error(int c = errno); -#else -//! Helper for constructing an error code from a DWORD -inline error_code win32_error(SYSTEM_ERROR2_NAMESPACE::win32::DWORD c = SYSTEM_ERROR2_NAMESPACE::win32::GetLastError()); -//! Helper for constructing an error code from a NTSTATUS -inline error_code ntkernel_error(SYSTEM_ERROR2_NAMESPACE::win32::NTSTATUS c); -#endif - -namespace detail -{ - inline std::ostream &operator<<(std::ostream &s, const error_code &v) { return s << "afio::error_code(" << v.message().c_str() << ")"; } -} -inline error_code error_from_exception(std::exception_ptr &&ep = std::current_exception(), error_code not_matched = generic_error(errc::resource_unavailable_try_again)) noexcept -{ - if(!ep) - { - return generic_error(errc::success); - } - try - { - std::rethrow_exception(ep); - } - catch(const std::invalid_argument & /*unused*/) - { - ep = std::exception_ptr(); - return generic_error(errc::invalid_argument); - } - catch(const std::domain_error & /*unused*/) - { - ep = std::exception_ptr(); - return generic_error(errc::argument_out_of_domain); - } - catch(const std::length_error & /*unused*/) - { - ep = std::exception_ptr(); - return generic_error(errc::argument_list_too_long); - } - catch(const std::out_of_range & /*unused*/) - { - ep = std::exception_ptr(); - return generic_error(errc::result_out_of_range); - } - catch(const std::logic_error & /*unused*/) /* base class for this group */ - { - ep = std::exception_ptr(); - return generic_error(errc::invalid_argument); - } - catch(const std::system_error &e) /* also catches ios::failure */ - { - ep = std::exception_ptr(); - if(e.code().category() == std::generic_category()) - { - return generic_error(static_cast<errc>(static_cast<int>(e.code().value()))); - } - // Don't know this error code category, so fall through - } - catch(const std::overflow_error & /*unused*/) - { - ep = std::exception_ptr(); - return generic_error(errc::value_too_large); - } - catch(const std::range_error & /*unused*/) - { - ep = std::exception_ptr(); - return generic_error(errc::result_out_of_range); - } - catch(const std::runtime_error & /*unused*/) /* base class for this group */ - { - ep = std::exception_ptr(); - return generic_error(errc::resource_unavailable_try_again); - } - catch(const std::bad_alloc & /*unused*/) - { - ep = std::exception_ptr(); - return generic_error(errc::not_enough_memory); - } - catch(...) - { - } - return not_matched; -} - -AFIO_V2_NAMESPACE_END - - -#else // AFIO_EXPERIMENTAL_STATUS_CODE - - -// Bring in a result implementation based on std::error_code -#include "outcome/include/outcome.hpp" - -AFIO_V2_NAMESPACE_BEGIN - -namespace detail -{ - template <class Src> inline void append_path_info(Src &src, std::string &ret); - template <class Dest, class Src> inline void fill_failure_info(Dest &dest, const Src &src); -} - -struct error_info; -inline std::error_code make_error_code(error_info ei); - -/*! \struct error_info -\brief The cause of the failure of an operation in AFIO. -*/ -struct error_info -{ - friend inline std::error_code make_error_code(error_info ei); - template <class Src> friend inline void detail::append_path_info(Src &src, std::string &ret); - template <class Dest, class Src> friend inline void detail::fill_failure_info(Dest &dest, const Src &src); - -private: - // The error code for the failure - std::error_code ec; - -#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO - // The id of the thread where this failure occurred - uint32_t _thread_id{0}; - // The TLS path store entry - uint16_t _tls_path_id1{static_cast<uint16_t>(-1)}, _tls_path_id2{static_cast<uint16_t>(-1)}; - // The id of the relevant log entry in the AFIO log (if logging enabled) - size_t _log_id{static_cast<size_t>(-1)}; - -public: -#endif - - //! Default constructor - error_info() = default; - // Explicit construction from an error code - explicit inline error_info(std::error_code _ec); // NOLINT - /* NOTE TO SELF: The error_info constructor implementation is in handle.hpp as we need that - defined before we can do useful logging. - */ - //! Implicit construct from an error condition enum - OUTCOME_TEMPLATE(class ErrorCondEnum) - OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) - error_info(ErrorCondEnum &&v) // NOLINT - : error_info(make_error_code(std::forward<ErrorCondEnum>(v))) - { - } - - //! Retrieve the value of the error code - int value() const noexcept { return ec.value(); } - //! Retrieve any first path associated with this failure. Note this only works if called from the same thread as where the failure occurred. - inline filesystem::path path1() const; - //! Retrieve any second path associated with this failure. Note this only works if called from the same thread as where the failure occurred. - inline filesystem::path path2() const; - //! Retrieve a descriptive message for this failure, possibly with paths and stack backtraces. Extra detail only appears if called from the same thread as where the failure occurred. - inline std::string message() const; - /*! Throw this failure as a C++ exception. Firstly if the error code matches any of the standard - C++ exception types e.g. `bad_alloc`, we throw those types using the string from `message()` - where possible. We then will throw an `error` exception type. - */ - inline void throw_exception() const; -}; -inline bool operator==(const error_info &a, const error_info &b) -{ - return make_error_code(a) == make_error_code(b); -} -inline bool operator!=(const error_info &a, const error_info &b) -{ - return make_error_code(a) != make_error_code(b); -} -OUTCOME_TEMPLATE(class ErrorCondEnum) -OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) -inline bool operator==(const error_info &a, const ErrorCondEnum &b) -{ - return make_error_code(a) == std::error_condition(b); -} -OUTCOME_TEMPLATE(class ErrorCondEnum) -OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) -inline bool operator==(const ErrorCondEnum &a, const error_info &b) -{ - return std::error_condition(a) == make_error_code(b); -} -OUTCOME_TEMPLATE(class ErrorCondEnum) -OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) -inline bool operator!=(const error_info &a, const ErrorCondEnum &b) -{ - return make_error_code(a) != std::error_condition(b); -} -OUTCOME_TEMPLATE(class ErrorCondEnum) -OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) -inline bool operator!=(const ErrorCondEnum &a, const error_info &b) -{ - return std::error_condition(a) != make_error_code(b); -} -#ifndef NDEBUG -// Is trivial in all ways, except default constructibility -static_assert(std::is_trivially_copyable<error_info>::value, "error_info is not a trivially copyable!"); -#endif -inline std::ostream &operator<<(std::ostream &s, const error_info &v) -{ - if(make_error_code(v)) - { - return s << "afio::error_info(" << v.message() << ")"; - } - return s << "afio::error_info(null)"; -} -// Tell Outcome that error_info is to be treated as an error_code -inline std::error_code make_error_code(error_info ei) -{ - return ei.ec; -} -// Tell Outcome to call error_info::throw_exception() on no-value observation -inline void outcome_throw_as_system_error_with_payload(const error_info &ei) -{ - ei.throw_exception(); -} - -/*! \class error -\brief The exception type synthesised and thrown when an `afio::result` or `afio::outcome` is no-value observed. -*/ -class error : public filesystem::filesystem_error -{ -public: - error_info ei; - - //! Constructs from an error_info - explicit error(error_info _ei) - : filesystem::filesystem_error(_ei.message(), _ei.path1(), _ei.path2(), make_error_code(_ei)) - , ei(_ei) - { - } -}; - -inline void error_info::throw_exception() const -{ - std::string msg; - try - { - msg = message(); - } - catch(...) - { - } - OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(ec, msg); - throw error(*this); -} - -template <class T> using result = OUTCOME_V2_NAMESPACE::result<T, error_info>; -template <class T> using outcome = OUTCOME_V2_NAMESPACE::outcome<T, error_info>; -using OUTCOME_V2_NAMESPACE::success; -using OUTCOME_V2_NAMESPACE::failure; -inline error_info error_from_exception(std::exception_ptr &&ep = std::current_exception(), std::error_code not_matched = std::make_error_code(std::errc::resource_unavailable_try_again)) noexcept -{ - return error_info(OUTCOME_V2_NAMESPACE::error_from_exception(std::move(ep), not_matched)); -} -using OUTCOME_V2_NAMESPACE::in_place_type; - -static_assert(OUTCOME_V2_NAMESPACE::trait::has_error_code_v<error_info>, "error_info is not detected to be an error code"); - -//! Choose an errc implementation -using std::errc; - -//! Helper for constructing an error info from an errc -inline error_info generic_error(errc c) -{ - return error_info(make_error_code(c)); -} -#ifndef _WIN32 -//! Helper for constructing an error info from a POSIX errno -inline error_info posix_error(int c = errno) -{ - return error_info(std::error_code(c, std::system_category())); -} -#endif - -AFIO_V2_NAMESPACE_END -#endif // AFIO_EXPERIMENTAL_STATUS_CODE - - -#if AFIO_LOGGING_LEVEL - -/*! \todo TODO FIXME Replace in-memory log with memory map file backed log. -*/ -AFIO_V2_NAMESPACE_BEGIN - -//! The log used by AFIO -inline AFIO_DECL QUICKCPPLIB_NAMESPACE::ringbuffer_log::simple_ringbuffer_log<AFIO_LOGGING_MEMORY> &log() noexcept -{ - static QUICKCPPLIB_NAMESPACE::ringbuffer_log::simple_ringbuffer_log<AFIO_LOGGING_MEMORY> _log(static_cast<QUICKCPPLIB_NAMESPACE::ringbuffer_log::level>(AFIO_LOGGING_LEVEL)); -#ifdef AFIO_LOG_TO_OSTREAM - if(_log.immediate() != &AFIO_LOG_TO_OSTREAM) - { - _log.immediate(&AFIO_LOG_TO_OSTREAM); - } -#endif - return _log; -} -//! Enum for the log level -using log_level = QUICKCPPLIB_NAMESPACE::ringbuffer_log::level; -//! RAII class for temporarily adjusting the log level -class log_level_guard -{ - log_level _v; - -public: - log_level_guard() = delete; - log_level_guard(const log_level_guard &) = delete; - log_level_guard(log_level_guard &&) = delete; - log_level_guard &operator=(const log_level_guard &) = delete; - log_level_guard &operator=(log_level_guard &&) = delete; - explicit log_level_guard(log_level n) - : _v(log().log_level()) - { - log().log_level(n); - } - ~log_level_guard() { log().log_level(_v); } -}; - -// Infrastructure for recording the current path for when failure occurs -#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO -namespace detail -{ - // Our thread local store - struct tls_errored_results_t - { - uint32_t this_thread_id{QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id()}; - handle *current_handle{nullptr}; // The current handle for this thread. Changed via RAII via AFIO_LOG_FUNCTION_CALL, see below. - bool reentering_self{false}; // Prevents any failed call to current_path() by us reentering ourselves - - char paths[190][16]{}; // Last 190 chars of path - uint16_t pathidx{0}; - char *next(uint16_t &idx) - { - idx = pathidx++; - return paths[idx % 16]; // NOLINT - } - const char *get(uint16_t idx) const - { - // If the idx is stale, return not found - if(idx - pathidx >= 16) - { - return nullptr; - } - return paths[idx % 16]; // NOLINT - } - }; - inline tls_errored_results_t &tls_errored_results() - { -#if AFIO_THREAD_LOCAL_IS_CXX11 - static thread_local tls_errored_results_t v; - return v; -#else - static AFIO_THREAD_LOCAL tls_errored_results_t *v; - if(!v) - { - v = new tls_errored_results_t; - } - return *v; -#endif - } - template <bool _enabled> struct tls_current_handle_holder - { - handle *old{nullptr}; - bool enabled{false}; - tls_current_handle_holder() = delete; - tls_current_handle_holder(const tls_current_handle_holder &) = delete; - tls_current_handle_holder(tls_current_handle_holder &&) = delete; - tls_current_handle_holder &operator=(const tls_current_handle_holder &) = delete; - tls_current_handle_holder &operator=(tls_current_handle_holder &&) = delete; - explicit tls_current_handle_holder(const handle *h) - { - if(h != nullptr && log().log_level() >= log_level::error) - { - auto &tls = tls_errored_results(); - old = tls.current_handle; - tls.current_handle = const_cast<handle *>(h); // NOLINT - enabled = true; - } - } - ~tls_current_handle_holder() - { - if(enabled) - { - auto &tls = tls_errored_results(); - tls.current_handle = old; - } - } - }; - template <> struct tls_current_handle_holder<false> - { - tls_current_handle_holder() = delete; - tls_current_handle_holder(const tls_current_handle_holder &) = delete; - tls_current_handle_holder(tls_current_handle_holder &&) = delete; - tls_current_handle_holder &operator=(const tls_current_handle_holder &) = delete; - tls_current_handle_holder &operator=(tls_current_handle_holder &&) = delete; - ~tls_current_handle_holder() = default; - template <class T> explicit tls_current_handle_holder(T && /*unused*/) {} - }; -#define AFIO_LOG_INST_TO_TLS(inst) AFIO_V2_NAMESPACE::detail::tls_current_handle_holder<std::is_base_of<AFIO_V2_NAMESPACE::handle, std::decay_t<std::remove_pointer_t<decltype(inst)>>>::value> AFIO_UNIQUE_NAME(inst) -} // namespace detail -#else // AFIO_DISABLE_PATHS_IN_FAILURE_INFO -#define AFIO_LOG_INST_TO_TLS(inst) -#endif // AFIO_DISABLE_PATHS_IN_FAILURE_INFO - -AFIO_V2_NAMESPACE_END - -#ifndef AFIO_LOG_FATAL_TO_CERR -#include <cstdio> -#define AFIO_LOG_FATAL_TO_CERR(expr) \ - fprintf(stderr, "%s\n", (expr)); \ - fflush(stderr) -#endif -#endif - -#if AFIO_LOGGING_LEVEL >= 1 -#define AFIO_LOG_FATAL(inst, message) \ - { \ - AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::fatal, (message), AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 1U)) ? nullptr : __func__, __LINE__); \ - AFIO_LOG_FATAL_TO_CERR(message); \ - } -#else -#define AFIO_LOG_FATAL(inst, message) AFIO_LOG_FATAL_TO_CERR(message) -#endif -#if AFIO_LOGGING_LEVEL >= 2 -#define AFIO_LOG_ERROR(inst, message) \ - AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::error, (message), AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 2U)) ? nullptr : __func__, __LINE__) -#else -#define AFIO_LOG_ERROR(inst, message) -#endif -#if AFIO_LOGGING_LEVEL >= 3 -#define AFIO_LOG_WARN(inst, message) \ - AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::warn, (message), AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 3U)) ? nullptr : __func__, __LINE__) -#else -#define AFIO_LOG_WARN(inst, message) -#endif -#if AFIO_LOGGING_LEVEL >= 4 -#define AFIO_LOG_INFO(inst, message) \ - AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::info, (message), AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 4U)) ? nullptr : __func__, __LINE__) - -// Need to expand out our namespace into a string -#define AFIO_LOG_STRINGIFY9(s) #s "::" -#define AFIO_LOG_STRINGIFY8(s) AFIO_LOG_STRINGIFY9(s) -#define AFIO_LOG_STRINGIFY7(s) AFIO_LOG_STRINGIFY8(s) -#define AFIO_LOG_STRINGIFY6(s) AFIO_LOG_STRINGIFY7(s) -#define AFIO_LOG_STRINGIFY5(s) AFIO_LOG_STRINGIFY6(s) -#define AFIO_LOG_STRINGIFY4(s) AFIO_LOG_STRINGIFY5(s) -#define AFIO_LOG_STRINGIFY3(s) AFIO_LOG_STRINGIFY4(s) -#define AFIO_LOG_STRINGIFY2(s) AFIO_LOG_STRINGIFY3(s) -#define AFIO_LOG_STRINGIFY(s) AFIO_LOG_STRINGIFY2(s) -AFIO_V2_NAMESPACE_BEGIN -namespace detail -{ - // Returns the AFIO namespace as a string - inline span<char> afio_namespace_string() - { - static char buffer[64]; - static size_t length; - if(length) - return span<char>(buffer, length); - const char *src = AFIO_LOG_STRINGIFY(AFIO_V2_NAMESPACE); - char *bufferp = buffer; - for(; *src && (bufferp - buffer) < (ptrdiff_t) sizeof(buffer); src++) - { - if(*src != ' ') - *bufferp++ = *src; - } - *bufferp = 0; - length = bufferp - buffer; - return span<char>(buffer, length); - } - // Returns the Outcome namespace as a string - inline span<char> outcome_namespace_string() - { - static char buffer[64]; - static size_t length; - if(length) - return span<char>(buffer, length); - const char *src = AFIO_LOG_STRINGIFY(OUTCOME_V2_NAMESPACE); - char *bufferp = buffer; - for(; *src && (bufferp - buffer) < (ptrdiff_t) sizeof(buffer); src++) - { - if(*src != ' ') - *bufferp++ = *src; - } - *bufferp = 0; - length = bufferp - buffer; - return span<char>(buffer, length); - } - // Strips a __PRETTY_FUNCTION__ of all instances of AFIO_V2_NAMESPACE:: and AFIO_V2_NAMESPACE:: - inline void strip_pretty_function(char *out, size_t bytes, const char *in) - { - const span<char> remove1 = afio_namespace_string(); - const span<char> remove2 = outcome_namespace_string(); - for(--bytes; bytes && *in; --bytes) - { - if(!strncmp(in, remove1.data(), remove1.size())) - in += remove1.size(); - if(!strncmp(in, remove2.data(), remove2.size())) - in += remove2.size(); - *out++ = *in++; - } - *out = 0; - } - template <class T> void log_inst_to_info(T &&inst, const char *buffer) { AFIO_LOG_INFO(inst, buffer); } -} -AFIO_V2_NAMESPACE_END -#ifdef _MSC_VER -#define AFIO_LOG_FUNCTION_CALL(inst) \ - if(log().log_level() >= log_level::info) \ - { \ - char buffer[256]; \ - AFIO_V2_NAMESPACE::detail::strip_pretty_function(buffer, sizeof(buffer), __FUNCSIG__); \ - AFIO_V2_NAMESPACE::detail::log_inst_to_info(inst, buffer); \ - } \ - AFIO_LOG_INST_TO_TLS(inst) -#else -#define AFIO_LOG_FUNCTION_CALL(inst) \ - if(log().log_level() >= log_level::info) \ - { \ - char buffer[256]; \ - AFIO_V2_NAMESPACE::detail::strip_pretty_function(buffer, sizeof(buffer), __PRETTY_FUNCTION__); \ - AFIO_V2_NAMESPACE::detail::log_inst_to_info(inst, buffer); \ - } \ - AFIO_LOG_INST_TO_TLS(inst) -#endif -#else -#define AFIO_LOG_INFO(inst, message) -#define AFIO_LOG_FUNCTION_CALL(inst) AFIO_LOG_INST_TO_TLS(inst) -#endif -#if AFIO_LOGGING_LEVEL >= 5 -#define AFIO_LOG_DEBUG(inst, message) \ - AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::debug, AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 5U)) ? nullptr : __func__, __LINE__) -#else -#define AFIO_LOG_DEBUG(inst, message) -#endif -#if AFIO_LOGGING_LEVEL >= 6 -#define AFIO_LOG_ALL(inst, message) \ - AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::all, (message), AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 6U)) ? nullptr : __func__, __LINE__) -#else -#define AFIO_LOG_ALL(inst, message) -#endif - -AFIO_V2_NAMESPACE_BEGIN - -#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO -namespace detail -{ - template <class Src> inline void append_path_info(Src &src, std::string &ret) - { - if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == src._thread_id) - { - auto &tls = detail::tls_errored_results(); - const char *path1 = tls.get(src._tls_path_id1), *path2 = tls.get(src._tls_path_id2); - if(path1 != nullptr) - { - ret.append(" [path1 = "); - ret.append(path1); - if(path2 != nullptr) - { - ret.append(", path2 = "); - ret.append(path2); - } - ret.append("]"); - } - } -#if AFIO_LOGGING_LEVEL >= 2 - if(src._log_id != static_cast<uint32_t>(-1)) - { - if(log().valid(src._log_id)) - { - ret.append(" [location = "); - ret.append(location(log()[src._log_id])); - ret.append("]"); - } - } -#endif - } -} -#endif - -#if AFIO_EXPERIMENTAL_STATUS_CODE -#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO -template <class BaseStatusCodeDomain> inline typename error_domain<BaseStatusCodeDomain>::string_ref error_domain<BaseStatusCodeDomain>::_do_message(const SYSTEM_ERROR2_NAMESPACE::status_code<void> &code) const noexcept // NOLINT -{ - assert(code.domain() == *this); - const auto &v = static_cast<const SYSTEM_ERROR2_NAMESPACE::status_code<error_domain> &>(code); // NOLINT - std::string ret = _base::_do_message(code).c_str(); - detail::append_path_info(v.value(), ret); - char *p = (char *) malloc(ret.size() + 1); - if(p == nullptr) - { - return string_ref("Failed to allocate memory to store error string"); - } - memcpy(p, ret.c_str(), ret.size() + 1); - return atomic_refcounted_string_ref(p, ret.size()); -} -#endif - -#else // AFIO_EXPERIMENTAL_STATUS_CODE - -inline filesystem::path error_info::path1() const -{ -#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO - if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == _thread_id) - { - auto &tls = detail::tls_errored_results(); - const char *path1 = tls.get(_tls_path_id1); - if(path1 != nullptr) - { - return filesystem::path(path1); - } - } -#endif - return {}; -} -inline filesystem::path error_info::path2() const -{ -#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO - if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == _thread_id) - { - auto &tls = detail::tls_errored_results(); - const char *path2 = tls.get(_tls_path_id2); - if(path2 != nullptr) - { - return filesystem::path(path2); - } - } -#endif - return {}; -} -inline std::string error_info::message() const -{ - std::string ret(ec.message()); -#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO - detail::append_path_info(*this, ret); -#endif - return ret; -} - -#endif // AFIO_EXPERIMENTAL_STATUS_CODE - -AFIO_V2_NAMESPACE_END - - -#include <ctime> // for struct timespec - AFIO_V2_NAMESPACE_BEGIN namespace detail diff --git a/include/afio/v2.0/handle.hpp b/include/afio/v2.0/handle.hpp index f51c3fd2..57f7618f 100644 --- a/include/afio/v2.0/handle.hpp +++ b/include/afio/v2.0/handle.hpp @@ -27,6 +27,7 @@ Distributed under the Boost Software License, Version 1.0. #include "deadline.h" #include "native_handle_type.hpp" +#include "status_code.hpp" #include <algorithm> // for std::count #include <cassert> diff --git a/include/afio/v2.0/logging.hpp b/include/afio/v2.0/logging.hpp new file mode 100644 index 00000000..851678b7 --- /dev/null +++ b/include/afio/v2.0/logging.hpp @@ -0,0 +1,334 @@ +/* Configures AFIO +(C) 2015-2018 Niall Douglas <http://www.nedproductions.biz/> (24 commits) +File Created: Dec 2015 + + +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 in the accompanying file +Licence.txt or 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. + + +Distributed under the Boost Software License, Version 1.0. + (See accompanying file Licence.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ + +#ifndef AFIO_LOGGING_HPP +#define AFIO_LOGGING_HPP + +#include "config.hpp" + +#if AFIO_LOGGING_LEVEL + +/*! \todo TODO FIXME Replace in-memory log with memory map file backed log. +*/ +AFIO_V2_NAMESPACE_BEGIN + +//! The log used by AFIO +inline AFIO_DECL QUICKCPPLIB_NAMESPACE::ringbuffer_log::simple_ringbuffer_log<AFIO_LOGGING_MEMORY> &log() noexcept +{ + static QUICKCPPLIB_NAMESPACE::ringbuffer_log::simple_ringbuffer_log<AFIO_LOGGING_MEMORY> _log(static_cast<QUICKCPPLIB_NAMESPACE::ringbuffer_log::level>(AFIO_LOGGING_LEVEL)); +#ifdef AFIO_LOG_TO_OSTREAM + if(_log.immediate() != &AFIO_LOG_TO_OSTREAM) + { + _log.immediate(&AFIO_LOG_TO_OSTREAM); + } +#endif + return _log; +} +//! Enum for the log level +using log_level = QUICKCPPLIB_NAMESPACE::ringbuffer_log::level; +//! RAII class for temporarily adjusting the log level +class log_level_guard +{ + log_level _v; + +public: + log_level_guard() = delete; + log_level_guard(const log_level_guard &) = delete; + log_level_guard(log_level_guard &&) = delete; + log_level_guard &operator=(const log_level_guard &) = delete; + log_level_guard &operator=(log_level_guard &&) = delete; + explicit log_level_guard(log_level n) + : _v(log().log_level()) + { + log().log_level(n); + } + ~log_level_guard() { log().log_level(_v); } +}; + +// Infrastructure for recording the current path for when failure occurs +#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO +namespace detail +{ + // Our thread local store + struct tls_errored_results_t + { + uint32_t this_thread_id{QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id()}; + handle *current_handle{nullptr}; // The current handle for this thread. Changed via RAII via AFIO_LOG_FUNCTION_CALL, see below. + bool reentering_self{false}; // Prevents any failed call to current_path() by us reentering ourselves + + char paths[190][16]{}; // Last 190 chars of path + uint16_t pathidx{0}; + char *next(uint16_t &idx) + { + idx = pathidx++; + return paths[idx % 16]; // NOLINT + } + const char *get(uint16_t idx) const + { + // If the idx is stale, return not found + if(idx - pathidx >= 16) + { + return nullptr; + } + return paths[idx % 16]; // NOLINT + } + }; + inline tls_errored_results_t &tls_errored_results() + { +#if AFIO_THREAD_LOCAL_IS_CXX11 + static thread_local tls_errored_results_t v; + return v; +#else + static AFIO_THREAD_LOCAL tls_errored_results_t *v; + if(!v) + { + v = new tls_errored_results_t; + } + return *v; +#endif + } + template <bool _enabled> struct tls_current_handle_holder + { + handle *old{nullptr}; + bool enabled{false}; + tls_current_handle_holder() = delete; + tls_current_handle_holder(const tls_current_handle_holder &) = delete; + tls_current_handle_holder(tls_current_handle_holder &&) = delete; + tls_current_handle_holder &operator=(const tls_current_handle_holder &) = delete; + tls_current_handle_holder &operator=(tls_current_handle_holder &&) = delete; + explicit tls_current_handle_holder(const handle *h) + { + if(h != nullptr && log().log_level() >= log_level::error) + { + auto &tls = tls_errored_results(); + old = tls.current_handle; + tls.current_handle = const_cast<handle *>(h); // NOLINT + enabled = true; + } + } + ~tls_current_handle_holder() + { + if(enabled) + { + auto &tls = tls_errored_results(); + tls.current_handle = old; + } + } + }; + template <> struct tls_current_handle_holder<false> + { + tls_current_handle_holder() = delete; + tls_current_handle_holder(const tls_current_handle_holder &) = delete; + tls_current_handle_holder(tls_current_handle_holder &&) = delete; + tls_current_handle_holder &operator=(const tls_current_handle_holder &) = delete; + tls_current_handle_holder &operator=(tls_current_handle_holder &&) = delete; + ~tls_current_handle_holder() = default; + template <class T> explicit tls_current_handle_holder(T && /*unused*/) {} + }; +#define AFIO_LOG_INST_TO_TLS(inst) ::AFIO_V2_NAMESPACE::detail::tls_current_handle_holder<std::is_base_of<::AFIO_V2_NAMESPACE::handle, std::decay_t<std::remove_pointer_t<decltype(inst)>>>::value> AFIO_UNIQUE_NAME(inst) +} // namespace detail +#else // AFIO_DISABLE_PATHS_IN_FAILURE_INFO +#define AFIO_LOG_INST_TO_TLS(inst) +#endif // AFIO_DISABLE_PATHS_IN_FAILURE_INFO + +AFIO_V2_NAMESPACE_END + +#ifndef AFIO_LOG_FATAL_TO_CERR +#include <cstdio> +#define AFIO_LOG_FATAL_TO_CERR(expr) \ + fprintf(stderr, "%s\n", (expr)); \ + fflush(stderr) +#endif +#endif // AFIO_LOGGING_LEVEL + +#if AFIO_LOGGING_LEVEL >= 1 +#define AFIO_LOG_FATAL(inst, message) \ + { \ + ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::fatal, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 1U)) ? nullptr : __func__, __LINE__); \ + AFIO_LOG_FATAL_TO_CERR(message); \ + } +#else +#define AFIO_LOG_FATAL(inst, message) AFIO_LOG_FATAL_TO_CERR(message) +#endif +#if AFIO_LOGGING_LEVEL >= 2 +#define AFIO_LOG_ERROR(inst, message) \ + ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::error, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 2U)) ? nullptr : __func__, __LINE__) +#else +#define AFIO_LOG_ERROR(inst, message) +#endif +#if AFIO_LOGGING_LEVEL >= 3 +#define AFIO_LOG_WARN(inst, message) \ + ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::warn, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 3U)) ? nullptr : __func__, __LINE__) +#else +#define AFIO_LOG_WARN(inst, message) +#endif +#if AFIO_LOGGING_LEVEL >= 4 +#define AFIO_LOG_INFO(inst, message) \ + ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::info, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 4U)) ? nullptr : __func__, __LINE__) + +// Need to expand out our namespace into a string +#define AFIO_LOG_STRINGIFY9(s) #s "::" +#define AFIO_LOG_STRINGIFY8(s) AFIO_LOG_STRINGIFY9(s) +#define AFIO_LOG_STRINGIFY7(s) AFIO_LOG_STRINGIFY8(s) +#define AFIO_LOG_STRINGIFY6(s) AFIO_LOG_STRINGIFY7(s) +#define AFIO_LOG_STRINGIFY5(s) AFIO_LOG_STRINGIFY6(s) +#define AFIO_LOG_STRINGIFY4(s) AFIO_LOG_STRINGIFY5(s) +#define AFIO_LOG_STRINGIFY3(s) AFIO_LOG_STRINGIFY4(s) +#define AFIO_LOG_STRINGIFY2(s) AFIO_LOG_STRINGIFY3(s) +#define AFIO_LOG_STRINGIFY(s) AFIO_LOG_STRINGIFY2(s) +AFIO_V2_NAMESPACE_BEGIN +namespace detail +{ + // Returns the AFIO namespace as a string + inline span<char> afio_namespace_string() + { + static char buffer[64]; + static size_t length; + if(length) + return span<char>(buffer, length); + const char *src = AFIO_LOG_STRINGIFY(AFIO_V2_NAMESPACE); + char *bufferp = buffer; + for(; *src && (bufferp - buffer) < (ptrdiff_t) sizeof(buffer); src++) + { + if(*src != ' ') + *bufferp++ = *src; + } + *bufferp = 0; + length = bufferp - buffer; + return span<char>(buffer, length); + } + // Returns the Outcome namespace as a string + inline span<char> outcome_namespace_string() + { + static char buffer[64]; + static size_t length; + if(length) + return span<char>(buffer, length); + const char *src = AFIO_LOG_STRINGIFY(OUTCOME_V2_NAMESPACE); + char *bufferp = buffer; + for(; *src && (bufferp - buffer) < (ptrdiff_t) sizeof(buffer); src++) + { + if(*src != ' ') + *bufferp++ = *src; + } + *bufferp = 0; + length = bufferp - buffer; + return span<char>(buffer, length); + } + // Strips a __PRETTY_FUNCTION__ of all instances of ::AFIO_V2_NAMESPACE:: and ::AFIO_V2_NAMESPACE:: + inline void strip_pretty_function(char *out, size_t bytes, const char *in) + { + const span<char> remove1 = afio_namespace_string(); + const span<char> remove2 = outcome_namespace_string(); + for(--bytes; bytes && *in; --bytes) + { + if(!strncmp(in, remove1.data(), remove1.size())) + in += remove1.size(); + if(!strncmp(in, remove2.data(), remove2.size())) + in += remove2.size(); + *out++ = *in++; + } + *out = 0; + } + template <class T> void log_inst_to_info(T &&inst, const char *buffer) { AFIO_LOG_INFO(inst, buffer); } +} +AFIO_V2_NAMESPACE_END +#ifdef _MSC_VER +#define AFIO_LOG_FUNCTION_CALL(inst) \ + if(log().log_level() >= log_level::info) \ + { \ + char buffer[256]; \ + ::AFIO_V2_NAMESPACE::detail::strip_pretty_function(buffer, sizeof(buffer), __FUNCSIG__); \ + ::AFIO_V2_NAMESPACE::detail::log_inst_to_info(inst, buffer); \ + } \ + AFIO_LOG_INST_TO_TLS(inst) +#else +#define AFIO_LOG_FUNCTION_CALL(inst) \ + if(log().log_level() >= log_level::info) \ + { \ + char buffer[256]; \ + ::AFIO_V2_NAMESPACE::detail::strip_pretty_function(buffer, sizeof(buffer), __PRETTY_FUNCTION__); \ + ::AFIO_V2_NAMESPACE::detail::log_inst_to_info(inst, buffer); \ + } \ + AFIO_LOG_INST_TO_TLS(inst) +#endif +#else +#define AFIO_LOG_INFO(inst, message) +#define AFIO_LOG_FUNCTION_CALL(inst) AFIO_LOG_INST_TO_TLS(inst) +#endif +#if AFIO_LOGGING_LEVEL >= 5 +#define AFIO_LOG_DEBUG(inst, message) \ + ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::debug, ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 5U)) ? nullptr : __func__, __LINE__) +#else +#define AFIO_LOG_DEBUG(inst, message) +#endif +#if AFIO_LOGGING_LEVEL >= 6 +#define AFIO_LOG_ALL(inst, message) \ + ::AFIO_V2_NAMESPACE::log().emplace_back(QUICKCPPLIB_NAMESPACE::ringbuffer_log::level::all, (message), ::AFIO_V2_NAMESPACE::detail::unsigned_integer_cast<unsigned>(inst), QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id(), (AFIO_LOG_BACKTRACE_LEVELS & (1U << 6U)) ? nullptr : __func__, __LINE__) +#else +#define AFIO_LOG_ALL(inst, message) +#endif + +AFIO_V2_NAMESPACE_BEGIN + +#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO +namespace detail +{ + template <class Src> inline void append_path_info(Src &src, std::string &ret) + { + if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == src._thread_id) + { + auto &tls = detail::tls_errored_results(); + const char *path1 = tls.get(src._tls_path_id1), *path2 = tls.get(src._tls_path_id2); + if(path1 != nullptr) + { + ret.append(" [path1 = "); + ret.append(path1); + if(path2 != nullptr) + { + ret.append(", path2 = "); + ret.append(path2); + } + ret.append("]"); + } + } +#if AFIO_LOGGING_LEVEL >= 2 + if(src._log_id != static_cast<uint32_t>(-1)) + { + if(log().valid(src._log_id)) + { + ret.append(" [location = "); + ret.append(location(log()[src._log_id])); + ret.append("]"); + } + } +#endif + } +} +#endif + +AFIO_V2_NAMESPACE_END + + +#endif diff --git a/include/afio/v2.0/status_code.hpp b/include/afio/v2.0/status_code.hpp new file mode 100644 index 00000000..93185c77 --- /dev/null +++ b/include/afio/v2.0/status_code.hpp @@ -0,0 +1,464 @@ +/* Configures AFIO +(C) 2018 Niall Douglas <http://www.nedproductions.biz/> (24 commits) +File Created: June 2018 + + +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 in the accompanying file +Licence.txt or 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. + + +Distributed under the Boost Software License, Version 1.0. + (See accompanying file Licence.txt or copy at + http://www.boost.org/LICENSE_1_0.txt) +*/ + +#ifndef AFIO_STATUS_CODE_HPP +#define AFIO_STATUS_CODE_HPP + +#include "logging.hpp" + +/* The SG14 status code implementation is quite profoundly different to the +error code implementation. In the error code implementation, std::error_code +is fixed by the standard library, so we wrap it with extra metadata into +an error_info type. error_info then constructs off a code and a code domain +tag. + +Status code, on the other hand, is templated and is designed for custom +domains which can set arbitrary payloads. So we define custom domains and +status codes for AFIO with these combinations: + +- win32_error{ DWORD } +- ntkernel_error{ LONG } +- posix_error{ int } +- generic_error{ errc } + +Each of these is a separate AFIO custom status code domain. We also define +an erased form of these custom domains, and that is typedefed to +error_domain<intptr_t>::value_type. + +This design ensure that AFIO can be configured into either std-based error +handling or SG14 experimental status code handling. It defaults to the latter +as that (a) enables safe header only AFIO on Windows (b) produces better codegen +(c) drags in far fewer STL headers. +*/ + +#if AFIO_EXPERIMENTAL_STATUS_CODE + +// Bring in a result implementation based on status_code +#include "outcome/include/outcome/experimental/status_result.hpp" +#include "outcome/include/outcome/try.hpp" + +AFIO_V2_NAMESPACE_BEGIN + +#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO + +namespace detail +{ + template <class T> struct error_domain_value_type + { + //! \brief The type of code + T sc{}; + + // The id of the thread where this failure occurred + uint32_t _thread_id{0}; + // The TLS path store entry + uint16_t _tls_path_id1{static_cast<uint16_t>(-1)}, _tls_path_id2{static_cast<uint16_t>(-1)}; + // The id of the relevant log entry in the AFIO log (if logging enabled) + size_t _log_id{static_cast<size_t>(-1)}; + + //! Default construction + error_domain_value_type() = default; + + //! Implicitly constructs an instance + constexpr inline error_domain_value_type(T _sc) + : sc(_sc) + { + } // NOLINT + + //! Compares to a T + constexpr bool operator==(const T &b) const noexcept { return sc == b; } + }; +} + +/*! \class error_domain +\brief The SG14 status code domain for errors in AFIO. +*/ +template <class BaseStatusCodeDomain> class error_domain : public BaseStatusCodeDomain +{ + friend class SYSTEM_ERROR2_NAMESPACE::status_code<error_domain>; + using _base = BaseStatusCodeDomain; + +public: + using string_ref = typename BaseStatusCodeDomain::string_ref; + using atomic_refcounted_string_ref = typename BaseStatusCodeDomain::atomic_refcounted_string_ref; + + //! \brief The value type of errors in AFIO + using value_type = detail::error_domain_value_type<typename _base::value_type>; + + error_domain() = default; + error_domain(const error_domain &) = default; + error_domain(error_domain &&) = default; + error_domain &operator=(const error_domain &) = default; + error_domain &operator=(error_domain &&) = default; + ~error_domain() = default; + +protected: + virtual inline string_ref _do_message(const SYSTEM_ERROR2_NAMESPACE::status_code<void> &code) const noexcept override final + { + assert(code.domain() == *this); + const auto &v = static_cast<const SYSTEM_ERROR2_NAMESPACE::status_code<error_domain> &>(code); // NOLINT + std::string ret = _base::_do_message(code).c_str(); + detail::append_path_info(v.value(), ret); + char *p = (char *) malloc(ret.size() + 1); + if(p == nullptr) + { + return string_ref("Failed to allocate memory to store error string"); + } + memcpy(p, ret.c_str(), ret.size() + 1); + return atomic_refcounted_string_ref(p, ret.size()); + } +}; + +#else +template <class BaseStatusCodeDomain> using error_domain = BaseStatusCodeDomain; +#endif + +namespace detail +{ + using error_domain_value_system_code = error_domain_value_type<SYSTEM_ERROR2_NAMESPACE::system_code::value_type>; +} + +//! An erased status code +using error_code = SYSTEM_ERROR2_NAMESPACE::status_code<SYSTEM_ERROR2_NAMESPACE::erased<detail::error_domain_value_system_code>>; + + +template <class T> using result = OUTCOME_V2_NAMESPACE::experimental::erased_result<T, error_code>; +using OUTCOME_V2_NAMESPACE::success; +using OUTCOME_V2_NAMESPACE::failure; +using OUTCOME_V2_NAMESPACE::in_place_type; + +//! Choose an errc implementation +using SYSTEM_ERROR2_NAMESPACE::errc; + +//! Helper for constructing an error code from an errc +inline error_code generic_error(errc c); +#ifndef _WIN32 +//! Helper for constructing an error code from a POSIX errno +inline error_code posix_error(int c = errno); +#else +//! Helper for constructing an error code from a DWORD +inline error_code win32_error(SYSTEM_ERROR2_NAMESPACE::win32::DWORD c = SYSTEM_ERROR2_NAMESPACE::win32::GetLastError()); +//! Helper for constructing an error code from a NTSTATUS +inline error_code ntkernel_error(SYSTEM_ERROR2_NAMESPACE::win32::NTSTATUS c); +#endif + +namespace detail +{ + inline std::ostream &operator<<(std::ostream &s, const error_code &v) { return s << "afio::error_code(" << v.message().c_str() << ")"; } +} +inline error_code error_from_exception(std::exception_ptr &&ep = std::current_exception(), error_code not_matched = generic_error(errc::resource_unavailable_try_again)) noexcept +{ + if(!ep) + { + return generic_error(errc::success); + } + try + { + std::rethrow_exception(ep); + } + catch(const std::invalid_argument & /*unused*/) + { + ep = std::exception_ptr(); + return generic_error(errc::invalid_argument); + } + catch(const std::domain_error & /*unused*/) + { + ep = std::exception_ptr(); + return generic_error(errc::argument_out_of_domain); + } + catch(const std::length_error & /*unused*/) + { + ep = std::exception_ptr(); + return generic_error(errc::argument_list_too_long); + } + catch(const std::out_of_range & /*unused*/) + { + ep = std::exception_ptr(); + return generic_error(errc::result_out_of_range); + } + catch(const std::logic_error & /*unused*/) /* base class for this group */ + { + ep = std::exception_ptr(); + return generic_error(errc::invalid_argument); + } + catch(const std::system_error &e) /* also catches ios::failure */ + { + ep = std::exception_ptr(); + if(e.code().category() == std::generic_category()) + { + return generic_error(static_cast<errc>(static_cast<int>(e.code().value()))); + } + // Don't know this error code category, so fall through + } + catch(const std::overflow_error & /*unused*/) + { + ep = std::exception_ptr(); + return generic_error(errc::value_too_large); + } + catch(const std::range_error & /*unused*/) + { + ep = std::exception_ptr(); + return generic_error(errc::result_out_of_range); + } + catch(const std::runtime_error & /*unused*/) /* base class for this group */ + { + ep = std::exception_ptr(); + return generic_error(errc::resource_unavailable_try_again); + } + catch(const std::bad_alloc & /*unused*/) + { + ep = std::exception_ptr(); + return generic_error(errc::not_enough_memory); + } + catch(...) + { + } + return not_matched; +} + +AFIO_V2_NAMESPACE_END + + +#else // AFIO_EXPERIMENTAL_STATUS_CODE + + +// Bring in a result implementation based on std::error_code +#include "outcome/include/outcome.hpp" + +AFIO_V2_NAMESPACE_BEGIN + +namespace detail +{ + template <class Dest, class Src> inline void fill_failure_info(Dest &dest, const Src &src); +} + +struct error_info; +inline std::error_code make_error_code(error_info ei); + +/*! \struct error_info +\brief The cause of the failure of an operation in AFIO. +*/ +struct error_info +{ + friend inline std::error_code make_error_code(error_info ei); + template <class Src> friend inline void detail::append_path_info(Src &src, std::string &ret); + template <class Dest, class Src> friend inline void detail::fill_failure_info(Dest &dest, const Src &src); + +private: + // The error code for the failure + std::error_code ec; + +#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO + // The id of the thread where this failure occurred + uint32_t _thread_id{0}; + // The TLS path store entry + uint16_t _tls_path_id1{static_cast<uint16_t>(-1)}, _tls_path_id2{static_cast<uint16_t>(-1)}; + // The id of the relevant log entry in the AFIO log (if logging enabled) + size_t _log_id{static_cast<size_t>(-1)}; + +public: +#endif + + //! Default constructor + error_info() = default; + // Explicit construction from an error code + explicit inline error_info(std::error_code _ec); // NOLINT + /* NOTE TO SELF: The error_info constructor implementation is in handle.hpp as we need that + defined before we can do useful logging. + */ + //! Implicit construct from an error condition enum + OUTCOME_TEMPLATE(class ErrorCondEnum) + OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) + error_info(ErrorCondEnum &&v) // NOLINT + : error_info(make_error_code(std::forward<ErrorCondEnum>(v))) + { + } + + //! Retrieve the value of the error code + int value() const noexcept { return ec.value(); } + //! Retrieve any first path associated with this failure. Note this only works if called from the same thread as where the failure occurred. + inline filesystem::path path1() const + { +#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO + if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == _thread_id) + { + auto &tls = detail::tls_errored_results(); + const char *path1 = tls.get(_tls_path_id1); + if(path1 != nullptr) + { + return filesystem::path(path1); + } + } +#endif + return {}; + } + //! Retrieve any second path associated with this failure. Note this only works if called from the same thread as where the failure occurred. + inline filesystem::path path2() const + { +#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO + if(QUICKCPPLIB_NAMESPACE::utils::thread::this_thread_id() == _thread_id) + { + auto &tls = detail::tls_errored_results(); + const char *path2 = tls.get(_tls_path_id2); + if(path2 != nullptr) + { + return filesystem::path(path2); + } + } +#endif + return {}; + } + //! Retrieve a descriptive message for this failure, possibly with paths and stack backtraces. Extra detail only appears if called from the same thread as where the failure occurred. + inline std::string message() const + { + std::string ret(ec.message()); +#ifndef AFIO_DISABLE_PATHS_IN_FAILURE_INFO + detail::append_path_info(*this, ret); +#endif + return ret; + } + /*! Throw this failure as a C++ exception. Firstly if the error code matches any of the standard + C++ exception types e.g. `bad_alloc`, we throw those types using the string from `message()` + where possible. We then will throw an `error` exception type. + */ + inline void throw_exception() const; +}; +inline bool operator==(const error_info &a, const error_info &b) +{ + return make_error_code(a) == make_error_code(b); +} +inline bool operator!=(const error_info &a, const error_info &b) +{ + return make_error_code(a) != make_error_code(b); +} +OUTCOME_TEMPLATE(class ErrorCondEnum) +OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) +inline bool operator==(const error_info &a, const ErrorCondEnum &b) +{ + return make_error_code(a) == std::error_condition(b); +} +OUTCOME_TEMPLATE(class ErrorCondEnum) +OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) +inline bool operator==(const ErrorCondEnum &a, const error_info &b) +{ + return std::error_condition(a) == make_error_code(b); +} +OUTCOME_TEMPLATE(class ErrorCondEnum) +OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) +inline bool operator!=(const error_info &a, const ErrorCondEnum &b) +{ + return make_error_code(a) != std::error_condition(b); +} +OUTCOME_TEMPLATE(class ErrorCondEnum) +OUTCOME_TREQUIRES(OUTCOME_TPRED(std::is_error_condition_enum<ErrorCondEnum>::value)) +inline bool operator!=(const ErrorCondEnum &a, const error_info &b) +{ + return std::error_condition(a) != make_error_code(b); +} +#ifndef NDEBUG +// Is trivial in all ways, except default constructibility +static_assert(std::is_trivially_copyable<error_info>::value, "error_info is not a trivially copyable!"); +#endif +inline std::ostream &operator<<(std::ostream &s, const error_info &v) +{ + if(make_error_code(v)) + { + return s << "afio::error_info(" << v.message() << ")"; + } + return s << "afio::error_info(null)"; +} +// Tell Outcome that error_info is to be treated as an error_code +inline std::error_code make_error_code(error_info ei) +{ + return ei.ec; +} +// Tell Outcome to call error_info::throw_exception() on no-value observation +inline void outcome_throw_as_system_error_with_payload(const error_info &ei) +{ + ei.throw_exception(); +} + +/*! \class error +\brief The exception type synthesised and thrown when an `afio::result` or `afio::outcome` is no-value observed. +*/ +class error : public filesystem::filesystem_error +{ +public: + error_info ei; + + //! Constructs from an error_info + explicit error(error_info _ei) + : filesystem::filesystem_error(_ei.message(), _ei.path1(), _ei.path2(), make_error_code(_ei)) + , ei(_ei) + { + } +}; + +inline void error_info::throw_exception() const +{ + std::string msg; + try + { + msg = message(); + } + catch(...) + { + } + OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(ec, msg); + throw error(*this); +} + +template <class T> using result = OUTCOME_V2_NAMESPACE::result<T, error_info>; +template <class T> using outcome = OUTCOME_V2_NAMESPACE::outcome<T, error_info>; +using OUTCOME_V2_NAMESPACE::success; +using OUTCOME_V2_NAMESPACE::failure; +inline error_info error_from_exception(std::exception_ptr &&ep = std::current_exception(), std::error_code not_matched = std::make_error_code(std::errc::resource_unavailable_try_again)) noexcept +{ + return error_info(OUTCOME_V2_NAMESPACE::error_from_exception(std::move(ep), not_matched)); +} +using OUTCOME_V2_NAMESPACE::in_place_type; + +static_assert(OUTCOME_V2_NAMESPACE::trait::has_error_code_v<error_info>, "error_info is not detected to be an error code"); + +//! Choose an errc implementation +using std::errc; + +//! Helper for constructing an error info from an errc +inline error_info generic_error(errc c) +{ + return error_info(make_error_code(c)); +} +#ifndef _WIN32 +//! Helper for constructing an error info from a POSIX errno +inline error_info posix_error(int c = errno) +{ + return error_info(std::error_code(c, std::system_category())); +} +#endif + +AFIO_V2_NAMESPACE_END + +#endif // AFIO_EXPERIMENTAL_STATUS_CODE + + +#endif |