Skip to content
This repository has been archived by the owner on May 4, 2018. It is now read-only.

Commit

Permalink
windows: improve timer precision
Browse files Browse the repository at this point in the history
Improve timing precision by using QueryPerformanceCounter.

This is part of the fix for Node.js' test-timers-first-fire.js.
  • Loading branch information
orangemocha authored and saghul committed Sep 17, 2014
1 parent 234b1e0 commit 6ced8c2
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 67 deletions.
2 changes: 0 additions & 2 deletions include/uv-win.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_ */ \
Expand Down
24 changes: 15 additions & 9 deletions src/win/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions src/win/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
49 changes: 10 additions & 39 deletions src/win/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
37 changes: 20 additions & 17 deletions src/win/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;


/*
Expand All @@ -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;
}
}


Expand Down Expand Up @@ -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);
}


Expand Down

0 comments on commit 6ced8c2

Please sign in to comment.