diff --git a/src/coreComponents/common/CMakeLists.txt b/src/coreComponents/common/CMakeLists.txt index 907e09333a4..2b094d68ad2 100644 --- a/src/coreComponents/common/CMakeLists.txt +++ b/src/coreComponents/common/CMakeLists.txt @@ -31,7 +31,6 @@ set( common_headers format/LogPart.hpp format/Format.hpp format/StringUtilities.hpp - logger/Logger.hpp BufferAllocator.hpp DataLayouts.hpp DataTypes.hpp @@ -41,6 +40,7 @@ set( common_headers MemoryInfos.hpp logger/Logger.hpp logger/ErrorHandling.hpp + logger/ExternalErrorHandler.hpp MpiWrapper.hpp Path.hpp Span.hpp @@ -77,6 +77,7 @@ set( common_sources format/StringUtilities.cpp logger/Logger.cpp logger/ErrorHandling.cpp + logger/ExternalErrorHandler.cpp BufferAllocator.cpp MemoryInfos.cpp MpiWrapper.cpp diff --git a/src/coreComponents/common/Units.hpp b/src/coreComponents/common/Units.hpp index 4d69236b43f..32adb6e6dc8 100644 --- a/src/coreComponents/common/Units.hpp +++ b/src/coreComponents/common/Units.hpp @@ -275,17 +275,17 @@ static constexpr double YearSeconds = YearDays * DaySeconds; struct TimeFormatInfo { /// Total time (including the decimal part) this instance represents in seconds - double const m_totalSeconds = 0.0; + double const m_totalSeconds; /// Number of integral years to show - int const m_years = 0; + int const m_years; /// Number of integral days to show - int const m_days = 0; + int const m_days; /// Number of integral hours to show - int const m_hours = 0; + int const m_hours; /// Number of integral minutes to show - int const m_minutes = 0; + int const m_minutes; /// Number of integral seconds to show - int const m_seconds = 0; + int const m_seconds; /** * @brief Construct a TimeFormatInfo from raw data (which must be coherent) diff --git a/src/coreComponents/common/initializeEnvironment.cpp b/src/coreComponents/common/initializeEnvironment.cpp index 5d78e7a45fe..1a08d139c61 100644 --- a/src/coreComponents/common/initializeEnvironment.cpp +++ b/src/coreComponents/common/initializeEnvironment.cpp @@ -20,6 +20,8 @@ #include "LvArray/src/system.hpp" #include "common/LifoStorageCommon.hpp" #include "common/MemoryInfos.hpp" +#include "logger/ErrorHandling.hpp" +#include "logger/ExternalErrorHandler.hpp" #include // TPL includes #include @@ -66,6 +68,88 @@ void setupLogger() #else logger::InitializeLogger(); #endif + + { // setup error handling (using LvArray helper system functions) + using ErrorContext = ErrorLogger::ErrorContext; + + ///// set Post-Handled Error behaviour ///// + LvArray::system::setErrorHandler( []() + { + #if defined( GEOS_USE_MPI ) + int mpi = 0; + MPI_Initialized( &mpi ); + if( mpi ) + { + MPI_Abort( MPI_COMM_WORLD, EXIT_FAILURE ); + } + #endif + std::abort(); + } ); + + ///// set external error handling behaviour ///// + ExternalErrorHandler::instance().setErrorHandling( []( string_view errorMsg, + string_view detectionLocation ) + { + std::string const stackHistory = LvArray::system::stackTrace( true ); + + GEOS_LOG( GEOS_FMT( "***** ERROR\n" + "***** LOCATION: (external error, detected {})\n" + "{}\n{}", + detectionLocation, errorMsg, stackHistory ) ); + if( ErrorLogger::global().isOutputFileEnabled() ) + { + ErrorLogger::ErrorMsg error; + error.setType( ErrorLogger::MsgType::Error ); + error.addToMsg( errorMsg ); + error.addRank( ::geos::logger::internal::g_rank ); + error.addCallStackInfo( stackHistory ); + error.addContextInfo( + ErrorContext{ { { ErrorContext::Attribute::DetectionLoc, string( detectionLocation ) } } } ); + + ErrorLogger::global().flushErrorMsg( error ); + } + + // we do not terminate the program as 1. the error could be non-fatal, 2. there may be more messages to output. + } ); + ExternalErrorHandler::instance().enableStderrPipe( true ); + + ///// set signal handling behaviour ///// + LvArray::system::setSignalHandling( []( int const signal ) + { + // Disable signal handling to prevent catching exit signal (infinite loop) + LvArray::system::setSignalHandling( nullptr ); + + // first of all, external error can await to be output, we must output them + ExternalErrorHandler::instance().flush( "before signal error output" ); + + // error message output + std::string const stackHistory = LvArray::system::stackTrace( true ); + ErrorLogger::ErrorMsg error; + error.addSignalToMsg( signal ); + + GEOS_LOG( GEOS_FMT( "***** ERROR\n" + "***** SIGNAL: {}\n" + "***** LOCATION: (external error, captured by signal handler)\n" + "{}\n{}", + signal, error.m_msg, stackHistory ) ); + + if( ErrorLogger::global().isOutputFileEnabled() ) + { + error.setType( ErrorLogger::MsgType::Error ); + error.addRank( ::geos::logger::internal::g_rank ); + error.addCallStackInfo( stackHistory ); + error.addContextInfo( + ErrorContext{ { { ErrorContext::Attribute::Signal, std::to_string( signal ) } }, 1 }, + ErrorContext{ { { ErrorContext::Attribute::DetectionLoc, string( "signal handler" ) } }, 0 } ); + + ErrorLogger::global().flushErrorMsg( error ); + } + + // call program termination + LvArray::system::callErrorHandler(); + } ); + + } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -77,21 +161,6 @@ void finalizeLogger() /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void setupLvArray() { - LvArray::system::setErrorHandler( []() - { - #if defined( GEOS_USE_MPI ) - int mpi = 0; - MPI_Initialized( &mpi ); - if( mpi ) - { - MPI_Abort( MPI_COMM_WORLD, EXIT_FAILURE ); - } - #endif - std::abort(); - } ); - - LvArray::system::setSignalHandling( []( int const signal ) { LvArray::system::stackTraceHandler( signal, true ); } ); - #if defined(GEOS_USE_FPE) LvArray::system::setFPE(); #else diff --git a/src/coreComponents/common/logger/ErrorHandling.cpp b/src/coreComponents/common/logger/ErrorHandling.cpp index ef526f2b7bf..5bec5c6754c 100644 --- a/src/coreComponents/common/logger/ErrorHandling.cpp +++ b/src/coreComponents/common/logger/ErrorHandling.cpp @@ -25,6 +25,11 @@ #include #include +// signal management +#include +#include +#include + namespace geos { static constexpr std::string_view g_level1Start = " - "; @@ -68,7 +73,9 @@ std::string ErrorLogger::ErrorContext::attributeToString( ErrorLogger::ErrorCont case ErrorLogger::ErrorContext::Attribute::InputFile: return "inputFile"; case ErrorLogger::ErrorContext::Attribute::InputLine: return "inputLine"; case ErrorLogger::ErrorContext::Attribute::DataPath: return "dataPath"; - default: return "Unknown"; + case ErrorLogger::ErrorContext::Attribute::DetectionLoc: return "detectionLocation"; + case ErrorLogger::ErrorContext::Attribute::Signal: return "signal"; + default: return "unknown"; } } @@ -98,6 +105,39 @@ ErrorLogger::ErrorMsg & ErrorLogger::ErrorMsg::addToMsg( std::string_view errorM return *this; } +ErrorLogger::ErrorMsg & ErrorLogger::ErrorMsg::addSignalToMsg( int sig, bool toEnd ) +{ + if( sig == SIGFPE ) + { + std::string errorMsg = "Floating point error encountered: \n"; + + if( std::fetestexcept( FE_DIVBYZERO ) ) + errorMsg += "- Division by zero operation.\n"; + + if( std::fetestexcept( FE_INEXACT ) ) + errorMsg += "- Inexact result.\n"; + + if( std::fetestexcept( FE_INVALID ) ) + errorMsg += "- Domain error occurred in an earlier floating-point operation.\n"; + + if( std::fetestexcept( FE_OVERFLOW ) ) + errorMsg += "- The result of the earlier floating-point operation was too large to be representable.\n"; + + if( std::fetestexcept( FE_UNDERFLOW ) ) + errorMsg += "- The result of the earlier floating-point operation was subnormal with a loss of precision.\n"; + + return addToMsg( errorMsg, + toEnd ); + } + else + { + // standard messages + return addToMsg( GEOS_FMT( "Signal no. {} encountered: {}\n", + sig, ::strsignal( sig ) ), + toEnd ); + } +} + ErrorLogger::ErrorMsg & ErrorLogger::ErrorMsg::setCodeLocation( std::string_view msgFile, integer msgLine ) { m_file = msgFile; @@ -230,10 +270,12 @@ void ErrorLogger::flushErrorMsg( ErrorLogger::ErrorMsg & errorMsg ) } // Location of the error in the code - yamlFile << g_level1Next << "sourceLocation:\n"; - yamlFile << g_level2Next << "file: " << errorMsg.m_file << "\n"; - yamlFile << g_level2Next << "line: " << errorMsg.m_line << "\n"; - + if( !errorMsg.m_file.empty() ) + { + yamlFile << g_level1Next << "sourceLocation:\n"; + yamlFile << g_level2Next << "file: " << errorMsg.m_file << "\n"; + yamlFile << g_level2Next << "line: " << errorMsg.m_line << "\n"; + } // Information about the stack trace if( !errorMsg.m_sourceCallStack.empty() ) { @@ -249,7 +291,7 @@ void ErrorLogger::flushErrorMsg( ErrorLogger::ErrorMsg & errorMsg ) yamlFile << "\n"; yamlFile.flush(); errorMsg = ErrorMsg(); - GEOS_LOG_RANK( GEOS_FMT( "The error file {} was appended.", m_filename ) ); + GEOS_LOG_RANK( GEOS_FMT( "The error file {} has been appended.\n", m_filename ) ); } else { diff --git a/src/coreComponents/common/logger/ErrorHandling.hpp b/src/coreComponents/common/logger/ErrorHandling.hpp index a1275433a14..62e3db66df6 100644 --- a/src/coreComponents/common/logger/ErrorHandling.hpp +++ b/src/coreComponents/common/logger/ErrorHandling.hpp @@ -61,7 +61,9 @@ class ErrorLogger { InputFile, InputLine, - DataPath + DataPath, + DetectionLoc, + Signal, }; /// The map contains contextual information about the error @@ -157,6 +159,16 @@ class ErrorLogger */ ErrorMsg & addToMsg( std::string_view msg, bool toEnd = false ); + /** + * @brief Add text to the error msg that occured according to the specified signal. + * - the signal can be one of the main error signals. + * - if the signal is SIGFPE, the nature of floating point error will be interpreted. + * @param signal The signal, from ISO C99 or POSIX standard. + * @param toEnd adds the message to the end if true, at the start otherwise. + * @return The instance, for builder pattern. + */ + ErrorMsg & addSignalToMsg( int signal, bool toEnd = false ); + /** * @brief Set the source code location values (file and line where the error is detected) * @param msgFile Name of the source file location to add diff --git a/src/coreComponents/common/logger/ExternalErrorHandler.cpp b/src/coreComponents/common/logger/ExternalErrorHandler.cpp new file mode 100644 index 00000000000..0efb7038d1a --- /dev/null +++ b/src/coreComponents/common/logger/ExternalErrorHandler.cpp @@ -0,0 +1,269 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ErrorHandling.cpp + */ + +#include "ExternalErrorHandler.hpp" +#include "common/logger/Logger.hpp" + +#include +#include +#include +#include +#include + +namespace geos +{ + +void OutputStreamDeviation::Pipe::create() +{ + if( ::pipe( fileDescriptorsArray ) == m_errorResult ) + { + GEOS_WARNING( "Failed to create error pipe: " + getLastPosixErrorString() ); + } +} + +bool OutputStreamDeviation::Pipe::setDescriptorInheritanceMode( bool inherit ) +{ + bool r = true; + + // set O_CLOEXEC flag on both descriptors + for( PosixId pipeEndFD : fileDescriptorsArray ) + { + // get current flags for pipe end + PosixId flags = ::fcntl( pipeEndFD, F_GETFD ); + if( flags == m_errorResult ) + { + r = false; + } + else + { + // enable O_CLOEXEC flag if inheritance is disabled + flags = inherit ? ( flags & ~O_CLOEXEC ) : ( flags | FD_CLOEXEC ); + + // apply new flags + if( ::fcntl( pipeEndFD, F_SETFD, flags ) == m_errorResult ) + r = false; + } + } + + return r; +} + +bool OutputStreamDeviation::Pipe::redirectWriteEnd( int targetFd ) +{ + return ::dup2( writeEnd(), targetFd ) != m_errorResult; +} + +void OutputStreamDeviation::Pipe::closePipe() +{ + closePipeEnd( readEnd() ); + closePipeEnd( writeEnd() ); +} + +OutputStreamDeviation::OutputStreamDeviation( PosixId fileNo ): + m_redirectedStream( fileNo ), + m_originalStreamTarget( m_disabledPipeEnd ) +{ + // Create pipe with appropriate flags + m_deviationPipe.create(); + m_deviationPipe.setDescriptorInheritanceMode( false ); + + if( !setPipeEndBlockingMode( m_deviationPipe.readEnd(), true ) ) + { + m_deviationPipe.closePipe(); + GEOS_WARNING( "Failed to set pipe end non-blocking: " + getLastPosixErrorString() ); + } + + // backup original descriptor for reset after destruction + m_originalStreamTarget = duplicateDescriptor( m_redirectedStream ); + if( m_originalStreamTarget == m_disabledPipeEnd ) + { + m_deviationPipe.closePipe(); + GEOS_WARNING( "Failed to duplicate original descriptor: " + getLastPosixErrorString() ); + } + + // Redirect stderr to pipe + if( !m_deviationPipe.redirectWriteEnd( m_redirectedStream ) ) + { + m_deviationPipe.closePipe(); + closePipeEnd( m_originalStreamTarget ); + GEOS_WARNING( "Failed to redirect stream: " + getLastPosixErrorString() ); + } + + // Close the write end of the parent pipe: no longer useful as the source "file" will be written externally + // we will only use the readEnd of the pipe to get the content of it + closePipeEnd( m_deviationPipe.writeEnd() ); + + prepareStreamingBuffer(); +} + +bool OutputStreamDeviation::setPipeEndBlockingMode( PosixId pipeEnd, bool nonBlocking ) +{ + // get current flags for pipe end + PosixId flags = ::fcntl( pipeEnd, F_GETFL ); + if( flags == m_errorResult ) + return false; + + // set flags depending on nonBlocking value + flags = nonBlocking ? ( flags | O_NONBLOCK ) : ( flags & ~O_NONBLOCK ); + + // apply new flags + return ::fcntl( pipeEnd, F_SETFL, flags ) != m_errorResult; +} + +int OutputStreamDeviation::duplicateDescriptor( int pipeEnd ) +{ + int fdDuplicata = ::dup( pipeEnd ); + // if ::dup() fails, it should returns -1, which is the same value as m_disabledPipeEnd + return fdDuplicata; +} + +void OutputStreamDeviation::prepareStreamingBuffer() +{ + // pre-allocate the message processing buffer + m_unprocessedData.reserve( 16384 ); + + { // resize the pipe stream buffer from 16ko to 1mo to be able to process the biggest error messages as one bit (platform specific) + #ifdef __linux__ + ::fcntl( m_deviationPipe.readEnd(), F_SETPIPE_SZ, 1048576 ); + #endif + + #ifdef __APPLE__ + PosixId bufsize = 1048576; + ::setsockopt( m_deviationPipe.readEnd(), SOL_SOCKET, SO_RCVBUF, + &bufsize, sizeof(bufsize) ); + #endif + } +} + +void OutputStreamDeviation::closePipeEnd( PosixId & pipeEnd ) +{ + if( pipeEnd != m_disabledPipeEnd ) + { + ::close( pipeEnd ); + pipeEnd = m_disabledPipeEnd; + } +} + +string OutputStreamDeviation::getLastPosixErrorString() +{ + return std::string( strerror( errno ) ); +} + +OutputStreamDeviation::~OutputStreamDeviation() +{ + if( m_originalStreamTarget != m_disabledPipeEnd ) + { + if( ::dup2( m_originalStreamTarget, m_redirectedStream ) == m_errorResult ) + { + GEOS_WARNING( "Failed to restore pipe" ); + } + + ::close( m_originalStreamTarget ); + m_originalStreamTarget = m_disabledPipeEnd; + } + + if( m_deviationPipe.readEnd() != m_disabledPipeEnd ) + { + ::close( m_deviationPipe.readEnd() ); + m_deviationPipe.readEnd() = m_disabledPipeEnd; + } +} + + +void OutputStreamDeviation::flush( OutputStreamDeviation::LineHandlingFunctor const & lineFunctor, + std::string_view detectionLocation ) +{ + std::array< char, 8192 > readBuffer; + ssize_t bytesRead; + + // read all pending data from the original stream & add it in the text buffer to process + while( ( bytesRead = ::read( m_deviationPipe.readEnd(), + readBuffer.data(), + readBuffer.size() ) ) > 0 ) + { + m_unprocessedData.append( readBuffer.data(), bytesRead ); + } + + { // process each full lines + size_t lineStart = 0; + size_t lineEnd = 0; + + while((lineEnd = m_unprocessedData.find( '\n', lineStart )) != std::string::npos ) + { + std::string_view line = std::string_view( m_unprocessedData.data() + lineStart, + lineEnd - lineStart ); + lineFunctor( line, detectionLocation ); + lineStart = lineEnd + 1; + } + + // keep last line residual if it exists (incomplete line) + if( lineStart < m_unprocessedData.size() ) + { + m_unprocessedData.erase( 0, lineStart ); + } + else + { + m_unprocessedData.clear(); + } + } +} + + +ExternalErrorHandler::ExternalErrorHandler(): + m_processErrorFunctor( ExternalErrorHandler::defaultErrorHandling ) +{} + +ExternalErrorHandler::~ExternalErrorHandler() +{ + enableStderrPipe( false ); +} + +ExternalErrorHandler & ExternalErrorHandler::instance() +{ + static ExternalErrorHandler instance; + return instance; +} + +void ExternalErrorHandler::enableStderrPipe( bool enable ) +{ + if( enable && !m_stderrDeviation ) + { + m_stderrDeviation = std::make_unique< OutputStreamDeviation >( STDERR_FILENO ); + } + else if( !enable && m_stderrDeviation ) + { + m_stderrDeviation = nullptr; + } +} + +void ExternalErrorHandler::flush( std::string_view detectionLocation ) +{ + if( m_stderrDeviation && m_processErrorFunctor ) + { + m_stderrDeviation->flush( m_processErrorFunctor, detectionLocation ); + } +} + +void ExternalErrorHandler::defaultErrorHandling( std::string_view errorMsg, + std::string_view detectionLocation ) +{ + std::cout << "External error, detected" << detectionLocation << ": " << errorMsg << std::endl; +} + +} /* namespace geos */ diff --git a/src/coreComponents/common/logger/ExternalErrorHandler.hpp b/src/coreComponents/common/logger/ExternalErrorHandler.hpp new file mode 100644 index 00000000000..cb6e1922e02 --- /dev/null +++ b/src/coreComponents/common/logger/ExternalErrorHandler.hpp @@ -0,0 +1,244 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + +/** + * @file ErrorHandling.hpp + */ + +#ifndef LOGGER_EXTERNALERRORHANDLER_HPP +#define LOGGER_EXTERNALERRORHANDLER_HPP + +#include "ErrorHandling.hpp" + +#include +#include +#include +#include + +namespace geos +{ + +/** + * @brief This class implements pipe redirection to allow to capture and process externally streamed messages + */ +class OutputStreamDeviation +{ +public: + + /// Posix identifier, can be a file handle, an error number... Must be consistent with posix functions. + using PosixId = int; + + /** + * @brief A functor executed for each independant lines to process, taking the line as the 1st + * string_view parameter, and the detectionLocation as the 2nd. + */ + using LineHandlingFunctor = std::function< void (std::string_view, std::string_view) >; + + /** + * @brief Construct and enable a new pipe redirection. + * @param fileNo The file descriptor number, as returned by fileno() or can be one of the + * following: "STDOUT_FILENO" (1), "STDERR_FILENO" (2). + */ + OutputStreamDeviation( PosixId fileNo ); + + /** + * @brief Destroy the OutputStreamDeviation object, restoring the original pipe state. + */ + ~OutputStreamDeviation(); + + /** + * @brief Flush the buffer from the original output pipe in a string, allowing to log it where needed. + * @param lineProcessingFunctor see LineHandlingFunctor. + * @param detectionLocation A label to describe when the flush() operation is being made, thus + * explaining to the user when the error has been detected. + */ + void flush( LineHandlingFunctor const & lineProcessingFunctor, std::string_view detectionLocation ); + +private: + struct Pipe + { + /// the file descriptors of the stream, read end first, then write end. + PosixId fileDescriptorsArray[2]; + + /** + * @brief Initialize the file descriptors to open the pipe as a "virtual file". + */ + void create(); + + /** + * @return the read end file descriptor of the pipe (serve to read data). + */ + PosixId & readEnd() + { return fileDescriptorsArray[0]; } + + /** + * @return the write end file descriptor of the pipe (serve to write data). + */ + PosixId & writeEnd() + { return fileDescriptorsArray[1]; } + + /** + * @brief Prevent or enable file descriptors of the pipe instance from being inherited by child processes. + * @param inherit If true, enable file descriptors inheritance. If false, disable it. + * @return True if successful, false otherwise. If the function fails, a warning is logged. + * @details When disabled, this function sets the close-on-exec flag on a file descriptor, + * preventing it from being inherited by child processes when exec() is called. + * This helps prevent resource leaks and security issues in process management. + */ + bool setDescriptorInheritanceMode( bool inherit ); + + /** + * @brief Redirect the write end of this pipe to a target stream, enabling capture of output + written to that stream through the read end. + The write end can still be used to write through the same pipe. + * @param targetFd The file descriptor to redirect to (e.g., stdout, stderr). + * @return True if successful, false otherwise. + */ + bool redirectWriteEnd( int targetFd ); + + /** + * @brief Close the pipe ends. + */ + void closePipe(); + }; + + /// a special value to represant a disabled / not existing pipe. + static constexpr PosixId m_disabledPipeEnd = -1; + + /// error values from POSIX functions. + static constexpr PosixId m_errorResult = -1; + + /// the original pipe to deviate + PosixId m_redirectedStream; + + /// Backup for restoring the original pipe at destruction. + PosixId m_originalStreamTarget; + + /// the pipe that deviate the original pipe + Pipe m_deviationPipe; + + /// a buffer to store the flush() results + std::string m_unprocessedData; + + /** + * @brief Set a file descriptor to non-blocking mode for asynchronous I/O operations. + * @param pipeEnd The file descriptor to set the blocking mode. + * @param nonBlocking If true, non-blocking mode is enabled. If false, blocking mode is used. + * @return True if successful, false otherwise. + * @details This function allows the read operation to return immediately even when no data is available, + * rather than blocking until data becomes available. + */ + static bool setPipeEndBlockingMode( PosixId pipeEnd, bool nonBlocking ); + + /** + * @brief Duplicate a file descriptor, creating a new file descriptor that refers to the same underlying object. + * @param pipeEnd The original file descriptor to duplicate. + * @return The new duplicated file descriptor, or m_disabledPipeEnd on failure. + * @details Used primarily for backup purposes to preserve the original stream state to restore that after cleanup. + */ + static int duplicateDescriptor( int pipeEnd ); + + /** + * @brief Prepare the streaming buffer with a few optimisation by: + - preallocating the intermediate message processing buffer, + - growing the pipe stream to make it able to process the biggest error messages as one bit. + */ + void prepareStreamingBuffer(); + + /** + * @brief Close the given pipe end + * @param posixErrNo Reference to the POSIX pipe identifier. Set to m_disabledPipeEnd afterwards. + */ + static void closePipeEnd( PosixId & pipeEnd ); + + /** + * @param posixErrNo POSIX error number + * @return the error description of the given error number + */ + static string getLastPosixErrorString(); +}; + +/** + * @brief Class to handle external error capture. + * This class role is to capture and process external error messages, using the geos logger for + * better tracing, logging and handling of messages. + */ +class ExternalErrorHandler +{ +public: + + /** + * @brief A functor executed for each error mesage to process, taking the message as the 1st + * string_view parameter, and the detectionLocation as the 2nd. + * @see defaultErrorHandling() for a default implementation. + */ + using ErrorHandlingFunctor = OutputStreamDeviation::LineHandlingFunctor; + + /** + * @brief Strinct singleton pattern has been choosen since we will only have single sources of external + * errors (stderr for now, we could extend that for HYPRE errors, or for more dependencies). + * @return The unique global instance. + */ + static ExternalErrorHandler & instance(); + + /** + * @brief Destructor, disable all error piping features. + */ + ~ExternalErrorHandler(); + + /** + * @brief Set the function that process the external errors that have been captured. The processing + * typically consists in using the given error message, adding metadata, and logging the message. + * @param errorHandlingFunctor see ErrorHandlingFunctor. + * @note Implementation treat each independant lines as an single error. + */ + void setErrorHandling( ErrorHandlingFunctor && errorHandlingFunctor ) + { m_processErrorFunctor = errorHandlingFunctor; } + + /** + * @brief Enable capture of errors piped from the std::cerr stream. + * Helpful to capture GLIBC errors, or other errors from dependencies not managed by GEOS itself. + * @param enable Enable the feature if true, disable it otherwise. + * @note Disabled by default. + */ + void enableStderrPipe( bool enable ); + + /** + * @brief Process all awaiting captured errors that were produced externally, then clear the error stream. + * @param detectionLocation A label to describe when the flush() operation is being made, thus + * explaining to the user when the error has been detected. + * @see setErrorHandling() to set the error processing procedure. + */ + void flush( std::string_view detectionLocation ); + + /** + * @brief Not designed for direct calls, error handling function in default use if never calling + * setErrorHandling(). + * @param errorMsg the error text message. + * @param detectionLocation A label to describe to the user when the error has been detected. + */ + static void defaultErrorHandling( std::string_view errorMsg, std::string_view detectionLocation ); + +private: + std::unique_ptr< OutputStreamDeviation > m_stderrDeviation; + + ErrorHandlingFunctor m_processErrorFunctor; + + ExternalErrorHandler(); +}; + +} /* namespace geos */ + +#endif diff --git a/src/coreComponents/events/EventManager.cpp b/src/coreComponents/events/EventManager.cpp index 7e2415131b7..5e24698b008 100644 --- a/src/coreComponents/events/EventManager.cpp +++ b/src/coreComponents/events/EventManager.cpp @@ -24,6 +24,7 @@ #include "events/EventBase.hpp" #include "common/MpiWrapper.hpp" #include "common/Units.hpp" +#include "common/logger/ExternalErrorHandler.hpp" #include "events/LogLevelsInfo.hpp" namespace geos @@ -117,6 +118,9 @@ bool EventManager::run( DomainPartition & domain ) integer exitFlag = 0; + // flush stderr pipe in case any error happened during GEOS loading + ExternalErrorHandler::instance().flush( "post GEOS loading" ); + // Setup event targets, sequence indicators array1d< integer > eventCounters( 2 ); this->forSubGroups< EventBase >( [&]( EventBase & subEvent ) @@ -202,6 +206,9 @@ bool EventManager::run( DomainPartition & domain ) earlyReturn = subEvent->execute( m_time, m_dt, m_cycle, 0, 0, domain ); } + // check stderr pipe in case any error happened during subevent + ExternalErrorHandler::instance().flush( GEOS_FMT( "post {} sub-event processing", subEvent->getName() ) ); + // Check the exit flag // Note: Currently, this is only being used by the HaltEvent // If it starts being used elsewhere it may need to be synchronized