source: box/trunk/lib/common/Timer.cpp @ 3084

Revision 3084, 15.3 KB checked in by chris, 3 months ago (diff)

Add experimental "TCP Nice" mode, disabled by default.

  • Property svn:eol-style set to native
Line 
1// --------------------------------------------------------------------------
2//
3// File
4//              Name:    Timer.cpp
5//              Purpose: Generic timers which execute arbitrary code when
6//                       they expire.
7//              Created: 5/11/2006
8//
9// --------------------------------------------------------------------------
10
11#include "Box.h"
12
13#ifdef WIN32
14#       ifndef _WIN32_WINNT
15#               define _WIN32_WINNT 0x0500
16#       elif _WIN32_WINNT < 0x0500
17#               error Timers require at least Windows 2000 headers
18#       endif
19#endif
20
21#include <signal.h>
22#include <cstring>
23
24#include "Timer.h"
25#include "Logging.h"
26
27#include "MemLeakFindOn.h"
28
29std::vector<Timer*>* Timers::spTimers = NULL;
30bool Timers::sRescheduleNeeded = false;
31
32#define TIMER_ID "timer " << mName << " (" << this << ") "
33#define TIMER_ID_OF(t) "timer " << (t).GetName() << " (" << &(t) << ") "
34
35typedef void (*sighandler_t)(int);
36
37// --------------------------------------------------------------------------
38//
39// Function
40//              Name:    static void Timers::Init()
41//              Purpose: Initialise timers, prepare signal handler
42//              Created: 5/11/2006
43//
44// --------------------------------------------------------------------------
45void Timers::Init()
46{
47        ASSERT(!spTimers);
48       
49        #if defined WIN32 && ! defined PLATFORM_CYGWIN
50                // no init needed
51        #else
52                struct sigaction newact, oldact;
53                newact.sa_handler = Timers::SignalHandler;
54                newact.sa_flags = SA_RESTART;
55                sigemptyset(&newact.sa_mask);
56                if (::sigaction(SIGALRM, &newact, &oldact) != 0)
57                {
58                        BOX_ERROR("Failed to install signal handler");
59                        THROW_EXCEPTION(CommonException, Internal);
60                }
61                ASSERT(oldact.sa_handler == 0);
62        #endif // WIN32 && !PLATFORM_CYGWIN
63       
64        spTimers = new std::vector<Timer*>;
65}
66
67// --------------------------------------------------------------------------
68//
69// Function
70//              Name:    static void Timers::Cleanup()
71//              Purpose: Clean up timers, stop signal handler
72//              Created: 6/11/2006
73//
74// --------------------------------------------------------------------------
75void Timers::Cleanup()
76{
77        ASSERT(spTimers);
78        if (!spTimers)
79        {
80                BOX_ERROR("Tried to clean up timers when not initialised!");
81                return;
82        }
83       
84        #if defined WIN32 && ! defined PLATFORM_CYGWIN
85                // no cleanup needed
86        #else
87                struct itimerval timeout;
88                memset(&timeout, 0, sizeof(timeout));
89
90                int result = ::setitimer(ITIMER_REAL, &timeout, NULL);
91                ASSERT(result == 0);
92
93                struct sigaction newact, oldact;
94                newact.sa_handler = SIG_DFL;
95                newact.sa_flags = SA_RESTART;
96                sigemptyset(&(newact.sa_mask));
97                if (::sigaction(SIGALRM, &newact, &oldact) != 0)
98                {
99                        BOX_ERROR("Failed to remove signal handler");
100                        THROW_EXCEPTION(CommonException, Internal);
101                }
102                ASSERT(oldact.sa_handler == Timers::SignalHandler);
103        #endif // WIN32 && !PLATFORM_CYGWIN
104
105        spTimers->clear();
106        delete spTimers;
107        spTimers = NULL;
108}
109
110// --------------------------------------------------------------------------
111//
112// Function
113//              Name:    static void Timers::Add(Timer&)
114//              Purpose: Add a new timer to the set, and reschedule next wakeup
115//              Created: 5/11/2006
116//
117// --------------------------------------------------------------------------
118void Timers::Add(Timer& rTimer)
119{
120        ASSERT(spTimers);
121        ASSERT(&rTimer);
122        spTimers->push_back(&rTimer);
123        Reschedule();
124}
125
126// --------------------------------------------------------------------------
127//
128// Function
129//              Name:    static void Timers::Remove(Timer&)
130//              Purpose: Removes the timer from the set (preventing it from
131//                       being called) and reschedule next wakeup
132//              Created: 5/11/2006
133//
134// --------------------------------------------------------------------------
135void Timers::Remove(Timer& rTimer)
136{
137        ASSERT(spTimers);
138        ASSERT(&rTimer);
139
140        bool restart = true;
141        while (restart)
142        {
143                restart = false;
144
145                for (std::vector<Timer*>::iterator i = spTimers->begin();
146                        i != spTimers->end(); i++)
147                {
148                        if (&rTimer == *i)
149                        {
150                                spTimers->erase(i);
151                                restart = true;
152                                break;
153                        }
154                }
155        }
156               
157        Reschedule();
158}
159
160void Timers::RequestReschedule()
161{
162        sRescheduleNeeded = true;
163}
164
165void Timers::RescheduleIfNeeded()
166{
167        if (sRescheduleNeeded) 
168        {
169                Reschedule();
170        }
171}
172
173#define FORMAT_MICROSECONDS(t) \
174        (int)(t / 1000000) << "." << \
175        (int)(t % 1000000) << " seconds"
176
177// --------------------------------------------------------------------------
178//
179// Function
180//              Name:    static void Timers::Reschedule()
181//              Purpose: Recalculate when the next wakeup is due
182//              Created: 5/11/2006
183//
184// --------------------------------------------------------------------------
185void Timers::Reschedule()
186{
187        ASSERT(spTimers);
188        if (spTimers == NULL)
189        {
190                THROW_EXCEPTION(CommonException, Internal)
191        }
192
193        #ifndef WIN32
194                struct sigaction oldact;
195                if (::sigaction(SIGALRM, NULL, &oldact) != 0)
196                {
197                        BOX_ERROR("Failed to check signal handler");
198                        THROW_EXCEPTION(CommonException, Internal)
199                }
200
201                ASSERT(oldact.sa_handler == Timers::SignalHandler);
202
203                if (oldact.sa_handler != Timers::SignalHandler)
204                {
205                        BOX_ERROR("Signal handler was " <<
206                                (void *)oldact.sa_handler << 
207                                ", expected " <<
208                                (void *)Timers::SignalHandler);
209                        THROW_EXCEPTION(CommonException, Internal)
210                }
211        #endif
212
213        // Clear the reschedule-needed flag to false before we start.
214        // If a timer event occurs while we are scheduling, then we
215        // may or may not need to reschedule again, but this way
216        // we will do it anyway.
217        sRescheduleNeeded = false;
218
219#ifdef WIN32
220        // win32 timers need no management
221#else
222        box_time_t timeNow = GetCurrentBoxTime();
223
224        // scan for, trigger and remove expired timers. Removal requires
225        // us to restart the scan each time, due to std::vector semantics.
226        bool restart = true;
227        while (restart)
228        {
229                restart = false;
230
231                for (std::vector<Timer*>::iterator i = spTimers->begin();
232                        i != spTimers->end(); i++)
233                {
234                        Timer& rTimer = **i;
235                        int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow;
236               
237                        if (timeToExpiry <= 0)
238                        {
239                                /*
240                                BOX_TRACE("timer " << *i << " has expired, "
241                                        "triggering it");
242                                */
243                                BOX_TRACE(TIMER_ID_OF(**i) "has expired, "
244                                        "triggering " <<
245                                        FORMAT_MICROSECONDS(-timeToExpiry) <<
246                                        " late");
247                                rTimer.OnExpire();
248                                spTimers->erase(i);
249                                restart = true;
250                                break;
251                        }
252                        else
253                        {
254                                /*
255                                BOX_TRACE("timer " << *i << " has not "
256                                        "expired, triggering in " <<
257                                        FORMAT_MICROSECONDS(timeToExpiry) <<
258                                        " seconds");
259                                */
260                        }
261                }
262        }
263
264        // Now the only remaining timers should all be in the future.
265        // Scan to find the next one to fire (earliest deadline).
266                       
267        int64_t timeToNextEvent = 0;
268        std::string nameOfNextEvent;
269
270        for (std::vector<Timer*>::iterator i = spTimers->begin();
271                i != spTimers->end(); i++)
272        {
273                Timer& rTimer = **i;
274                int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow;
275
276                ASSERT(timeToExpiry > 0)
277                if (timeToExpiry <= 0)
278                {
279                        timeToExpiry = 1;
280                }
281               
282                if (timeToNextEvent == 0 || timeToNextEvent > timeToExpiry)
283                {
284                        timeToNextEvent = timeToExpiry;
285                        nameOfNextEvent = rTimer.GetName();
286                }
287        }
288       
289        ASSERT(timeToNextEvent >= 0);
290
291        if (timeToNextEvent == 0)
292        {
293                BOX_TRACE("timer: no more events, going to sleep.");
294        }
295        else
296        {
297                BOX_TRACE("timer: next event: " << nameOfNextEvent <<
298                        " expires in " << FORMAT_MICROSECONDS(timeToNextEvent));
299        }
300
301        struct itimerval timeout;
302        memset(&timeout, 0, sizeof(timeout));
303       
304        timeout.it_value.tv_sec  = BoxTimeToSeconds(timeToNextEvent);
305        timeout.it_value.tv_usec = (int)
306                (BoxTimeToMicroSeconds(timeToNextEvent)
307                % MICRO_SEC_IN_SEC);
308
309        if(::setitimer(ITIMER_REAL, &timeout, NULL) != 0)
310        {
311                BOX_ERROR("Failed to initialise system timer\n");
312                THROW_EXCEPTION(CommonException, Internal)
313        }
314#endif
315}
316
317// --------------------------------------------------------------------------
318//
319// Function
320//              Name:    static void Timers::SignalHandler(unused)
321//              Purpose: Called as signal handler. Nothing is safe in a signal
322//                       handler, not even traversing the list of timers, so
323//                       just request a reschedule in future, which will do
324//                       that for us, and trigger any expired timers at that
325//                       time.
326//              Created: 5/11/2006
327//
328// --------------------------------------------------------------------------
329void Timers::SignalHandler(int unused)
330{
331        // ASSERT(spTimers);
332        Timers::RequestReschedule();
333}
334
335// --------------------------------------------------------------------------
336//
337// Function
338//              Name:    Timer::Timer(size_t timeoutMillis,
339//                       const std::string& rName)
340//              Purpose: Standard timer constructor, takes a timeout in
341//                       seconds from now, and an optional name for
342//                       logging purposes.
343//              Created: 27/07/2008
344//
345// --------------------------------------------------------------------------
346
347Timer::Timer(size_t timeoutMillis, const std::string& rName)
348: mExpires(GetCurrentBoxTime() + MilliSecondsToBoxTime(timeoutMillis)),
349  mExpired(false),
350  mName(rName)
351#ifdef WIN32
352, mTimerHandle(INVALID_HANDLE_VALUE)
353#endif
354{
355        #ifndef BOX_RELEASE_BUILD
356        if (timeoutMillis == 0)
357        {
358                BOX_TRACE(TIMER_ID "initialised for " << timeoutMillis << 
359                        " ms, will not fire");
360        }
361        else
362        {
363                BOX_TRACE(TIMER_ID "initialised for " << timeoutMillis <<
364                        " ms, to fire at " << FormatTime(mExpires, false, true));
365        }
366        #endif
367
368        if (timeoutMillis == 0)
369        {
370                mExpires = 0;
371        }
372        else
373        {
374                Timers::Add(*this);
375                Start(timeoutMillis * 1000);
376        }
377}
378
379// --------------------------------------------------------------------------
380//
381// Function
382//              Name:    Timer::Start()
383//              Purpose: This internal function initialises an OS TimerQueue
384//                       timer on Windows, while on Unixes there is only a
385//                       single global timer, managed by the Timers class,
386//                       so this method does nothing.
387//              Created: 27/07/2008
388//
389// --------------------------------------------------------------------------
390
391void Timer::Start()
392{
393#ifdef WIN32
394        box_time_t timeNow = GetCurrentBoxTime();
395        int64_t timeToExpiry = mExpires - timeNow;
396
397        if (timeToExpiry <= 0)
398        {
399                BOX_WARNING(TIMER_ID << "fudging expiry from -" <<
400                        FORMAT_MICROSECONDS(-timeToExpiry))
401                timeToExpiry = 1;
402        }
403
404        Start(timeToExpiry);
405#endif
406}
407
408// --------------------------------------------------------------------------
409//
410// Function
411//              Name:    Timer::Start(int64_t timeoutMillis)
412//              Purpose: This internal function initialises an OS TimerQueue
413//                       timer on Windows, with a specified delay already
414//                       calculated to save us doing it again. Like
415//                       Timer::Start(), on Unixes it does nothing.
416//              Created: 27/07/2008
417//
418// --------------------------------------------------------------------------
419
420void Timer::Start(int64_t timeoutMillis)
421{
422#ifdef WIN32
423        // only call me once!
424        ASSERT(mTimerHandle == INVALID_HANDLE_VALUE);
425
426        // Windows XP always seems to fire timers up to 20 ms late,
427        // at least on my test laptop. Not critical in practice, but our
428        // tests are precise enough that they will fail if we don't
429        // correct for it.
430        timeoutMillis -= 20;
431       
432        // Set a system timer to call our timer routine
433        if (CreateTimerQueueTimer(&mTimerHandle, NULL, TimerRoutine,
434                (PVOID)this, timeoutMillis, 0, WT_EXECUTEINTIMERTHREAD)
435                == FALSE)
436        {
437                BOX_ERROR(TIMER_ID "failed to create timer: " <<
438                        GetErrorMessage(GetLastError()));
439                mTimerHandle = INVALID_HANDLE_VALUE;
440        }
441#endif
442}
443
444// --------------------------------------------------------------------------
445//
446// Function
447//              Name:    Timer::Stop()
448//              Purpose: This internal function deletes the associated OS
449//                       TimerQueue timer on Windows, and on Unixes does
450//                       nothing.
451//              Created: 27/07/2008
452//
453// --------------------------------------------------------------------------
454
455void Timer::Stop()
456{
457#ifdef WIN32
458        if (mTimerHandle != INVALID_HANDLE_VALUE)
459        {
460                if (DeleteTimerQueueTimer(NULL, mTimerHandle,
461                        INVALID_HANDLE_VALUE) == FALSE)
462                {
463                        BOX_ERROR(TIMER_ID "failed to delete timer: " <<
464                                GetErrorMessage(GetLastError()));
465                }
466                mTimerHandle = INVALID_HANDLE_VALUE;
467        }
468#endif
469}
470
471// --------------------------------------------------------------------------
472//
473// Function
474//              Name:    Timer::~Timer()
475//              Purpose: Destructor for Timer objects.
476//              Created: 27/07/2008
477//
478// --------------------------------------------------------------------------
479
480Timer::~Timer()
481{
482        #ifndef BOX_RELEASE_BUILD
483        BOX_TRACE(TIMER_ID "destroyed");
484        #endif
485
486        Timers::Remove(*this);
487        Stop();
488}
489
490// --------------------------------------------------------------------------
491//
492// Function
493//              Name:    Timer::Timer(Timer& rToCopy)
494//              Purpose: Copy constructor for Timer objects. Creates a new
495//                       timer that will trigger at the same time as the
496//                       original. The original will usually be discarded.
497//              Created: 27/07/2008
498//
499// --------------------------------------------------------------------------
500
501Timer::Timer(const Timer& rToCopy)
502: mExpires(rToCopy.mExpires),
503  mExpired(rToCopy.mExpired),
504  mName(rToCopy.mName)
505#ifdef WIN32
506, mTimerHandle(INVALID_HANDLE_VALUE)
507#endif
508{
509        #ifndef BOX_RELEASE_BUILD
510        if (mExpired)
511        {
512                BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", "
513                        "already expired, will not fire");
514        }
515        else if (mExpires == 0)
516        {
517                BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", "
518                        "no expiry, will not fire");
519        }
520        else
521        {
522                BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", "
523                        "to fire at " <<
524                        (int)(mExpires / MICRO_SEC_IN_SEC_LL) << "." <<
525                        (int)(mExpires % MICRO_SEC_IN_SEC_LL));
526        }
527        #endif
528
529        if (!mExpired && mExpires != 0)
530        {
531                Timers::Add(*this);
532                Start();
533        }
534}
535
536// --------------------------------------------------------------------------
537//
538// Function
539//              Name:    Timer::operator=(const Timer& rToCopy)
540//              Purpose: Assignment operator for Timer objects. Works
541//                       exactly the same as the copy constructor, except
542//                       that if the receiving timer is already running,
543//                       it is stopped first.
544//              Created: 27/07/2008
545//
546// --------------------------------------------------------------------------
547
548Timer& Timer::operator=(const Timer& rToCopy)
549{
550        #ifndef BOX_RELEASE_BUILD
551        if (rToCopy.mExpired)
552        {
553                BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", "
554                        "already expired, will not fire");
555        }
556        else if (rToCopy.mExpires == 0)
557        {
558                BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", "
559                        "no expiry, will not fire");
560        }
561        else
562        {
563                BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", "
564                        "to fire at " <<
565                        (int)(rToCopy.mExpires / MICRO_SEC_IN_SEC_LL) << "." <<
566                        (int)(rToCopy.mExpires % MICRO_SEC_IN_SEC_LL));
567        }
568        #endif
569
570        Timers::Remove(*this);
571        Stop();
572
573        mExpires = rToCopy.mExpires;
574        mExpired = rToCopy.mExpired;
575        mName    = rToCopy.mName;
576
577        if (!mExpired && mExpires != 0)
578        {
579                Timers::Add(*this);
580                Start();
581        }
582
583        return *this;
584}
585
586// --------------------------------------------------------------------------
587//
588// Function
589//              Name:    Timer::OnExpire()
590//              Purpose: Method called by Timers::Reschedule (on Unixes)
591//                       on next poll after timer expires, or from
592//                       Timer::TimerRoutine (on Windows) from a separate
593//                       thread managed by the OS. Marks the timer as
594//                       expired for future reference.
595//              Created: 27/07/2008
596//
597// --------------------------------------------------------------------------
598
599void Timer::OnExpire()
600{
601        #ifndef BOX_RELEASE_BUILD
602        BOX_TRACE(TIMER_ID "fired");
603        #endif
604
605        mExpired = true;
606}
607
608// --------------------------------------------------------------------------
609//
610// Function
611//              Name:    Timer::TimerRoutine(PVOID lpParam,
612//                       BOOLEAN TimerOrWaitFired)
613//              Purpose: Static method called by the Windows OS when a
614//                       TimerQueue timer expires.
615//              Created: 27/07/2008
616//
617// --------------------------------------------------------------------------
618
619#ifdef WIN32
620VOID CALLBACK Timer::TimerRoutine(PVOID lpParam,
621        BOOLEAN TimerOrWaitFired)
622{
623        Timer* pTimer = (Timer*)lpParam;
624        pTimer->OnExpire();
625        // is it safe to write to write debug output from a timer?
626        // e.g. to write to the Event Log?
627}
628#endif
Note: See TracBrowser for help on using the repository browser.