/** * 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. */ #pragma once #include #include #include "mongo/base/status.h" #include "mongo/platform/mutex.h" #include "mongo/util/clock_source.h" #include "mongo/util/functional.h" #include "mongo/util/future.h" namespace mongo { /* * An alarm scheduler will fill a Future at some time in the future and allow the caller to * cancel specific alarms by opaque ID. * * All alarm service implementations take a ClockSource and all Date_t/Durations used to schedule * alarms must be in relation to that ClockSource's now() and epoch. * * A scheduler won't actually process alarms. Some executor must be calling processExpiredAlarms() * in order for alarms to fire. */ class AlarmScheduler { public: /* * This type gets returned when an alarm is scheduled and allows you to cancel the alarm. * * Once this handle is destroyed, the alarm will still be processed if it is still outstanding * and there will be no way to cancel it. */ class Handle { public: virtual ~Handle() = default; /* * Cancels the alarm if it has not already been fulfilled. If the alarm has already been * fulfilled this returns an ErrorCodes::AlarmAlreadyFulfilled error */ virtual Status cancel() = 0; }; using SharedHandle = std::shared_ptr; using AlarmCount = uint64_t; explicit AlarmScheduler(ClockSource* clockSource) : _clockSource(clockSource) {} virtual ~AlarmScheduler() = default; /* * Fulfills all outstanding alarms with CallbackCanceled. Memory associated with the scheduler * may not be freed until the last outstanding Handle is destroyed, however there should be * no broken promises. * * This will be called implicitly by the destructor. */ virtual void clearAllAlarms() = 0; /* * Clears all alarms as above, and prevents any new alarms from being scheduled. Calls to * alarmAt will return a ready Future with a ShutdownInProgress error code. */ virtual void clearAllAlarmsAndShutdown() = 0; struct Alarm { Future future; SharedHandle handle; }; /* * Schedules an alarm some milliseconds from now(). */ Alarm alarmFromNow(Milliseconds time) { return alarmAt(_clockSource->now() + time); }; /* * Schedules an alarm at a specific time on the service's clock source. */ virtual Alarm alarmAt(Date_t time) = 0; /* * Registers a callback that will be called when a new alarm has been registered. * * The hook will be called with a Date_t representing the next time an alarm will expire after * all internal locks have been released. This can be used to unblock between calling * processExpiredAlarms() with a new amount of time to block for. */ using AlarmRegisterHook = unique_function&)>; void setAlarmRegisterHook(AlarmRegisterHook onAlarmHook) { _registerHook = std::move(onAlarmHook); } /* * Processes all alarms that have expired as of now(). If maxAlarms is not boost::none, then * this will only expire that many alarms before returning. * * Returns the number of alarms that were expired by this call. */ using AlarmExpireHook = unique_function; virtual void processExpiredAlarms(boost::optional hook = boost::none) = 0; /* * Returns the Date_t of the next scheduled alarm. */ virtual Date_t nextAlarm() = 0; virtual ClockSource* clockSource() const { return _clockSource; } protected: void callRegisterHook(Date_t nextAlarm, const std::shared_ptr& which) { if (_registerHook) { _registerHook(nextAlarm, which); } } private: AlarmRegisterHook _registerHook; ClockSource* const _clockSource; }; /* * Implements a basic alarm scheduler based on a multimap of date_t to promise. * * Scheduling an alarm takes O(log(n)) where n is the number of outstanding alarms. * Canceling an alarm is done in constant time. * Processing alarms is done in constant time. */ class AlarmSchedulerPrecise : public AlarmScheduler, public std::enable_shared_from_this { public: explicit AlarmSchedulerPrecise(ClockSource* clockSource) : AlarmScheduler(clockSource) {} ~AlarmSchedulerPrecise(); void clearAllAlarms() override; void clearAllAlarmsAndShutdown() override; Alarm alarmAt(Date_t time) override; void processExpiredAlarms(boost::optional hook) override; Date_t nextAlarm() override; private: class HandleImpl; enum AlarmState { kOutstanding, kFulfilled, kAbandoned }; struct AlarmData { explicit AlarmData(Promise promise_) : promise(std::move(promise_)) {} std::weak_ptr handle; Promise promise; }; using AlarmMap = std::multimap; using AlarmMapIt = AlarmMap::iterator; void _clearAllAlarmsImpl(stdx::unique_lock& lk); Mutex _mutex = MONGO_MAKE_LATCH("AlarmSchedulerPrecise::_mutex"); bool _shutdown = false; AlarmMap _alarms; }; } // namespace mongo