diff --git a/Control/RootUtils/CMakeLists.txt b/Control/RootUtils/CMakeLists.txt index fff981ad55c180da7bee92f3c4979b5ea432223a..e1eef70dde9acb25ea0868d868966b54126f6248 100644 --- a/Control/RootUtils/CMakeLists.txt +++ b/Control/RootUtils/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +# Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration # Declare the package name: atlas_subdir( RootUtils ) @@ -56,5 +56,10 @@ atlas_add_test( TTreePatch_test SCRIPT ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/TTreePatch_t.py ) +atlas_add_test( WithRootErrorHandler_test + SOURCES test/WithRootErrorHandler_test.cxx + INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} + LINK_LIBRARIES ${ROOT_LIBRARIES} RootUtils ) + # Install files from the package: atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} ) diff --git a/Control/RootUtils/RootUtils/WithRootErrorHandler.h b/Control/RootUtils/RootUtils/WithRootErrorHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..30630cae1690c21cfc9d4de76ff8405c47e79f12 --- /dev/null +++ b/Control/RootUtils/RootUtils/WithRootErrorHandler.h @@ -0,0 +1,107 @@ +// This file's extension implies that it's C, but it's really -*- C++ -*-. +/* + * Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file RootUtils/WithRootErrorHandler.h + * @author scott snyder <snyder@bnl.gov> + * @date Mar, 2021 + * @brief Run a MT piece of code with an alternate root error handler. + */ + + +#include "TError.h" +#include <functional> +#include <type_traits> + + +#ifndef ROOTUTILS_WITHROOTERRORHANDLER_H +#define ROOTUTILS_WITHROOTERRORHANDLER_H + +namespace RootUtils { + + +/** + * @brief Run a MT piece of code with an alternate root error handler. + * + * Sometimes we want to run a piece of code with an alternate root + * error handler. This is however problematic in MT jobs since the + * root error handler pointer is just a global variable visible + * in all threads. We also frequently need to introduce additional + * globals if we need to communicate with the handler. + * + * This piece of code tries to get around these limitations. + * It installs a single root error handler during initialization. + * This handler will then dispatch to other handlers stored + * in thread-local storage. This effectively makes changing + * the root error handler thread-local. We also manage the handler + * as a std::function, allowing it to have associated state, + * and clean up the change using RAII. + * + * To change the root error handler, create an instance of this class, + * passing it a function object, which should have signature: + * + *@code + * bool hand (int level, bool abort, const char* location, const char* msg) + @endcode + * + * The arguments are the same as for standard root error handlers. + * The handler should return true if previous handlers should also be called. + * If a handler returns false, no further handlers will be called. + * + * The change in the error handler is visible only in the current thread, + * and the revious handler will be restored when the object is deleted. + */ +class WithRootErrorHandler +{ +public: + // A ROOT error handler has signature + // void hand (int level, Bool_t abort, const char* location, const char* msg) + + /// Type of handler to register. Like a ROOT error handler, except that + /// it returns a bool. + using Handler_t = std::function<bool (int, Bool_t, const char*, const char*)>; + + + /** + * @brief Temporarily install a thread-local root error handler. + * @param errhand The handler to be installed. + * + * The installed handler will only run in the current thread, and it will + * be removed when this object is deleted. In addition to the usual + * arguments for a root error handler, it returns a bool. If the returned + * value is true, previous handlers will also be executed; otherwise, + * no further handlers will be executed. + */ + WithRootErrorHandler (Handler_t errhand); + + + /** + * @brief Destructor. + * + * Remove the error handler. + */ + ~WithRootErrorHandler(); + + + // Try to prevent misuse. + WithRootErrorHandler (const WithRootErrorHandler&) = delete; + WithRootErrorHandler (WithRootErrorHandler&&) = delete; + WithRootErrorHandler& operator= (const WithRootErrorHandler&) = delete; + WithRootErrorHandler& operator= (WithRootErrorHandler&&) = delete; + + static void* operator new (size_t) = delete; + static void* operator new[] (size_t) = delete; + static void operator delete (void*) = delete; + static void operator delete[] (void*) = delete; + + +private: + /// For error checking. + size_t m_size; +}; + + +} // namespace RootUtils + +#endif // not ROOTUTILS_WITHROOTERRORHANDLER_H diff --git a/Control/RootUtils/share/WithRootErrorHandler_test.ref b/Control/RootUtils/share/WithRootErrorHandler_test.ref new file mode 100644 index 0000000000000000000000000000000000000000..804a09378002da98b1110cfd25c01b93bd711462 --- /dev/null +++ b/Control/RootUtils/share/WithRootErrorHandler_test.ref @@ -0,0 +1,9 @@ +RootUtils/WithRootErrorHandler_test +test1 +Error in <foo1>: Bar1 +try1: Bar2 +Error in <foo2>: Bar2 +try2: Bar3 +try1: Bar3 +Error in <foo3>: Bar3 +try2: Bar4 diff --git a/Control/RootUtils/src/WithRootErrorHandler.cxx b/Control/RootUtils/src/WithRootErrorHandler.cxx new file mode 100644 index 0000000000000000000000000000000000000000..4988e6f9a82fbf246acb4edfb5de53ba150fad94 --- /dev/null +++ b/Control/RootUtils/src/WithRootErrorHandler.cxx @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file RootUtils/src/WithRootErrorHandler.cxx + * @author scott snyder <snyder@bnl.gov> + * @date Mar, 2021 + * @brief Run a MT piece of code with an alternate root error handler. + */ + + +#include "RootUtils/WithRootErrorHandler.h" +#include <vector> + + +namespace { + + +/// Thread-local list of registered error handlers, from oldest to newest. +using Handler_t = RootUtils::WithRootErrorHandler::Handler_t; +thread_local std::vector<Handler_t> rootErrorHandlers; + + +/// Declare our own error handler to root during initialization +/// and save the previous handler. +void errorHandler (int level, + Bool_t abort, + const char* location, + const char* msg); +const ErrorHandlerFunc_t origHandler = ::SetErrorHandler (errorHandler); + + +/** + * @brief Global root error handler. + */ +void errorHandler (int level, + Bool_t abort, + const char* location, + const char* msg) +{ + // Execute all the handlers in our thread-local list from newest to oldest. + // Stop if one returns false. + for (int i = rootErrorHandlers.size()-1; i >= 0; --i) { + if (!rootErrorHandlers[i] (level, abort, location, msg)) return; + } + // They all returned true. Call the previous handler. + origHandler (level, abort, location, msg); +} + + +} // anonymous namespace + + +namespace RootUtils { + + +/** + * @brief Temporarily install a thread-local root error handler. + * @param errhand The handler to be installed. + * + * The installed handler will only run in the current thread, and it will + * be removed when this object is deleted. In addition to the usual + * arguments for a root error handler, it returns a bool. If the returned + * value is true, previous handlers will also be executed; otherwise, + * no further handlers will be executed. + */ +WithRootErrorHandler::WithRootErrorHandler (Handler_t errhand) + : m_size (rootErrorHandlers.size()+1) +{ + rootErrorHandlers.push_back (errhand); +} + + +/** + * @brief Destructor. + * + * Remove the error handler. + */ +WithRootErrorHandler::~WithRootErrorHandler() +{ + if (m_size != rootErrorHandlers.size()) std::abort(); + rootErrorHandlers.pop_back(); +} + + +} // namespace RootUtils diff --git a/Control/RootUtils/test/WithRootErrorHandler_test.cxx b/Control/RootUtils/test/WithRootErrorHandler_test.cxx new file mode 100644 index 0000000000000000000000000000000000000000..703bb8e1d21141c849d702877f08af0a8f49bdb3 --- /dev/null +++ b/Control/RootUtils/test/WithRootErrorHandler_test.cxx @@ -0,0 +1,77 @@ +/* + Copyright (C) 2002-2021 CERN for the benefit of the ATLAS collaboration +*/ +/** + * @file RootUtils/test/WithRootErrorHandler_test.cxx + * @author scott snyder <snyder@bnl.gov> + * @date Mar, 2021 + * @brief Tests for WithRootErrorHandler. + */ + + +#undef NDEBUG +#include "RootUtils/WithRootErrorHandler.h" +#include "TError.h" +#include <string> +#include <iostream> +#include <cassert> + + +struct TestHand +{ + TestHand (const std::string& name, bool passOn) + : m_name (name), m_passOn (passOn) + { } + bool operator() (int level, Bool_t abort, const char* loc, const char* msg); + + std::string m_name; + bool m_passOn; +}; + + +bool TestHand::operator() (int /*level*/, Bool_t /*abort*/, + const char* /*loc*/, const char* msg) +{ + std::cout << m_name << ": " << msg << "\n"; + std::cout.flush(); + return m_passOn; +} + + +void test1() +{ + std::cout << "test1\n"; + std::cout.flush(); + + ::Error ("foo1", "Bar1"); + std::cerr.flush(); + std::cout.flush(); + { + RootUtils::WithRootErrorHandler hand { TestHand("try1", true) }; + ::Error ("foo2", "Bar2"); + std::cerr.flush(); + std::cout.flush(); + } + { + RootUtils::WithRootErrorHandler hand1 { TestHand("try1", true) }; + RootUtils::WithRootErrorHandler hand2 { TestHand("try2", true) }; + ::Error ("foo3", "Bar3"); + std::cerr.flush(); + std::cout.flush(); + } + { + RootUtils::WithRootErrorHandler hand1 { TestHand("try1", true) }; + RootUtils::WithRootErrorHandler hand2 { TestHand("try2", false) }; + ::Error ("foo4", "Bar4"); + std::cerr.flush(); + std::cout.flush(); + } +} + + +int main() +{ + std::cout << "RootUtils/WithRootErrorHandler_test\n"; + test1(); + return 0; +}