diff --git a/src/global/global.cpp b/src/global/global.cpp index 2cdb508d1..2aa5792fe 100644 --- a/src/global/global.cpp +++ b/src/global/global.cpp @@ -12,7 +12,8 @@ #include -#include "../io/io.h" //defines chprintf +#include "../io/io.h" //defines chprintf +#include "../utils/error_handling.h" // defines ASSERT /* Global variables */ Real gama; // Ratio of specific heats @@ -33,6 +34,7 @@ void Set_Gammas(Real gamma_in) { // set gamma gama = gamma_in; + CHOLLA_ASSERT(gama > 1.0, "Gamma must be greater than one."); } /*! \fn double get_time(void) diff --git a/src/io/io.cpp b/src/io/io.cpp index c9817cc6d..09ffd0d17 100644 --- a/src/io/io.cpp +++ b/src/io/io.cpp @@ -2788,11 +2788,10 @@ void Ensure_Outdir_Exists(std::string outdir) // to a directory (it's unclear from docs whether err-code is set in that // case) if (err_code or not std::filesystem::is_directory(without_file_prefix)) { - chprintf( + CHOLLA_ERROR( "something went wrong while trying to create the path to the " - "output-dir: %s\n", + "output-dir: %s", outdir.c_str()); - chexit(1); } } } diff --git a/src/utils/error_handling.cpp b/src/utils/error_handling.cpp index 040c1885b..5a7bad073 100644 --- a/src/utils/error_handling.cpp +++ b/src/utils/error_handling.cpp @@ -1,12 +1,14 @@ #include "../utils/error_handling.h" #include +#include +#include #include #include #ifdef MPI_CHOLLA - #include -void chexit(int code) + #include "../mpi/mpi_routines.h" +[[noreturn]] void chexit(int code) { if (code == 0) { /*exit normally*/ @@ -20,14 +22,14 @@ void chexit(int code) } } #else /*MPI_CHOLLA*/ -void chexit(int code) +[[noreturn]] void chexit(int code) { /*exit using code*/ exit(code); } #endif /*MPI_CHOLLA*/ -void Check_Configuration(parameters const &P) +void Check_Configuration(parameters const& P) { // General Checks // ============== @@ -56,13 +58,13 @@ void Check_Configuration(parameters const &P) #endif // Only one integrator check // Check the boundary conditions - auto Check_Boundary = [](int const &boundary, std::string const &direction) { + auto Check_Boundary = [](int const& boundary, std::string const& direction) { bool is_allowed_bc = boundary >= 0 and boundary <= 4; - std::string const error_message = - "WARNING: Possibly invalid boundary conditions for direction: " + direction + - " flag: " + std::to_string(boundary) + - ". Must select between 0 (no boundary), 1 (periodic), 2 (reflective), 3 (transmissive), 4 (custom), 5 (mpi)."; - assert(is_allowed_bc && error_message.c_str()); + CHOLLA_ASSERT(is_allowed_bc, + "WARNING: Possibly invalid boundary conditions for direction: %s flag: %d. Must " + "select between 0 (no boundary), 1 (periodic), 2 (reflective), 3 (transmissive), " + "4 (custom), 5 (mpi).", + direction.c_str(), boundary); }; Check_Boundary(P.xl_bcnd, "xl_bcnd"); Check_Boundary(P.xu_bcnd, "xu_bcnd"); @@ -82,9 +84,6 @@ void Check_Configuration(parameters const &P) #endif //! PRECISION static_assert(PRECISION == 2, "PRECISION must be 2. Single precision is not currently supported"); - // Check that gamma, the ratio of specific heats, is greater than 1 - assert(::gama <= 1.0 and "Gamma must be greater than one."); - // MHD Checks // ========== #ifdef MHD @@ -126,3 +125,62 @@ void Check_Configuration(parameters const &P) #endif // MHD } + +// NOLINTNEXTLINE(cert-dcl50-cpp) +[[noreturn]] void Abort_With_Err_(const char* func_name, const char* file_name, int line_num, const char* msg, ...) +{ + // considerations when using MPI: + // - all processes must execute this function to catch errors that happen on + // just one process + // - to handle cases where all processes encounter the same error, we + // pre-buffer the error message (so that the output remains legible) + + // since we are aborting, it's OK that this isn't the most optimized + + // prepare some info for the error message header + const char* santized_func_name = (func_name == nullptr) ? "{unspecified}" : func_name; + +#ifdef MPI_CHOLLA + std::string proc_info = std::to_string(procID) + " / " + std::to_string(nproc) + " (using MPI)"; +#else + std::string proc_info = "0 / 1 (NOT using MPI)"; +#endif + + // prepare the formatted message + std::string msg_buf; + if (msg == nullptr) { + msg_buf = "{nullptr encountered instead of error message}"; + } else { + std::va_list args, args_copy; + va_start(args, msg); + va_copy(args_copy, args); + + std::size_t bufsize_without_terminator = std::vsnprintf(nullptr, 0, msg, args); + va_end(args); + + // NOTE: starting in C++17 it's possible to mutate msg_buf by mutating msg_buf.data() + + // we initialize a msg_buf with size == bufsize_without_terminator (filled with ' ' chars) + // - msg_buf.data() returns a ptr with msg_buf.size() + 1 characters. We are allowed to + // mutate any of the first msg_buf.size() characters. The entry at + // msg_buf.data()[msg_buf.size()] is initially '\0' (& it MUST remain equal to '\0') + // - the 2nd argument of std::vsnprintf is the size of the output buffer. We NEED to + // include the terminator character in this argument, otherwise the formatted message + // will be truncated + msg_buf = std::string(bufsize_without_terminator, ' '); + std::vsnprintf(msg_buf.data(), bufsize_without_terminator + 1, msg, args_copy); + va_end(args_copy); + } + + // now write the error and exit + std::fprintf(stderr, + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n" + "Error occurred in %s on line %d\n" + "Function: %s\n" + "Rank: %s\n" + "Message: %s\n" + "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n", + file_name, line_num, santized_func_name, proc_info.data(), msg_buf.data()); + std::fflush(stderr); // may be unnecessary for stderr + chexit(1); +} \ No newline at end of file diff --git a/src/utils/error_handling.h b/src/utils/error_handling.h index d539f0e50..4db749881 100644 --- a/src/utils/error_handling.h +++ b/src/utils/error_handling.h @@ -3,12 +3,69 @@ #include #include "../global/global.h" -void chexit(int code); +[[noreturn]] void chexit(int code); /*! * \brief Check that the Cholla configuration and parameters don't have any significant errors. Mostly compile time * checks. * */ -void Check_Configuration(parameters const &P); +void Check_Configuration(parameters const& P); + +/*! + * \brief helper function that prints an error message & aborts the program (in + * an MPI-safe way). Commonly invoked through a macro. + * + */ +[[noreturn]] void Abort_With_Err_(const char* func_name, const char* file_name, int line_num, const char* msg, ...); + +/* __CHOLLA_PRETTY_FUNC__ is a magic constant like __LINE__ or __FILE__ that + * provides the name of the current function. + * - The C++11 standard requires that __func__ is provided on all platforms, but + * that only provides limited information (just the name of the function). + * - Where available, we prefer to use compiler-specific features that provide + * more information about the function (like the scope of the function & the + * the function signature). + */ +#ifdef __GNUG__ + #define __CHOLLA_PRETTY_FUNC__ __PRETTY_FUNCTION__ +#else + #define __CHOLLA_PRETTY_FUNC__ __func__ +#endif + +/*! + * \brief print an error-message (with printf formatting) & abort the program. + * + * This macro should be treated as a function with the signature: + * [[noreturn]] void CHOLLA_ERROR(const char* msg, ...); + * + * - The 1st arg is printf-style format argument specifying the error message + * - The remaining args arguments are used to format error message + * + * \note + * the ``msg`` string is part of the variadic args so that there is always + * at least 1 variadic argument (even in cases when ``msg`` doesn't format + * any arguments). There is no way around this until C++ 20. + */ +#define CHOLLA_ERROR(...) Abort_With_Err_(__CHOLLA_PRETTY_FUNC__, __FILE__, __LINE__, __VA_ARGS__) + +/*! + * \brief if the condition is false, print an error-message (with printf + * formatting) & abort the program. + * + * This macro should be treated as a function with the signature: + * [[noreturn]] void CHOLLA_ASSERT(bool cond, const char* msg, ...); + * + * - The 1st arg is a boolean condition. When true, this does noth + * - The 2nd arg is printf-style format argument specifying the error message + * - The remaining args arguments are used to format error message + * + * \note + * the behavior is independent of the ``NDEBUG`` macro + */ +#define CHOLLA_ASSERT(cond, ...) \ + if (not(cond)) { \ + Abort_With_Err_(__CHOLLA_PRETTY_FUNC__, __FILE__, __LINE__, __VA_ARGS__); \ + } + #endif /*ERROR_HANDLING_CHOLLA_H*/