/**
* Copyright (C) 2013 10gen 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.
*/
#pragma once
#include
#include "mongo/base/disallow_copying.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/platform/unordered_map.h"
#include "mongo/stdx/condition_variable.h"
#include "mongo/stdx/mutex.h"
#include "mongo/stdx/thread.h"
#include "mongo/util/concurrency/mutex.h"
#include "mongo/util/time_support.h"
namespace mongo {
// Returns the current interrupt interval from the setParameter value
int getScriptingEngineInterruptInterval();
/**
* DeadlineMonitor
*
* Monitors tasks which are required to complete before a deadline. When
* a deadline is started on a _Task*, either the deadline must be stopped,
* or _Task::kill() will be called when the deadline arrives.
*
* Each instance of a DeadlineMonitor spawns a thread which waits for one of the
* following conditions:
* - a task is added to the monitor
* - a task is removed from the monitor
* - the nearest deadline has arrived
*
* Ownership:
* The _Task* must not be freed until the deadline has elapsed or stopDeadline()
* has been called.
*
* NOTE: Each instance of this class spawns a new thread. It is intended to be a stop-gap
* solution for simple deadline monitoring until a more robust solution can be
* implemented.
*
* NOTE: timing is based on wallclock time, which may not be precise.
*/
template
class DeadlineMonitor {
MONGO_DISALLOW_COPYING(DeadlineMonitor);
public:
DeadlineMonitor() {
// NOTE(schwerin): Because _monitorThread takes a pointer to "this", all of the fields
// of this instance must be initialized before the thread is created. As a result, we
// should not create the thread in the initializer list. Creating it there leaves us
// vulnerable to errors introduced by rearranging the order of fields in the class.
_monitorThread = stdx::thread(&mongo::DeadlineMonitor<_Task>::deadlineMonitorThread, this);
}
~DeadlineMonitor() {
{
// ensure the monitor thread has been stopped before destruction
stdx::lock_guard lk(_deadlineMutex);
_inShutdown = true;
_newDeadlineAvailable.notify_one();
}
_monitorThread.join();
}
/**
* Start monitoring a task for deadline lapse. User must call stopDeadline() before
* deleting the task. Note that stopDeadline() cannot be called from within the
* kill() method.
* @param task the task to kill()
* @param timeoutMs number of milliseconds before the deadline expires
*/
void startDeadline(_Task* const task, int64_t timeoutMs) {
Date_t deadline;
if (timeoutMs > 0) {
deadline = Date_t::now() + Milliseconds(timeoutMs);
} else {
deadline = Date_t::max();
}
stdx::lock_guard lk(_deadlineMutex);
if (_tasks.find(task) == _tasks.end()) {
_tasks.emplace(task, deadline);
}
if (deadline < _nearestDeadlineWallclock) {
_nearestDeadlineWallclock = deadline;
_newDeadlineAvailable.notify_one();
}
}
/**
* Stop monitoring a task. Can be called multiple times, before or after a
* deadline has expired (as long as the task remains allocated).
* @return true if the task was found and erased
*/
bool stopDeadline(_Task* const task) {
stdx::lock_guard lk(_deadlineMutex);
return _tasks.erase(task);
}
private:
/**
* Main deadline monitor loop. Waits on a condition variable until a task
* is started, stopped, or the nearest deadline arrives. If a deadline arrives,
* _Task::kill() is invoked.
*/
void deadlineMonitorThread() {
stdx::unique_lock lk(_deadlineMutex);
Date_t lastInterruptCycle = Date_t::now();
while (!_inShutdown) {
// get the next interval to wait
const Date_t now = Date_t::now();
const auto interruptInterval = Milliseconds{getScriptingEngineInterruptInterval()};
if ((interruptInterval.count() > 0) && (now - lastInterruptCycle > interruptInterval)) {
for (const auto& task : _tasks) {
if (task.second > now)
task.first->interrupt();
}
lastInterruptCycle = now;
}
// wait for a task to be added or a deadline to expire
if (_nearestDeadlineWallclock > now) {
if (_nearestDeadlineWallclock == Date_t::max()) {
if ((interruptInterval.count() > 0) &&
(_nearestDeadlineWallclock - now > interruptInterval)) {
_newDeadlineAvailable.wait_for(lk, interruptInterval.toSystemDuration());
} else {
_newDeadlineAvailable.wait(lk);
}
} else {
_newDeadlineAvailable.wait_until(lk,
_nearestDeadlineWallclock.toSystemTimePoint());
}
continue;
}
// set the next interval to wait for deadline completion
_nearestDeadlineWallclock = Date_t::max();
auto i = _tasks.begin();
while (i != _tasks.end()) {
if (i->second < now) {
// deadline expired
i->first->kill();
_tasks.erase(i++);
} else {
if (i->second < _nearestDeadlineWallclock) {
// nearest deadline seen so far
_nearestDeadlineWallclock = i->second;
}
++i;
}
}
}
}
using TaskDeadlineMap = stdx::unordered_map<_Task*, Date_t>;
TaskDeadlineMap _tasks; // map of running tasks with deadlines
stdx::mutex _deadlineMutex; // protects all non-const members, except _monitorThread
stdx::condition_variable _newDeadlineAvailable; // Signaled for timeout, start and stop
stdx::thread _monitorThread; // the deadline monitor thread
Date_t _nearestDeadlineWallclock = Date_t::max(); // absolute time of the nearest deadline
bool _inShutdown = false;
};
} // namespace mongo