PageRenderTime 59ms CodeModel.GetById 10ms app.highlight 43ms RepoModel.GetById 2ms app.codeStats 0ms

/mordor/timer.cpp

http://github.com/mozy/mordor
C++ | 346 lines | 297 code | 31 blank | 18 comment | 50 complexity | d912f72972613248b79c02403e38dfaf MD5 | raw file
  1// Copyright (c) 2009 - Mozy, Inc.
  2
  3#include "timer.h"
  4
  5#include <algorithm>
  6#include <vector>
  7
  8#include "assert.h"
  9#include "atomic.h"
 10#include "exception.h"
 11#include "log.h"
 12#include "version.h"
 13#include "util.h"
 14#include "mordor/config.h"
 15
 16#ifdef OSX
 17 #include <mach/mach_time.h>
 18#elif defined(WINDOWS)
 19 #include <windows.h>  // for LARGE_INTEGER, QueryPerformanceFrequency()
 20#else
 21 #include <sys/time.h>
 22 #include <time.h>
 23#endif
 24
 25namespace Mordor {
 26
 27boost::function<unsigned long long ()> TimerManager::ms_clockDg;
 28
 29static Logger::ptr g_log = Log::lookup("mordor:timer");
 30
 31// now() doesn't roll over at 0xffff...., because underlying hardware timers
 32// do not count in microseconds; also, prevent clock anomalies from causing
 33// timers that will never expire
 34static ConfigVar<unsigned long long>::ptr g_clockRolloverThreshold =
 35    Config::lookup<unsigned long long>("timer.clockrolloverthreshold", 5000000ULL,
 36    "Expire all timers if the clock goes backward by >= this amount");
 37
 38static void
 39stubOnTimer(boost::weak_ptr<void> weakCond, boost::function<void ()> dg);
 40
 41#ifdef WINDOWS
 42static unsigned long long queryFrequency()
 43{
 44    LARGE_INTEGER frequency;
 45    BOOL bRet = QueryPerformanceFrequency(&frequency);
 46    MORDOR_ASSERT(bRet);
 47    return (unsigned long long)frequency.QuadPart;
 48}
 49
 50unsigned long long g_frequency = queryFrequency();
 51#elif defined (OSX)
 52static mach_timebase_info_data_t queryTimebase()
 53{
 54    mach_timebase_info_data_t timebase;
 55    mach_timebase_info(&timebase);
 56    return timebase;
 57}
 58mach_timebase_info_data_t g_timebase = queryTimebase();
 59#endif
 60
 61unsigned long long
 62TimerManager::now()
 63{
 64    if (ms_clockDg)
 65        return ms_clockDg();
 66
 67#ifdef WINDOWS
 68    LARGE_INTEGER count;
 69    if (!QueryPerformanceCounter(&count))
 70        MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("QueryPerformanceCounter");
 71    unsigned long long countUll = (unsigned long long)count.QuadPart;
 72    if (g_frequency == 0)
 73        g_frequency = queryFrequency();
 74    return muldiv64(countUll, 1000000, g_frequency);
 75#elif defined(OSX)
 76    unsigned long long absoluteTime = mach_absolute_time();
 77    if (g_timebase.denom == 0)
 78        g_timebase = queryTimebase();
 79    return muldiv64(absoluteTime, g_timebase.numer, (uint64_t)g_timebase.denom * 1000);
 80#else
 81    struct timespec ts;
 82
 83    if (clock_gettime(CLOCK_MONOTONIC, &ts))
 84        MORDOR_THROW_EXCEPTION_FROM_LAST_ERROR_API("clock_gettime");
 85    return ts.tv_sec * 1000000ull + ts.tv_nsec / 1000;
 86#endif
 87}
 88
 89Timer::Timer(unsigned long long us, boost::function<void ()> dg, bool recurring,
 90             TimerManager *manager)
 91    : m_recurring(recurring),
 92      m_us(us),
 93      m_dg(dg),
 94      m_manager(manager)
 95{
 96    MORDOR_ASSERT(m_dg);
 97    m_next = TimerManager::now() + m_us;
 98}
 99
100Timer::Timer(unsigned long long next)
101    : m_next(next)
102{}
103
104bool
105Timer::cancel()
106{
107    MORDOR_LOG_DEBUG(g_log) << this << " cancel";
108    boost::mutex::scoped_lock lock(m_manager->m_mutex);
109    if (m_dg) {
110        m_dg = NULL;
111        std::set<Timer::ptr, Timer::Comparator>::iterator it =
112            m_manager->m_timers.find(shared_from_this());
113        MORDOR_ASSERT(it != m_manager->m_timers.end());
114        m_manager->m_timers.erase(it);
115        return true;
116    }
117    return false;
118}
119
120bool
121Timer::refresh()
122{
123    boost::mutex::scoped_lock lock(m_manager->m_mutex);
124    if (!m_dg)
125        return false;
126    std::set<Timer::ptr, Timer::Comparator>::iterator it =
127        m_manager->m_timers.find(shared_from_this());
128    MORDOR_ASSERT(it != m_manager->m_timers.end());
129    m_manager->m_timers.erase(it);
130    m_next = TimerManager::now() + m_us;
131    m_manager->m_timers.insert(shared_from_this());
132    lock.unlock();
133    MORDOR_LOG_DEBUG(g_log) << this << " refresh";
134    return true;
135}
136
137bool
138Timer::reset(unsigned long long us, bool fromNow)
139{
140    boost::mutex::scoped_lock lock(m_manager->m_mutex);
141    if (!m_dg)
142        return false;
143    // No change
144    if (us == m_us && !fromNow)
145        return true;
146    std::set<Timer::ptr, Timer::Comparator>::iterator it =
147        m_manager->m_timers.find(shared_from_this());
148    MORDOR_ASSERT(it != m_manager->m_timers.end());
149    m_manager->m_timers.erase(it);
150    unsigned long long start;
151    if (fromNow)
152        start = TimerManager::now();
153    else
154        start = m_next - m_us;
155    m_us = us;
156    m_next = start + m_us;
157    it = m_manager->m_timers.insert(shared_from_this()).first;
158    bool atFront = (it == m_manager->m_timers.begin()) && !m_manager->m_tickled;
159    if (atFront)
160        m_manager->m_tickled = true;
161    lock.unlock();
162    MORDOR_LOG_DEBUG(g_log) << this << " reset to " << m_us;
163    if (atFront)
164        m_manager->onTimerInsertedAtFront();
165    return true;
166}
167
168TimerManager::TimerManager()
169: m_tickled(false),
170  m_previousTime(0ull)
171{}
172
173TimerManager::~TimerManager()
174{
175#ifndef NDEBUG
176    boost::mutex::scoped_lock lock(m_mutex);
177    MORDOR_NOTHROW_ASSERT(m_timers.empty());
178#endif
179}
180
181Timer::ptr
182TimerManager::registerTimer(unsigned long long us, boost::function<void ()> dg,
183        bool recurring)
184{
185    MORDOR_ASSERT(dg);
186    Timer::ptr result(new Timer(us, dg, recurring, this));
187    boost::mutex::scoped_lock lock(m_mutex);
188    std::set<Timer::ptr, Timer::Comparator>::iterator it =
189        m_timers.insert(result).first;
190    bool atFront = (it == m_timers.begin()) && !m_tickled;
191    if (atFront)
192        m_tickled = true;
193    lock.unlock();
194    MORDOR_LOG_DEBUG(g_log) << result.get() << " registerTimer(" << us
195        << ", " << recurring << "): " << atFront;
196    if (atFront)
197        onTimerInsertedAtFront();
198    return result;
199}
200
201Timer::ptr
202TimerManager::registerConditionTimer(unsigned long long us,
203    boost::function<void ()> dg,
204    boost::weak_ptr<void> weakCond,
205    bool recurring)
206{
207    return registerTimer(us,
208        boost::bind(stubOnTimer, weakCond, dg),
209        recurring);
210}
211
212static void
213stubOnTimer(
214    boost::weak_ptr<void> weakCond, boost::function<void ()> dg)
215{
216    boost::shared_ptr<void> temp = weakCond.lock();
217    if (temp) {
218        dg();
219    } else {
220        MORDOR_LOG_DEBUG(g_log) << " Conditionally skip in stubOnTimer!";
221    }
222}
223
224unsigned long long
225TimerManager::nextTimer()
226{
227    boost::mutex::scoped_lock lock(m_mutex);
228    m_tickled = false;
229    if (m_timers.empty()) {
230        MORDOR_LOG_DEBUG(g_log) << this << " nextTimer(): ~0ull";
231        return ~0ull;
232    }
233    const Timer::ptr &next = *m_timers.begin();
234    unsigned long long nowUs = now();
235    unsigned long long result;
236    if (nowUs >= next->m_next)
237        result = 0;
238    else
239        result = next->m_next - nowUs;
240    MORDOR_LOG_DEBUG(g_log) << this << " nextTimer(): " << result;
241    return result;
242}
243
244bool
245TimerManager::detectClockRollover(unsigned long long nowUs)
246{
247    // If the time jumps backward, expire timers (rather than have them
248    // expire in the distant future or not at all).
249    // We check this way because now() will not roll from 0xffff... to zero
250    // since the underlying hardware counter doesn't count microseconds.
251    // Use a threshold value so we don't overreact to minor clock jitter.
252    bool rollover = false;
253    if (nowUs < m_previousTime && // check first in case the next line would underflow
254        nowUs < m_previousTime - g_clockRolloverThreshold->val())
255    {
256        MORDOR_LOG_ERROR(g_log) << this << " clock has rolled back from "
257            << m_previousTime << " to " << nowUs << "; expiring all timers";
258        rollover = true;
259    }
260    m_previousTime = nowUs;
261    return rollover;
262}
263
264std::vector<boost::function<void ()> >
265TimerManager::processTimers()
266{
267    std::vector<Timer::ptr> expired;
268    std::vector<boost::function<void ()> > result;
269    unsigned long long nowUs = now();
270    {
271        boost::mutex::scoped_lock lock(m_mutex);
272        if (m_timers.empty())
273            return result;
274        bool rollover = detectClockRollover(nowUs);
275        if (!rollover && (*m_timers.begin())->m_next > nowUs)
276            return result;
277        Timer nowTimer(nowUs);
278        Timer::ptr nowTimerPtr(&nowTimer, &nop<Timer *>);
279        // Find all timers that are expired
280        std::set<Timer::ptr, Timer::Comparator>::iterator it =
281            rollover ? m_timers.end() : m_timers.lower_bound(nowTimerPtr);
282        while (it != m_timers.end() && (*it)->m_next == nowUs ) ++it;
283        // Copy to expired, remove from m_timers;
284        expired.insert(expired.begin(), m_timers.begin(), it);
285        m_timers.erase(m_timers.begin(), it);
286        result.reserve(expired.size());
287        // Look at expired timers and re-register recurring timers
288        // (while under the same lock)
289        for (std::vector<Timer::ptr>::iterator it2(expired.begin());
290            it2 != expired.end();
291            ++it2) {
292            Timer::ptr &timer = *it2;
293            MORDOR_ASSERT(timer->m_dg);
294            result.push_back(timer->m_dg);
295            if (timer->m_recurring) {
296                MORDOR_LOG_TRACE(g_log) << timer << " expired and refreshed";
297                timer->m_next = nowUs + timer->m_us;
298                m_timers.insert(timer);
299            } else {
300                MORDOR_LOG_TRACE(g_log) << timer << " expired";
301                timer->m_dg = NULL;
302            }
303        }
304    }
305    return result;
306}
307
308void
309TimerManager::executeTimers()
310{
311    std::vector<boost::function<void ()> > expired = processTimers();
312    // Run the callbacks for each expired timer (not under a lock)
313    for (std::vector<boost::function<void ()> >::iterator it(expired.begin());
314        it != expired.end();
315        ++it) {
316        (*it)();
317    }
318}
319
320void
321TimerManager::setClock(boost::function<unsigned long long()> dg)
322{
323    ms_clockDg = dg;
324}
325
326bool
327Timer::Comparator::operator()(const Timer::ptr &lhs,
328                              const Timer::ptr &rhs) const
329{
330    // Order NULL before everything else
331    if (!lhs && !rhs)
332        return false;
333    if (!lhs)
334        return true;
335    if (!rhs)
336        return false;
337    // Order primarily on m_next
338    if (lhs->m_next < rhs->m_next)
339        return true;
340    if (rhs->m_next < lhs->m_next)
341        return false;
342    // Order by raw pointer for equivalent timeout values
343    return lhs.get() < rhs.get();
344}
345
346}