diff options
-rwxr-xr-x | buildscripts/cpplint.py | 2 | ||||
-rw-r--r-- | src/mongo/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/stdx/SConscript | 63 | ||||
-rw-r--r-- | src/mongo/stdx/exception.h | 70 | ||||
-rw-r--r-- | src/mongo/stdx/set_terminate_dispatch_test.cpp | 59 | ||||
-rw-r--r-- | src/mongo/stdx/set_terminate_from_main_die_in_thread_test.cpp | 63 | ||||
-rw-r--r-- | src/mongo/stdx/set_terminate_from_thread_die_in_main_test.cpp | 63 | ||||
-rw-r--r-- | src/mongo/stdx/set_terminate_from_thread_die_in_thread_test.cpp | 67 | ||||
-rw-r--r-- | src/mongo/stdx/set_terminate_internals.cpp | 95 | ||||
-rw-r--r-- | src/mongo/stdx/thread.h | 55 | ||||
-rw-r--r-- | src/mongo/util/signal_handlers_synchronous.cpp | 3 |
11 files changed, 519 insertions, 22 deletions
diff --git a/buildscripts/cpplint.py b/buildscripts/cpplint.py index fd8feb579ee..ca7598ce9b7 100755 --- a/buildscripts/cpplint.py +++ b/buildscripts/cpplint.py @@ -1624,6 +1624,7 @@ def make_polyfill_regex(): 'defer_lock', 'future', 'future_status', + 'get_terminate', 'launch', 'lock_guard', 'mutex', @@ -1631,6 +1632,7 @@ def make_polyfill_regex(): 'packaged_task', 'promise', 'recursive_mutex', + 'set_terminate', 'shared_lock', 'shared_mutex', 'shared_timed_mutex', diff --git a/src/mongo/SConscript b/src/mongo/SConscript index bd6cfeb6802..9b4a524382a 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -163,6 +163,7 @@ baseEnv.Library( '$BUILD_DIR/third_party/shim_pcrecpp', '$BUILD_DIR/third_party/shim_unwind' if use_libunwind else [], 'boost_assert_shim', + 'stdx/stdx', 'util/quick_exit', ], LIBDEPS_PRIVATE=[ 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 <atomic> +#include <exception> +#include <utility> + +// 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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 <stdlib.h> + +#include <iostream> + +#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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 <stdlib.h> + +#include <iostream> + +#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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 <stdlib.h> + +#include <iostream> + +#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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 <stdlib.h> + +#include <iostream> + +#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 + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 <atomic> +#include <utility> + +#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<terminate_handler> 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 <thread> #include <type_traits> +#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<Function> 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<!std::is_same<thread, typename std::decay<Function>::type>::value, - int>::type = 0> - explicit thread(Function&& f, Args&&... args) try: - ::std::thread::thread(std::forward<Function>(f), std::forward<Args>(args)...) {} // NOLINT - catch (...) { - std::terminate(); + template <class Function, + class... Args, + std::enable_if_t<!std::is_same_v<thread, std::decay_t<Function>>, int> = 0> + explicit thread(Function f, Args&&... args) noexcept + : ::std::thread::thread( // NOLINT + [ + f = std::move(f), + pack = std::make_tuple(std::forward<Args>(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<thread&>( - ::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<Clock, Duration>& sleep_time) { } // namespace this_thread } // namespace stdx + +static_assert(std::is_move_constructible_v<stdx::thread>); +static_assert(std::is_move_assignable_v<stdx::thread>); } // namespace mongo diff --git a/src/mongo/util/signal_handlers_synchronous.cpp b/src/mongo/util/signal_handlers_synchronous.cpp index d6e595218e2..da60a37fd1f 100644 --- a/src/mongo/util/signal_handlers_synchronous.cpp +++ b/src/mongo/util/signal_handlers_synchronous.cpp @@ -46,6 +46,7 @@ #include "mongo/logger/log_domain.h" #include "mongo/logger/logger.h" #include "mongo/platform/compiler.h" +#include "mongo/stdx/exception.h" #include "mongo/stdx/thread.h" #include "mongo/util/concurrency/thread_name.h" #include "mongo/util/debug_util.h" @@ -288,7 +289,7 @@ void abruptQuitWithAddrSignal(int signalNum, siginfo_t* siginfo, void* ucontext_ } // namespace void setupSynchronousSignalHandlers() { - std::set_terminate(myTerminate); + stdx::set_terminate(myTerminate); std::set_new_handler(reportOutOfMemoryErrorAndExit); #if defined(_WIN32) |