From 088f6faa006b8414fe5b7197a2ae18c3cc0efdc9 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Thu, 22 Aug 2019 23:30:37 -0400 Subject: SERVER-25240 Make `stdx::set_terminate` which works on windows. The `stdx::set_terminate` primitive, on windows, wraps the per-thread terminate handler and emulates a single global terminate handler. --- src/mongo/stdx/SConscript | 63 ++++++++++++++ src/mongo/stdx/exception.h | 70 ++++++++++++++++ src/mongo/stdx/set_terminate_dispatch_test.cpp | 59 ++++++++++++++ .../set_terminate_from_main_die_in_thread_test.cpp | 63 ++++++++++++++ .../set_terminate_from_thread_die_in_main_test.cpp | 63 ++++++++++++++ ...et_terminate_from_thread_die_in_thread_test.cpp | 67 +++++++++++++++ src/mongo/stdx/set_terminate_internals.cpp | 95 ++++++++++++++++++++++ src/mongo/stdx/thread.h | 55 ++++++++----- 8 files changed, 514 insertions(+), 21 deletions(-) create mode 100644 src/mongo/stdx/exception.h create mode 100644 src/mongo/stdx/set_terminate_dispatch_test.cpp create mode 100644 src/mongo/stdx/set_terminate_from_main_die_in_thread_test.cpp create mode 100644 src/mongo/stdx/set_terminate_from_thread_die_in_main_test.cpp create mode 100644 src/mongo/stdx/set_terminate_from_thread_die_in_thread_test.cpp create mode 100644 src/mongo/stdx/set_terminate_internals.cpp (limited to 'src/mongo/stdx') diff --git a/src/mongo/stdx/SConscript b/src/mongo/stdx/SConscript index f954fdf8398..bd253c826c7 100644 --- a/src/mongo/stdx/SConscript +++ b/src/mongo/stdx/SConscript @@ -13,6 +13,17 @@ env.Benchmark( ], ) +env.Library( + target='stdx', + source=[ + 'set_terminate_internals.cpp', + ], + LIBDEPS=[ + # Ideally, there should be no linking dependencies upon any other libraries, for `libstdx`. + # This library is a shim filling in for deficiencies in various standard library + # implementations. There should never be any link-time dependencies into mongo internals. + ], +) env.CppUnitTest( target='stdx_test', @@ -23,3 +34,55 @@ env.CppUnitTest( '$BUILD_DIR/third_party/shim_abseil', ], ) + +# The tests for `stdx::set_terminate` need to run outside of the mongo unittest harneses. +# The tests require altering the global `set_terminate` handler, which our unittest framework +# doesn't expect to have happen. Further, the tests have to return successfully from a +# terminate condition which interacts poorly with the unittest framework. +# +# A set of dedicated binaries to each test case is actually the simplest way to accomplish +# robust testing of this mechanism. + +# Needs to be a different test -- It has to have direct control over the `main()` entry point. +env.RegisterUnitTest(env.Program( + target='set_terminate_dispatch_test', + source=[ + 'set_terminate_dispatch_test.cpp', + ], + LIBDEPS=[ + 'stdx', + ] +)[0]) + +# Needs to be a different test -- It has to have direct control over the `main()` entry point. +env.RegisterUnitTest(env.Program( + target='set_terminate_from_main_die_in_thread_test', + source=[ + 'set_terminate_from_main_die_in_thread_test.cpp', + ], + LIBDEPS=[ + 'stdx', + ] +)[0]) + +# Needs to be a different test -- It has to have direct control over the `main()` entry point. +env.RegisterUnitTest(env.Program( + target='set_terminate_from_thread_die_in_main_test', + source=[ + 'set_terminate_from_thread_die_in_main_test.cpp', + ], + LIBDEPS=[ + 'stdx', + ] +)[0]) + +# Needs to be a different test -- It has to have direct control over the `main()` entry point. +env.RegisterUnitTest(env.Program( + target='set_terminate_from_thread_die_in_thread_test', + source=[ + 'set_terminate_from_thread_die_in_thread_test.cpp', + ], + LIBDEPS=[ + 'stdx', + ] +)[0]) diff --git a/src/mongo/stdx/exception.h b/src/mongo/stdx/exception.h new file mode 100644 index 00000000000..9755cf131b6 --- /dev/null +++ b/src/mongo/stdx/exception.h @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include +#include +#include + +// This file provides a wrapper over the function registered by `std::set_terminate`. This +// facilitates making `stdx::set_terminate` work correctly on windows. In windows, +// `std::set_terminate` works on a per-thread basis. Our `stdx::thread` header registers our +// handler using the `stdx::terminate_detail::TerminateHandlerInterface::dispatch` as an entry point +// for `std::set_terminate` when a thread starts on windows. `stdx::set_terminate` sets the handler +// globally for all threads. Our wrapper, which is registered with each thread, calls the global +// handler. +// +// NOTE: Our wrapper is not initialization order safe. It is not safe to set the terminate handler +// until main has started. + +namespace mongo::stdx { + +// In order to grant `mongo::stdx::thread` access to the dispatch method, we need to know this +// class's name. A forward-decl header would be overkill for this singular special case. +class thread; + +// This must be the same as the definition in standard. Do not alter this alias. +using ::std::terminate_handler; + +#if defined(_WIN32) +class TerminateHandlerDetailsInterface { + friend ::mongo::stdx::thread; + static void dispatch() noexcept; +}; + +terminate_handler set_terminate(const terminate_handler handler) noexcept; + +terminate_handler get_terminate() noexcept; + +#else +using ::std::get_terminate; // NOLINT +using ::std::set_terminate; // NOLINT +#endif +} // namespace mongo::stdx diff --git a/src/mongo/stdx/set_terminate_dispatch_test.cpp b/src/mongo/stdx/set_terminate_dispatch_test.cpp new file mode 100644 index 00000000000..b796fc257a3 --- /dev/null +++ b/src/mongo/stdx/set_terminate_dispatch_test.cpp @@ -0,0 +1,59 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/stdx/exception.h" + +#include + +#include + +#include "mongo/stdx/thread.h" + +namespace { + +namespace stdx = ::mongo::stdx; + +void writeFeedbackAndCleanlyExit() { + std::cout << "Entered terminate handler." << std::endl; + exit(EXIT_SUCCESS); +} + +void testTerminateDispatch() { + std::cout << "Setting terminate handler" << std::endl; + stdx::set_terminate(writeFeedbackAndCleanlyExit); + std::cout << "Calling terminate." << std::endl; + std::terminate(); + exit(EXIT_FAILURE); +} +} // namespace + +int main() { + testTerminateDispatch(); + return EXIT_FAILURE; +} diff --git a/src/mongo/stdx/set_terminate_from_main_die_in_thread_test.cpp b/src/mongo/stdx/set_terminate_from_main_die_in_thread_test.cpp new file mode 100644 index 00000000000..f887039e95c --- /dev/null +++ b/src/mongo/stdx/set_terminate_from_main_die_in_thread_test.cpp @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/stdx/exception.h" + +#include + +#include + +#include "mongo/stdx/thread.h" + +namespace { + +namespace stdx = ::mongo::stdx; + +void writeFeedbackAndCleanlyExit() { + std::cout << "Entered terminate handler." << std::endl; + exit(EXIT_SUCCESS); +} + +void testTerminateDispatch() { + std::cout << "Setting terminate handler" << std::endl; + stdx::set_terminate(writeFeedbackAndCleanlyExit); + std::cout << "Starting background thread (which will terminate)." << std::endl; + stdx::thread{[] { + std::cout << "Calling terminate from background thread." << std::endl; + std::terminate(); + }} + .join(); + exit(EXIT_FAILURE); +} +} // namespace + +int main() { + testTerminateDispatch(); + return EXIT_FAILURE; +} diff --git a/src/mongo/stdx/set_terminate_from_thread_die_in_main_test.cpp b/src/mongo/stdx/set_terminate_from_thread_die_in_main_test.cpp new file mode 100644 index 00000000000..54a043073ce --- /dev/null +++ b/src/mongo/stdx/set_terminate_from_thread_die_in_main_test.cpp @@ -0,0 +1,63 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/stdx/exception.h" + +#include + +#include + +#include "mongo/stdx/thread.h" + +namespace { + +namespace stdx = ::mongo::stdx; + +void writeFeedbackAndCleanlyExit() { + std::cout << "Entered terminate handler." << std::endl; + exit(EXIT_SUCCESS); +} + +void testTerminateDispatch() { + std::cout << "Starting background thread (which will call `set_terminate`)." << std::endl; + stdx::thread{[] { + std::cout << "Setting terminate handler" << std::endl; + stdx::set_terminate(writeFeedbackAndCleanlyExit); + }} + .join(); + std::cout << "Calling terminate." << std::endl; + std::terminate(); + exit(EXIT_FAILURE); +} +} // namespace + +int main() { + testTerminateDispatch(); + return EXIT_FAILURE; +} diff --git a/src/mongo/stdx/set_terminate_from_thread_die_in_thread_test.cpp b/src/mongo/stdx/set_terminate_from_thread_die_in_thread_test.cpp new file mode 100644 index 00000000000..119d5b44c5d --- /dev/null +++ b/src/mongo/stdx/set_terminate_from_thread_die_in_thread_test.cpp @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/stdx/exception.h" + +#include + +#include + +#include "mongo/stdx/thread.h" + +namespace { + +namespace stdx = ::mongo::stdx; + +void writeFeedbackAndCleanlyExit() { + std::cout << "Entered terminate handler." << std::endl; + exit(EXIT_SUCCESS); +} + +void testTerminateDispatch() { + std::cout << "Starting background thread (which will call `set_terminate`)." << std::endl; + stdx::thread{[] { + std::cout << "Setting terminate handler" << std::endl; + stdx::set_terminate(writeFeedbackAndCleanlyExit); + }} + .join(); + std::cout << "Starting background thread (which will terminate)." << std::endl; + stdx::thread{[] { + std::cout << "Calling terminate from background thread." << std::endl; + std::terminate(); + }} + .join(); + exit(EXIT_FAILURE); +} +} // namespace + +int main() { + testTerminateDispatch(); + return EXIT_FAILURE; +} diff --git a/src/mongo/stdx/set_terminate_internals.cpp b/src/mongo/stdx/set_terminate_internals.cpp new file mode 100644 index 00000000000..68e709388b2 --- /dev/null +++ b/src/mongo/stdx/set_terminate_internals.cpp @@ -0,0 +1,95 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + + +#include "mongo/stdx/exception.h" + +#include +#include + +#if defined(_WIN32) + +namespace mongo { +namespace stdx { +// `dispatch_impl` is circularly dependent with the initialization of `terminationHandler`, but +// should not have linkage. To facilitate matching the definition to the declaration, we make this +// function `static`, rather than placing it in the anonymous namespace. +[[noreturn]] static void dispatch_impl() noexcept; + +namespace { +void uninitializedTerminateHandler() {} + +::std::atomic terminationHandler(&uninitializedTerminateHandler); // NOLINT + +void registerTerminationHook() noexcept { + const auto oldHandler = + terminationHandler.exchange(::std::set_terminate(&dispatch_impl)); // NOLINT + if (oldHandler != &uninitializedTerminateHandler) + std::abort(); // Someone set the handler, somehow before we got to initialize ourselves. +} + + +[[maybe_unused]] const int initializeTerminationHandler = []() noexcept { + registerTerminationHook(); + return 0; +} +(); + +} // namespace +} // namespace stdx + +stdx::terminate_handler stdx::set_terminate(const stdx::terminate_handler handler) noexcept { + const auto oldHandler = terminationHandler.exchange(handler); + if (oldHandler == &uninitializedTerminateHandler) + std::abort(); // Do not let people set terminate before the initializer has run. + return oldHandler; +} + +stdx::terminate_handler stdx::get_terminate() noexcept { + const auto currentHandler = terminationHandler.load(); + if (currentHandler == &uninitializedTerminateHandler) + std::abort(); // Do not let people see the terminate handler before the initializer has + // run. + return currentHandler; +} + +void stdx::dispatch_impl() noexcept { + if (const ::std::terminate_handler handler = terminationHandler.load()) + handler(); + + // The standard says that returning from your handler is undefined. We may as well make the + // wrapper have stronger guarantees. + std::abort(); +} + +void stdx::TerminateHandlerDetailsInterface::dispatch() noexcept { + return stdx::dispatch_impl(); +} +} // namespace mongo +#endif diff --git a/src/mongo/stdx/thread.h b/src/mongo/stdx/thread.h index 2968e9dcae2..43a7cf52879 100644 --- a/src/mongo/stdx/thread.h +++ b/src/mongo/stdx/thread.h @@ -35,6 +35,8 @@ #include #include +#include "mongo/stdx/exception.h" + namespace mongo { namespace stdx { @@ -60,37 +62,44 @@ public: using ::std::thread::id; // NOLINT using ::std::thread::native_handle_type; // NOLINT - thread() noexcept : ::std::thread::thread() {} // NOLINT + thread() noexcept = default; + ~thread() noexcept = default; thread(const thread&) = delete; - - thread(thread&& other) noexcept - : ::std::thread::thread(static_cast<::std::thread&&>(std::move(other))) {} // NOLINT + thread(thread&& other) noexcept = default; + thread& operator=(const thread&) = delete; + thread& operator=(thread&& other) noexcept = default; /** * As of C++14, the Function overload for std::thread requires that this constructor only * participate in overload resolution if std::decay_t is not the same type as thread. * That prevents this overload from intercepting calls that might generate implicit conversions * before binding to other constructors (specifically move/copy constructors). + * + * NOTE: The `Function f` parameter must be taken by value, not reference or forwarding + * reference, as it is used on the far side of the thread launch, and this ctor has to properly + * transfer ownership to the far side's thread. */ - template < - class Function, - class... Args, - typename std::enable_if::type>::value, - int>::type = 0> - explicit thread(Function&& f, Args&&... args) try: - ::std::thread::thread(std::forward(f), std::forward(args)...) {} // NOLINT - catch (...) { - std::terminate(); + template >, int> = 0> + explicit thread(Function f, Args&&... args) noexcept + : ::std::thread::thread( // NOLINT + [ + f = std::move(f), + pack = std::make_tuple(std::forward(args)...) + ]() mutable noexcept { +#if defined(_WIN32) + // On Win32 we have to set the terminate handler per thread. + // We set it to our universal terminate handler, which people can register via the + // `stdx::set_terminate` hook. + ::std::set_terminate( // NOLINT + ::mongo::stdx::TerminateHandlerDetailsInterface::dispatch); +#endif + return std::apply(std::move(f), std::move(pack)); + }) { } - thread& operator=(const thread&) = delete; - - thread& operator=(thread&& other) noexcept { - return static_cast( - ::std::thread::operator=(static_cast<::std::thread&&>(std::move(other)))); // NOLINT - }; - using ::std::thread::get_id; // NOLINT using ::std::thread::hardware_concurrency; // NOLINT using ::std::thread::joinable; // NOLINT @@ -100,10 +109,11 @@ public: using ::std::thread::join; // NOLINT void swap(thread& other) noexcept { - ::std::thread::swap(static_cast<::std::thread&>(other)); // NOLINT + this->::std::thread::swap(other); // NOLINT } }; + inline void swap(thread& lhs, thread& rhs) noexcept { lhs.swap(rhs); } @@ -142,4 +152,7 @@ void sleep_until(const std::chrono::time_point& sleep_time) { } // namespace this_thread } // namespace stdx + +static_assert(std::is_move_constructible_v); +static_assert(std::is_move_assignable_v); } // namespace mongo -- cgit v1.2.1