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

Windows: improve timer precision #1165

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -138,7 +138,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 @@ -313,13 +312,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 @@ -346,13 +349,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 @@ -411,7 +418,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 @@ -323,6 +323,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 @@ -206,16 +184,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
35 changes: 20 additions & 15 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,12 +465,15 @@ 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) {
/* If the performance interval is zero, there's no support. */
if (hrtime_interval_ == 0) {
/* uv__set_sys_error(loop, ERROR_NOT_SUPPORTED); */
return 0;
}
Expand All @@ -479,10 +484,10 @@ uint64_t uv_hrtime(void) {
}

/* 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