diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 89c4bfbbed5..2eb3fedd174 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -241,7 +241,7 @@ env.CppUnitTest('sock_test', ['util/net/sock_test.cpp'], env.CppUnitTest('curop_test', ['db/curop_test.cpp'], - LIBDEPS=['serveronly', 'coredb', 'coreserver'], + LIBDEPS=['serveronly', 'coredb', 'coreserver', 'ntservice_mock'], NO_CRUTCH=True) env.Library('index_names',["db/index_names.cpp"]) @@ -583,7 +583,10 @@ env.Library('ntservice', ['util/ntservice.cpp'], if windows: env.CppUnitTest('ntservice_test', 'util/ntservice_test.cpp', LIBDEPS=['ntservice'], - LIBS=['shell32', env['LIBS']]) + LIBS=['shell32', env['LIBS']], + NO_CRUTCH=True) + +env.Library('ntservice_mock', ['util/ntservice_mock.cpp']) env.Library( target='scripting_common', @@ -895,7 +898,8 @@ env.CppUnitTest("scoped_db_conn_test", [ "client/scoped_db_conn_test.cpp" ], "coreshard", "mongocommon", "message_server_port", - "mongoscore"], + "mongoscore", + "ntservice_mock"], NO_CRUTCH=True) env.CppUnitTest("shard_test", [ "s/shard_test.cpp" ], @@ -936,6 +940,7 @@ env.CppUnitTest( "$BUILD_DIR/mongo/serveronly", "$BUILD_DIR/mongo/coreserver", "$BUILD_DIR/mongo/coredb", + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True, ) @@ -949,6 +954,7 @@ env.CppUnitTest( "$BUILD_DIR/mongo/serveronly", "$BUILD_DIR/mongo/coreserver", "$BUILD_DIR/mongo/coredb", + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True, ) @@ -962,6 +968,7 @@ env.CppUnitTest( "$BUILD_DIR/mongo/serveronly", "$BUILD_DIR/mongo/coreserver", "$BUILD_DIR/mongo/coredb", + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True, ) @@ -1124,6 +1131,7 @@ env.Install( "tools/mongobridge_options_init.cpp" ], LIBDEPS=[ + "ntservice_mock", "serveronly", "coreserver", "coredb", @@ -1135,6 +1143,7 @@ env.Install( "client/examples/mongoperf.cpp", ], LIBDEPS=[ + "ntservice_mock", "serveronly", "coreserver", "coredb", @@ -1227,6 +1236,7 @@ env.Install('$BUILD_ROOT/', env.Program('file_allocator_bench', 'serveronly', 'coredb', 'coreserver', + "ntservice_mock", ])) env.Alias('file_allocator_bench', "$BUILD_ROOT/" + add_exe("file_allocator_bench")) diff --git a/src/mongo/client/scoped_db_conn_test.cpp b/src/mongo/client/scoped_db_conn_test.cpp index 600b54d2908..9f19c3a7ce7 100644 --- a/src/mongo/client/scoped_db_conn_test.cpp +++ b/src/mongo/client/scoped_db_conn_test.cpp @@ -77,6 +77,8 @@ namespace mongo { return shuttingDown; } + void signalShutdown() {} + DBClientBase* createDirectClient(OperationContext* txn) { return NULL; } void dbexit(ExitCode rc, const char *why){ diff --git a/src/mongo/db/dbcommands_generic.cpp b/src/mongo/db/dbcommands_generic.cpp index 0ccff8711ba..c4f705f6dd0 100644 --- a/src/mongo/db/dbcommands_generic.cpp +++ b/src/mongo/db/dbcommands_generic.cpp @@ -58,6 +58,7 @@ #include "mongo/util/fail_point_service.h" #include "mongo/util/log.h" #include "mongo/util/md5.hpp" +#include "mongo/util/ntservice.h" #include "mongo/util/processinfo.h" #include "mongo/util/ramlog.h" #include "mongo/util/version_reporting.h" @@ -323,8 +324,23 @@ namespace mongo { log() << "terminating, shutdown command received" << endl; - exitCleanly(EXIT_CLEAN); // this never returns - invariant(false); +#if defined(_WIN32) + // Signal the ServiceMain thread to shutdown. + if(ntservice::shouldStartService()) { + signalShutdown(); + + // Client expects us to abruptly close the socket as part of exiting + // so this function is not allowed to return. + // The ServiceMain thread will quit for us so just sleep until it does. + while (true) + sleepsecs(60); // Loop forever + } + else +#endif + { + exitCleanly(EXIT_CLEAN); // this never returns + invariant(false); + } } /* for testing purposes only */ diff --git a/src/mongo/db/exec/SConscript b/src/mongo/db/exec/SConscript index 37e91bd8476..f8f35573827 100644 --- a/src/mongo/db/exec/SConscript +++ b/src/mongo/db/exec/SConscript @@ -87,6 +87,7 @@ env.CppUnitTest( "$BUILD_DIR/mongo/coreserver", "$BUILD_DIR/mongo/coredb", "$BUILD_DIR/mongo/mocklib", + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True, ) @@ -102,6 +103,7 @@ env.CppUnitTest( "$BUILD_DIR/mongo/coreserver", "$BUILD_DIR/mongo/coredb", "$BUILD_DIR/mongo/mocklib", + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True, ) @@ -116,6 +118,7 @@ env.CppUnitTest( "$BUILD_DIR/mongo/serveronly", "$BUILD_DIR/mongo/coreserver", "$BUILD_DIR/mongo/coredb", + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True, ) diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 9e3ca6438f9..3f19f5769f1 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -1105,12 +1105,25 @@ namespace mongo { getGlobalEnvironment()->shutdownGlobalStorageEngineCleanly(); } + // shutdownLock + // + // Protects: + // Ensures shutdown is single threaded. + // Lock Ordering: + // No restrictions + boost::mutex shutdownLock; + + void signalShutdown() { + // Notify all threads shutdown has started + shutdownInProgress.fetchAndAdd(1); + } + void exitCleanly(ExitCode code) { - if (shutdownInProgress.compareAndSwap(0, 1) != 0) { - while (true) { - sleepsecs(1000); - } - } + // Notify all threads shutdown has started + shutdownInProgress.fetchAndAdd(1); + + // Grab the shutdown lock to prevent concurrent callers + boost::lock_guard lockguard(shutdownLock); // Global storage engine may not be started in all cases before we exit if (getGlobalEnvironment()->getGlobalStorageEngine() == NULL) { diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript index 6f2af6b5f2e..bc9b62945e2 100644 --- a/src/mongo/db/query/SConscript +++ b/src/mongo/db/query/SConscript @@ -61,6 +61,7 @@ env.CppUnitTest( "$BUILD_DIR/mongo/coreserver", "$BUILD_DIR/mongo/coredb", "$BUILD_DIR/mongo/mocklib", + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True, ) @@ -236,6 +237,7 @@ env.CppUnitTest( "$BUILD_DIR/mongo/coreserver", "$BUILD_DIR/mongo/coredb", "$BUILD_DIR/mongo/mocklib", + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True, ) diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index f4709cb2d8c..dae6b5c3f0e 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -209,5 +209,6 @@ env.CppUnitTest('isself_test', '$BUILD_DIR/mongo/serveronly', '$BUILD_DIR/mongo/coreserver', '$BUILD_DIR/mongo/coredb', + "$BUILD_DIR/mongo/ntservice_mock", ], NO_CRUTCH = True) diff --git a/src/mongo/db/storage/mmap_v1/SConscript b/src/mongo/db/storage/mmap_v1/SConscript index 69c20e8b39e..bacd25fa446 100644 --- a/src/mongo/db/storage/mmap_v1/SConscript +++ b/src/mongo/db/storage/mmap_v1/SConscript @@ -89,6 +89,7 @@ env.CppUnitTest( '$BUILD_DIR/mongo/serveronly', '$BUILD_DIR/mongo/coreserver', '$BUILD_DIR/mongo/coredb', + '$BUILD_DIR/mongo/ntservice_mock', ], NO_CRUTCH=True, ) diff --git a/src/mongo/db/storage/wiredtiger/SConscript b/src/mongo/db/storage/wiredtiger/SConscript index dc0ccc3be30..5b58b579681 100644 --- a/src/mongo/db/storage/wiredtiger/SConscript +++ b/src/mongo/db/storage/wiredtiger/SConscript @@ -88,6 +88,7 @@ if wiredtiger: '$BUILD_DIR/mongo/serveronly', '$BUILD_DIR/mongo/coreserver', '$BUILD_DIR/mongo/coredb', + '$BUILD_DIR/mongo/ntservice_mock', ], NO_CRUTCH=True, ) diff --git a/src/mongo/dbtests/framework.cpp b/src/mongo/dbtests/framework.cpp index 2b859fc8c2c..d2e7fdb1bfe 100644 --- a/src/mongo/dbtests/framework.cpp +++ b/src/mongo/dbtests/framework.cpp @@ -135,6 +135,14 @@ namespace mongo { } } // namespace dbtests +#ifdef _WIN32 +namespace ntservice { + bool shouldStartService() { + return false; + } +} +#endif + } // namespace mongo void mongo::unittest::onCurrentTestNameChange( const std::string &testName ) { diff --git a/src/mongo/s/server.cpp b/src/mongo/s/server.cpp index a8458f71c1f..d097fb7253b 100644 --- a/src/mongo/s/server.cpp +++ b/src/mongo/s/server.cpp @@ -475,6 +475,11 @@ int main(int argc, char* argv[], char** envp) { #undef exit +void mongo::signalShutdown() { + // Notify all threads shutdown has started + dbexitCalled = true; +} + void mongo::exitCleanly(ExitCode code) { // TODO: do we need to add anything? mongo::dbexit( code ); @@ -483,7 +488,11 @@ void mongo::exitCleanly(ExitCode code) { void mongo::dbexit( ExitCode rc, const char *why ) { dbexitCalled = true; audit::logShutdown(ClientBasic::getCurrent()); + #if defined(_WIN32) + // Windows Service Controller wants to be told when we are done shutting down + // and call quickExit itself. + // if ( rc == EXIT_WINDOWS_SERVICE_STOP ) { log() << "dbexit: exiting because Windows service was stopped" << endl; return; diff --git a/src/mongo/unittest/crutch.cpp b/src/mongo/unittest/crutch.cpp index 4a86df46ce0..59859207e5b 100644 --- a/src/mongo/unittest/crutch.cpp +++ b/src/mongo/unittest/crutch.cpp @@ -64,4 +64,14 @@ namespace mongo { invariant(!"unittests shouldn't call exitCleanly"); } +#ifdef _WIN32 + void signalShutdown() {} + +namespace ntservice { + bool shouldStartService() { + return false; + } +} +#endif + } // namespace mongo diff --git a/src/mongo/util/exit_code.h b/src/mongo/util/exit_code.h index f538641a9e6..bff937ba808 100644 --- a/src/mongo/util/exit_code.h +++ b/src/mongo/util/exit_code.h @@ -65,4 +65,12 @@ namespace mongo { */ void exitCleanly(ExitCode code); + /** + * Signal main or ServiceMain thread to exit + * Important for the ServiceMain thread to do the exit when mongod/s are running as NT Services + * on Windows. + * It is not required to be called before exitCleanly in the general case, only for + * proper NT Service shutdown. + */ + void signalShutdown(); } // namespace mongo diff --git a/src/mongo/util/ntservice.cpp b/src/mongo/util/ntservice.cpp index eb19146bc65..8e8b66a2179 100644 --- a/src/mongo/util/ntservice.cpp +++ b/src/mongo/util/ntservice.cpp @@ -525,12 +525,12 @@ namespace { // Stop the process // During clean shutdown, ie NT SCM signals us, _serviceCallback returns here - // as part of the listener loop terminating so we do not have to stop twice in this case - if ( ! inShutdown() ) { - // TODO: SERVER-5703, separate the "cleanup for shutdown" functionality from - // the "terminate process" functionality in exitCleanly. - exitCleanly( EXIT_WINDOWS_SERVICE_STOP ); - } + // as part of the listener loop terminating. + // exitCleanly is supposed to return. If it blocks, some other thread must be exiting. + // + // TODO: SERVER-5703, separate the "cleanup for shutdown" functionality from + // the "terminate process" functionality in exitCleanly. + exitCleanly( EXIT_WINDOWS_SERVICE_STOP ); reportStatus(SERVICE_STOPPED, 0, exitCode); } @@ -543,12 +543,9 @@ namespace { reportStatus( SERVICE_STOP_PENDING ); - if ( ! inShutdown() ) { - // TODO: SERVER-5703, separate the "cleanup for shutdown" functionality from - // the "terminate process" functionality in exitCleanly. - // Note: This triggers _serviceCallback to stop by setting inShutdown() == true - exitCleanly( EXIT_WINDOWS_SERVICE_STOP ); - } + // Note: This triggers _serviceCallback, ie ServiceMain, + // to stop by setting inShutdown() == true + signalShutdown(); // Note: we will report exit status in initService } diff --git a/src/mongo/util/ntservice_mock.cpp b/src/mongo/util/ntservice_mock.cpp new file mode 100644 index 00000000000..65c2be70641 --- /dev/null +++ b/src/mongo/util/ntservice_mock.cpp @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General 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 GNU Affero General 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. + */ + +namespace mongo { +namespace ntservice { + + bool shouldStartService() { + return false; + } + +} // namespace ntservice +} // namespace mongo diff --git a/src/mongo/util/ntservice_test.cpp b/src/mongo/util/ntservice_test.cpp index c8d5a727822..4d07b4ed7e3 100644 --- a/src/mongo/util/ntservice_test.cpp +++ b/src/mongo/util/ntservice_test.cpp @@ -117,4 +117,7 @@ namespace mongo { void Client::initThread(const char* desc, AbstractMessagingPort* mp) { } void removeControlCHandler() {} + void signalShutdown() {} + bool inShutdown() { return false; } + void exitCleanly(ExitCode code) { } } // namespace mongo