/** * Copyright (C) 2018-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/platform/basic.h" #include "mongo/platform/atomic_word.h" #include "mongo/platform/mutex.h" #include "mongo/stdx/thread.h" #include "mongo/unittest/unittest.h" #include "mongo/util/background.h" #include "mongo/util/concurrency/notification.h" #include "mongo/util/time_support.h" namespace mongo { namespace { class TestJob final : public BackgroundJob { public: TestJob(bool selfDelete, AtomicWord* flag, Notification* canProceed = nullptr, Notification* destructorInvoked = nullptr) : BackgroundJob(selfDelete), _flag(flag), _canProceed(canProceed), _destructorInvoked(destructorInvoked) {} ~TestJob() override { if (_destructorInvoked) _destructorInvoked->set(); } std::string name() const override { return "TestJob"; } void run() override { if (_canProceed) _canProceed->get(); _flag->store(true); } private: AtomicWord* const _flag; Notification* const _canProceed; Notification* const _destructorInvoked; }; TEST(BackgroundJobBasic, NormalCase) { AtomicWord flag(false); TestJob tj(false, &flag); tj.go(); ASSERT(tj.wait()); ASSERT_EQUALS(true, flag.load()); } TEST(BackgroundJobBasic, TimeOutCase) { AtomicWord flag(false); Notification canProceed; TestJob tj(false, &flag, &canProceed); tj.go(); ASSERT(!tj.wait(1000)); ASSERT_EQUALS(false, flag.load()); canProceed.set(); ASSERT(tj.wait()); ASSERT_EQUALS(true, flag.load()); } TEST(BackgroundJobBasic, SelfDeletingCase) { AtomicWord flag(false); Notification destructorInvoked; // Though it looks like one, this is not a leak since the job is self deleting. (new TestJob(true, &flag, nullptr, &destructorInvoked))->go(); destructorInvoked.get(); ASSERT_EQUALS(true, flag.load()); } TEST(BackgroundJobLifeCycle, Go) { class Job : public BackgroundJob { public: Job() : _hasRun(false) {} virtual std::string name() const { return "BackgroundLifeCycle::CannotCallGoAgain"; } virtual void run() { { stdx::lock_guard lock(_mutex); ASSERT_FALSE(_hasRun); _hasRun = true; } _n.get(); } void notify() { _n.set(); } private: Mutex _mutex = MONGO_MAKE_LATCH("Job::_mutex"); bool _hasRun; Notification _n; }; Job j; // This call starts Job running. j.go(); // Calling 'go' again while it is running is an error. ASSERT_THROWS(j.go(), AssertionException); // Stop the Job j.notify(); j.wait(); // Calling 'go' on a done task is a no-op. If it were not, // we would fail the assert in Job::run above. j.go(); } } // namespace } // namespace mongo