diff options
-rw-r--r-- | base/SConscript | 1 | ||||
-rw-r--r-- | base/build/base_unittests.vcproj | 4 | ||||
-rw-r--r-- | base/time.h | 1 | ||||
-rw-r--r-- | base/time_unittest.cc | 109 | ||||
-rw-r--r-- | base/time_unittest_win.cc | 102 | ||||
-rw-r--r-- | base/time_win.cc | 125 |
6 files changed, 180 insertions, 162 deletions
diff --git a/base/SConscript b/base/SConscript index e6ce007..a2fc266 100644 --- a/base/SConscript +++ b/base/SConscript @@ -290,6 +290,7 @@ if env['PLATFORM'] == 'win32': 'object_watcher_unittest.cc', 'pe_image_unittest.cc', 'sys_string_conversions_win_unittest.cc', + 'time_unittest_win.cc', 'win_util_unittest.cc', 'wmi_util_unittest.cc', 'gfx/platform_canvas_unittest_win.cc', diff --git a/base/build/base_unittests.vcproj b/base/build/base_unittests.vcproj index 4ffb11b..0c22754 100644 --- a/base/build/base_unittests.vcproj +++ b/base/build/base_unittests.vcproj @@ -320,6 +320,10 @@ > </File> <File + RelativePath="..\time_unittest_win.cc" + > + </File> + <File RelativePath="..\timer_unittest.cc" > </File> diff --git a/base/time.h b/base/time.h index c25419f..17296e3 100644 --- a/base/time.h +++ b/base/time.h @@ -436,6 +436,7 @@ class TimeTicks { int64 ticks_; #if defined(OS_WIN) + public: // The function to use for counting ticks. typedef int (__stdcall *TickFunction)(void); static TickFunction tick_function_; diff --git a/base/time_unittest.cc b/base/time_unittest.cc index 9da5779..3cd9595 100644 --- a/base/time_unittest.cc +++ b/base/time_unittest.cc @@ -2,16 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "build/build_config.h" - #include <time.h> -#if defined(OS_WIN) -#include <process.h> -#endif - -#include "base/time.h" #include "base/platform_thread.h" +#include "base/time.h" +#include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" // Test conversions to/from time_t and exploding/unexploding. @@ -116,103 +111,3 @@ TEST(TimeDelta, FromAndIn) { EXPECT_EQ(13.0, TimeDelta::FromMilliseconds(13).InMillisecondsF()); EXPECT_EQ(13, TimeDelta::FromMicroseconds(13).InMicroseconds()); } - -#if defined(OS_WIN) - -// TODO(pinkerton): Need to find a way to mock this for non-windows. - -namespace { - -class MockTimeTicks : public TimeTicks { - public: - static int Ticker() { - return static_cast<int>(InterlockedIncrement(&ticker_)); - } - - static void InstallTicker() { - old_tick_function_ = tick_function_; - tick_function_ = reinterpret_cast<TickFunction>(&Ticker); - ticker_ = -5; - } - - static void UninstallTicker() { - tick_function_ = old_tick_function_; - } - - private: - static volatile LONG ticker_; - static TickFunction old_tick_function_; -}; - -volatile LONG MockTimeTicks::ticker_; -MockTimeTicks::TickFunction MockTimeTicks::old_tick_function_; - -HANDLE g_rollover_test_start; - -unsigned __stdcall RolloverTestThreadMain(void* param) { - int64 counter = reinterpret_cast<int64>(param); - DWORD rv = WaitForSingleObject(g_rollover_test_start, INFINITE); - EXPECT_EQ(rv, WAIT_OBJECT_0); - - TimeTicks last = TimeTicks::Now(); - for (int index = 0; index < counter; index++) { - TimeTicks now = TimeTicks::Now(); - int64 milliseconds = (now - last).InMilliseconds(); - EXPECT_GT(milliseconds, 0); - EXPECT_LT(milliseconds, 250); - last = now; - } - return 0; -} - -} // namespace - -TEST(TimeTicks, Rollover) { - // The internal counter rolls over at ~49days. We'll use a mock - // timer to test this case. - // Basic test algorithm: - // 1) Set clock to rollover - N - // 2) Create N threads - // 3) Start the threads - // 4) Each thread loops through TimeTicks() N times - // 5) Each thread verifies integrity of result. - - const int kThreads = 8; - // Use int64 so we can cast into a void* without a compiler warning. - const int64 kChecks = 10; - - // It takes a lot of iterations to reproduce the bug! - // (See bug 1081395) - for (int loop = 0; loop < 4096; loop++) { - // Setup - MockTimeTicks::InstallTicker(); - g_rollover_test_start = CreateEvent(0, TRUE, FALSE, 0); - HANDLE threads[kThreads]; - - for (int index = 0; index < kThreads; index++) { - void* argument = reinterpret_cast<void*>(kChecks); - unsigned thread_id; - threads[index] = reinterpret_cast<HANDLE>( - _beginthreadex(NULL, 0, RolloverTestThreadMain, argument, 0, - &thread_id)); - EXPECT_NE((HANDLE)NULL, threads[index]); - } - - // Start! - SetEvent(g_rollover_test_start); - - // Wait for threads to finish - for (int index = 0; index < kThreads; index++) { - DWORD rv = WaitForSingleObject(threads[index], INFINITE); - EXPECT_EQ(rv, WAIT_OBJECT_0); - } - - CloseHandle(g_rollover_test_start); - - // Teardown - MockTimeTicks::UninstallTicker(); - } -} - -#endif - diff --git a/base/time_unittest_win.cc b/base/time_unittest_win.cc new file mode 100644 index 0000000..5b29db2 --- /dev/null +++ b/base/time_unittest_win.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> +#include <process.h> + +#include "base/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class MockTimeTicks : public TimeTicks { + public: + static int Ticker() { + return static_cast<int>(InterlockedIncrement(&ticker_)); + } + + static void InstallTicker() { + old_tick_function_ = tick_function_; + tick_function_ = reinterpret_cast<TickFunction>(&Ticker); + ticker_ = -5; + } + + static void UninstallTicker() { + tick_function_ = old_tick_function_; + } + + private: + static volatile LONG ticker_; + static TickFunction old_tick_function_; +}; + +volatile LONG MockTimeTicks::ticker_; +MockTimeTicks::TickFunction MockTimeTicks::old_tick_function_; + +HANDLE g_rollover_test_start; + +unsigned __stdcall RolloverTestThreadMain(void* param) { + int64 counter = reinterpret_cast<int64>(param); + DWORD rv = WaitForSingleObject(g_rollover_test_start, INFINITE); + EXPECT_EQ(rv, WAIT_OBJECT_0); + + TimeTicks last = TimeTicks::Now(); + for (int index = 0; index < counter; index++) { + TimeTicks now = TimeTicks::Now(); + int64 milliseconds = (now - last).InMilliseconds(); + EXPECT_GT(milliseconds, 0); + EXPECT_LT(milliseconds, 250); + last = now; + } + return 0; +} + +} // namespace + +TEST(TimeTicks, WinRollover) { + // The internal counter rolls over at ~49days. We'll use a mock + // timer to test this case. + // Basic test algorithm: + // 1) Set clock to rollover - N + // 2) Create N threads + // 3) Start the threads + // 4) Each thread loops through TimeTicks() N times + // 5) Each thread verifies integrity of result. + + const int kThreads = 8; + // Use int64 so we can cast into a void* without a compiler warning. + const int64 kChecks = 10; + + // It takes a lot of iterations to reproduce the bug! + // (See bug 1081395) + for (int loop = 0; loop < 4096; loop++) { + // Setup + MockTimeTicks::InstallTicker(); + g_rollover_test_start = CreateEvent(0, TRUE, FALSE, 0); + HANDLE threads[kThreads]; + + for (int index = 0; index < kThreads; index++) { + void* argument = reinterpret_cast<void*>(kChecks); + unsigned thread_id; + threads[index] = reinterpret_cast<HANDLE>( + _beginthreadex(NULL, 0, RolloverTestThreadMain, argument, 0, + &thread_id)); + EXPECT_NE((HANDLE)NULL, threads[index]); + } + + // Start! + SetEvent(g_rollover_test_start); + + // Wait for threads to finish + for (int index = 0; index < kThreads; index++) { + DWORD rv = WaitForSingleObject(threads[index], INFINITE); + EXPECT_EQ(rv, WAIT_OBJECT_0); + } + + CloseHandle(g_rollover_test_start); + + // Teardown + MockTimeTicks::UninstallTicker(); + } +} diff --git a/base/time_win.cc b/base/time_win.cc index 9d38993..a919395 100644 --- a/base/time_win.cc +++ b/base/time_win.cc @@ -7,9 +7,11 @@ #pragma comment(lib, "winmm.lib") #include <windows.h> #include <mmsystem.h> + #include "base/basictypes.h" #include "base/lock.h" #include "base/logging.h" +#include "base/singleton.h" namespace { @@ -122,53 +124,39 @@ void Time::Explode(bool is_local, Exploded* exploded) const { } // TimeTicks ------------------------------------------------------------------ - -TimeTicks::TickFunction TimeTicks::tick_function_= +TimeTicks::TickFunction TimeTicks::tick_function_ = reinterpret_cast<TickFunction>(&timeGetTime); -// static -TimeTicks TimeTicks::Now() { - // Uses the multimedia timers on Windows to get a higher resolution clock. - // timeGetTime() provides a resolution which is variable depending on - // hardware and system configuration. It can also be changed by other - // apps. This class does not attempt to change the resolution of the - // timer, because we don't want to affect other applications. - - // timeGetTime() should at least be accurate to ~5ms on all systems. - // timeGetTime() returns a 32-bit millisecond counter which has rollovers - // every ~49 days. - static DWORD last_tick_count = 0; - static int64 tick_rollover_accum = 0; - static Lock* tick_lock = NULL; // To protect during rollover periods. - - // Lazily create the lock we use. - if (!tick_lock) { - Lock* new_lock = new Lock; - if (InterlockedCompareExchangePointer( - reinterpret_cast<PVOID*>(&tick_lock), new_lock, NULL)) { - delete new_lock; - } - } +namespace { - // Atomically protect the low and high 32bit values for time. - // In the future we may be able to optimize with - // InterlockedCompareExchange64, but that doesn't work on XP. - DWORD tick_count; - int64 rollover_count; - { - AutoLock lock(*tick_lock); - tick_count = tick_function_(); - if (tick_count < last_tick_count) - tick_rollover_accum += GG_INT64_C(0x100000000); - - last_tick_count = tick_count; - rollover_count = tick_rollover_accum; +// We use timeGetTime() to implement TimeTicks::Now(). This can be problematic +// because it returns the number of millisecond since Windows has started, which +// will roll over the 32-bit value every ~49 days. We try to track rollover +// ourselves, which works if TimeTicks::Now() is called at least every 49 days. +class NowSingleton { + public: + NowSingleton() + : rollover_(TimeDelta::FromMilliseconds(0)), + last_seen_(TimeTicks::tick_function_()) { } + + TimeDelta Now() { + AutoLock locked(lock_); + // We should hold the lock while calling tick_function_ to make sure that + // we keep our last_seen_ stay correctly in sync. + DWORD now = TimeTicks::tick_function_(); + if (now < last_seen_) + rollover_ += TimeDelta::FromMilliseconds(0x100000000I64); // ~49.7 days. + last_seen_ = now; + return TimeDelta::FromMilliseconds(now) + rollover_; } - // GetTickCount returns milliseconds, we want microseconds. - return TimeTicks((tick_count + rollover_count) * - Time::kMicrosecondsPerMillisecond); -} + private: + Lock lock_; // To protected last_seen_ and rollover_. + TimeDelta rollover_; // Accumulation of time lost do to rollover. + DWORD last_seen_; // The last timeGetTime value we saw, to detect rollover. + + DISALLOW_COPY_AND_ASSIGN(NowSingleton); +}; // Overview of time counters: // (1) CPU cycle counter. (Retrieved via RDTSC) @@ -198,23 +186,50 @@ TimeTicks TimeTicks::Now() { // (3) System time. The system time provides a low-resolution (typically 10ms // to 55 milliseconds) time stamp but is comparatively less expensive to // retrieve and more reliable. +class UnreliableHighResNowSingleton { + public: + UnreliableHighResNowSingleton() : ticks_per_microsecond_(0) { + LARGE_INTEGER ticks_per_sec = {0}; + if (!QueryPerformanceFrequency(&ticks_per_sec)) + return; // Broken, we don't guarantee this function works. + ticks_per_microsecond_ = + ticks_per_sec.QuadPart / Time::kMicrosecondsPerSecond; + } -// static -TimeTicks TimeTicks::UnreliableHighResNow() { + bool IsBroken() { + return ticks_per_microsecond_ == 0; + } + + TimeDelta Now() { + LARGE_INTEGER now; + QueryPerformanceCounter(&now); + return TimeDelta::FromMicroseconds(now.QuadPart / ticks_per_microsecond_); + } + + private: // Cached clock frequency -> microseconds. This assumes that the clock // frequency is faster than one microsecond (which is 1MHz, should be OK). - static int64 ticks_per_microsecond = 0; + int64 ticks_per_microsecond_; // 0 indicates QPF failed and we're broken. - if (ticks_per_microsecond == 0) { - LARGE_INTEGER ticks_per_sec = { 0, 0 }; - if (!QueryPerformanceFrequency(&ticks_per_sec)) - return TimeTicks(0); // Broken, we don't guarantee this function works. - ticks_per_microsecond = - ticks_per_sec.QuadPart / Time::kMicrosecondsPerSecond; - } + DISALLOW_COPY_AND_ASSIGN(UnreliableHighResNowSingleton); +}; + +} // namespace - LARGE_INTEGER now; - QueryPerformanceCounter(&now); - return TimeTicks(now.QuadPart / ticks_per_microsecond); +// static +TimeTicks TimeTicks::Now() { + return TimeTicks() + Singleton<NowSingleton>::get()->Now(); } +// static +TimeTicks TimeTicks::UnreliableHighResNow() { + UnreliableHighResNowSingleton* now = + Singleton<UnreliableHighResNowSingleton>::get(); + + if (now->IsBroken()) { + NOTREACHED() << "QueryPerformanceCounter is broken."; + return TimeTicks(0); + } + + return TimeTicks() + now->Now(); +} |