From 6ced8c2cc76b7951d25cf1a7c376b18750ffbab7 Mon Sep 17 00:00:00 2001 From: Alexis Campailla Date: Tue, 9 Sep 2014 18:08:46 +0200 Subject: [PATCH] windows: improve timer precision Improve timing precision by using QueryPerformanceCounter. This is part of the fix for Node.js' test-timers-first-fire.js. --- include/uv-win.h | 2 -- src/win/core.c | 24 ++++++++++++++--------- src/win/internal.h | 1 + src/win/timer.c | 49 ++++++++++------------------------------------ src/win/util.c | 37 ++++++++++++++++++---------------- 5 files changed, 46 insertions(+), 67 deletions(-) diff --git a/include/uv-win.h b/include/uv-win.h index 378267a05f..293b41d08c 100644 --- a/include/uv-win.h +++ b/include/uv-win.h @@ -316,8 +316,6 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s); HANDLE iocp; \ /* The current time according to the event loop. in msecs. */ \ uint64_t time; \ - /* GetTickCount() result when the event loop time was last updated. */ \ - DWORD last_tick_count; \ /* Tail of a single-linked circular queue of pending reqs. If the queue */ \ /* is empty, tail_ is NULL. If there is only one item, */ \ /* tail_->next_req == tail_ */ \ diff --git a/src/win/core.c b/src/win/core.c index b151d6fbb2..c9e4c88fa7 100644 --- a/src/win/core.c +++ b/src/win/core.c @@ -137,7 +137,6 @@ int uv_loop_init(uv_loop_t* loop) { * to zero before calling uv_update_time for the first time. */ loop->time = 0; - loop->last_tick_count = 0; uv_update_time(loop); QUEUE_INIT(&loop->wq); @@ -314,13 +313,17 @@ static void uv_poll(uv_loop_t* loop, DWORD timeout) { /* Package was dequeued */ req = uv_overlapped_to_req(overlapped); uv_insert_pending_req(loop, req); + + /* Some time might have passed waiting for I/O, + * so update the loop time here. + */ + uv_update_time(loop); } else if (GetLastError() != WAIT_TIMEOUT) { /* Serious error */ uv_fatal_error(GetLastError(), "GetQueuedCompletionStatus"); - } else { - /* We're sure that at least `timeout` milliseconds have expired, but - * this may not be reflected yet in the GetTickCount() return value. - * Therefore we ensure it's taken into account here. + } else if (timeout > 0) { + /* GetQueuedCompletionStatus can occasionally return a little early. + * Make sure that the desired timeout is reflected in the loop time. */ uv__time_forward(loop, timeout); } @@ -347,13 +350,17 @@ static void uv_poll_ex(uv_loop_t* loop, DWORD timeout) { req = uv_overlapped_to_req(overlappeds[i].lpOverlapped); uv_insert_pending_req(loop, req); } + + /* Some time might have passed waiting for I/O, + * so update the loop time here. + */ + uv_update_time(loop); } else if (GetLastError() != WAIT_TIMEOUT) { /* Serious error */ uv_fatal_error(GetLastError(), "GetQueuedCompletionStatusEx"); } else if (timeout > 0) { - /* We're sure that at least `timeout` milliseconds have expired, but - * this may not be reflected yet in the GetTickCount() return value. - * Therefore we ensure it's taken into account here. + /* GetQueuedCompletionStatus can occasionally return a little early. + * Make sure that the desired timeout is reflected in the loop time. */ uv__time_forward(loop, timeout); } @@ -412,7 +419,6 @@ int uv_run(uv_loop_t *loop, uv_run_mode mode) { * UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from * the check. */ - uv_update_time(loop); uv_process_timers(loop); } diff --git a/src/win/internal.h b/src/win/internal.h index c1189e6779..d87402b73a 100644 --- a/src/win/internal.h +++ b/src/win/internal.h @@ -322,6 +322,7 @@ void uv__fs_poll_endgame(uv_loop_t* loop, uv_fs_poll_t* handle); */ void uv__util_init(); +uint64_t uv__hrtime(double scale); int uv_parent_pid(); __declspec(noreturn) void uv_fatal_error(const int errorno, const char* syscall); diff --git a/src/win/timer.c b/src/win/timer.c index 2604336cda..0da541a2c8 100644 --- a/src/win/timer.c +++ b/src/win/timer.c @@ -28,39 +28,17 @@ #include "handle-inl.h" -void uv_update_time(uv_loop_t* loop) { - DWORD ticks; - ULARGE_INTEGER time; - - ticks = GetTickCount(); +/* The number of milliseconds in one second. */ +#define UV__MILLISEC 1000 - time.QuadPart = loop->time; - /* GetTickCount() can conceivably wrap around, so when the current tick - * count is lower than the last tick count, we'll assume it has wrapped. - * uv_poll must make sure that the timer can never overflow more than - * once between two subsequent uv_update_time calls. - */ - time.LowPart = ticks; - if (ticks < loop->last_tick_count) - time.HighPart++; - - /* Remember the last tick count. */ - loop->last_tick_count = ticks; - - /* The GetTickCount() resolution isn't too good. Sometimes it'll happen - * that GetQueuedCompletionStatus() or GetQueuedCompletionStatusEx() has - * waited for a couple of ms but this is not reflected in the GetTickCount - * result yet. Therefore whenever GetQueuedCompletionStatus times out - * we'll add the number of ms that it has waited to the current loop time. - * When that happened the loop time might be a little ms farther than what - * we've just computed, and we shouldn't update the loop time. - */ - if (loop->time < time.QuadPart) - loop->time = time.QuadPart; +void uv_update_time(uv_loop_t* loop) { + uint64_t new_time = uv__hrtime(UV__MILLISEC); + if (new_time > loop->time) { + loop->time = new_time; + } } - void uv__time_forward(uv_loop_t* loop, uint64_t msecs) { loop->time += msecs; } @@ -191,16 +169,9 @@ DWORD uv__next_timeout(const uv_loop_t* loop) { timer = RB_MIN(uv_timer_tree_s, &((uv_loop_t*)loop)->timers); if (timer) { delta = timer->due - loop->time; - if (delta >= UINT_MAX >> 1) { - /* A timeout value of UINT_MAX means infinite, so that's no good. But - * more importantly, there's always the risk that GetTickCount wraps. - * uv_update_time can detect this, but we must make sure that the - * tick counter never overflows twice between two subsequent - * uv_update_time calls. We do this by never sleeping more than half - * the time it takes to wrap the counter - which is huge overkill, - * but hey, it's not so bad to wake up every 25 days. - */ - return UINT_MAX >> 1; + if (delta >= UINT_MAX - 1) { + /* A timeout value of UINT_MAX means infinite, so that's no good. */ + return UINT_MAX - 1; } else if (delta < 0) { /* Negative timeout values are not allowed */ return 0; diff --git a/src/win/util.c b/src/win/util.c index a56fbea500..8d1aefc538 100644 --- a/src/win/util.c +++ b/src/win/util.c @@ -52,16 +52,15 @@ #define MAX_TITLE_LENGTH 8192 /* The number of nanoseconds in one second. */ -#undef NANOSEC -#define NANOSEC 1000000000 +#define UV__NANOSEC 1000000000 /* Cached copy of the process title, plus a mutex guarding it. */ static char *process_title; static CRITICAL_SECTION process_title_lock; -/* Frequency (ticks per nanosecond) of the high-resolution clock. */ -static double hrtime_frequency_ = 0; +/* Interval (in seconds) of the high-resolution clock. */ +static double hrtime_interval_ = 0; /* @@ -73,11 +72,14 @@ void uv__util_init() { /* Initialize process title access mutex. */ InitializeCriticalSection(&process_title_lock); - /* Retrieve high-resolution timer frequency. */ - if (QueryPerformanceFrequency(&perf_frequency)) - hrtime_frequency_ = (double) perf_frequency.QuadPart / (double) NANOSEC; - else - hrtime_frequency_= 0; + /* Retrieve high-resolution timer frequency + * and precompute its reciprocal. + */ + if (QueryPerformanceFrequency(&perf_frequency)) { + hrtime_interval_ = 1.0 / perf_frequency.QuadPart; + } else { + hrtime_interval_= 0; + } } @@ -463,26 +465,27 @@ int uv_get_process_title(char* buffer, size_t size) { uint64_t uv_hrtime(void) { - LARGE_INTEGER counter; - uv__once_init(); + return uv__hrtime(UV__NANOSEC); +} + +uint64_t uv__hrtime(double scale) { + LARGE_INTEGER counter; - /* If the performance frequency is zero, there's no support. */ - if (hrtime_frequency_ == 0) { - /* uv__set_sys_error(loop, ERROR_NOT_SUPPORTED); */ + /* If the performance interval is zero, there's no support. */ + if (hrtime_interval_ == 0) { return 0; } if (!QueryPerformanceCounter(&counter)) { - /* uv__set_sys_error(loop, GetLastError()); */ return 0; } /* Because we have no guarantee about the order of magnitude of the - * performance counter frequency, integer math could cause this computation + * performance counter interval, integer math could cause this computation * to overflow. Therefore we resort to floating point math. */ - return (uint64_t) ((double) counter.QuadPart / hrtime_frequency_); + return (uint64_t) ((double) counter.QuadPart * hrtime_interval_ * scale); }