Skip to content

Commit 427e4c9

Browse files
joaocgreissaghul
authored andcommitted
win: wait for full timeout duration
uv_poll should wait for at least the full timeout duration when there is nothing else to do. This was not happening because GetQueuedCompletionStatus can occasionally return up to 15ms early. The added test reproduces https://github.com/joyent/node/blob/d13d7f74d794340ac5e126cfb4ce507fe0f803d5/test/simple/test-timers-first-fire.js on libuv, being flaky before this fix. Fixes: nodejs/node-v0.x-archive#8960 PR-URL: #385 Reviewed-By: Alexis Campailla <alexis@janeasystems.com> Reviewed-by: Bert Belder <bertbelder@gmail.com>
1 parent 61f13a6 commit 427e4c9

File tree

5 files changed

+114
-55
lines changed

5 files changed

+114
-55
lines changed

src/win/core.c

+83-47
Original file line numberDiff line numberDiff line change
@@ -256,30 +256,48 @@ static void uv_poll(uv_loop_t* loop, DWORD timeout) {
256256
ULONG_PTR key;
257257
OVERLAPPED* overlapped;
258258
uv_req_t* req;
259+
int repeat;
260+
uint64_t timeout_time;
259261

260-
GetQueuedCompletionStatus(loop->iocp,
261-
&bytes,
262-
&key,
263-
&overlapped,
264-
timeout);
262+
timeout_time = loop->time + timeout;
265263

266-
if (overlapped) {
267-
/* Package was dequeued */
268-
req = uv_overlapped_to_req(overlapped);
269-
uv_insert_pending_req(loop, req);
264+
for (repeat = 0; ; repeat++) {
265+
GetQueuedCompletionStatus(loop->iocp,
266+
&bytes,
267+
&key,
268+
&overlapped,
269+
timeout);
270270

271-
/* Some time might have passed waiting for I/O,
272-
* so update the loop time here.
273-
*/
274-
uv_update_time(loop);
275-
} else if (GetLastError() != WAIT_TIMEOUT) {
276-
/* Serious error */
277-
uv_fatal_error(GetLastError(), "GetQueuedCompletionStatus");
278-
} else if (timeout > 0) {
279-
/* GetQueuedCompletionStatus can occasionally return a little early.
280-
* Make sure that the desired timeout is reflected in the loop time.
281-
*/
282-
uv__time_forward(loop, timeout);
271+
if (overlapped) {
272+
/* Package was dequeued */
273+
req = uv_overlapped_to_req(overlapped);
274+
uv_insert_pending_req(loop, req);
275+
276+
/* Some time might have passed waiting for I/O,
277+
* so update the loop time here.
278+
*/
279+
uv_update_time(loop);
280+
} else if (GetLastError() != WAIT_TIMEOUT) {
281+
/* Serious error */
282+
uv_fatal_error(GetLastError(), "GetQueuedCompletionStatus");
283+
} else if (timeout > 0) {
284+
/* GetQueuedCompletionStatus can occasionally return a little early.
285+
* Make sure that the desired timeout target time is reached.
286+
*/
287+
uv_update_time(loop);
288+
if (timeout_time > loop->time) {
289+
timeout = (DWORD)(timeout_time - loop->time);
290+
/* The first call to GetQueuedCompletionStatus should return very
291+
* close to the target time and the second should reach it, but
292+
* this is not stated in the documentation. To make sure a busy
293+
* loop cannot happen, the timeout is increased exponentially
294+
* starting on the third round.
295+
*/
296+
timeout += repeat ? (1 << (repeat - 1)) : 0;
297+
continue;
298+
}
299+
}
300+
break;
283301
}
284302
}
285303

@@ -290,33 +308,51 @@ static void uv_poll_ex(uv_loop_t* loop, DWORD timeout) {
290308
OVERLAPPED_ENTRY overlappeds[128];
291309
ULONG count;
292310
ULONG i;
293-
294-
success = pGetQueuedCompletionStatusEx(loop->iocp,
295-
overlappeds,
296-
ARRAY_SIZE(overlappeds),
297-
&count,
298-
timeout,
299-
FALSE);
300-
301-
if (success) {
302-
for (i = 0; i < count; i++) {
303-
/* Package was dequeued */
304-
req = uv_overlapped_to_req(overlappeds[i].lpOverlapped);
305-
uv_insert_pending_req(loop, req);
311+
int repeat;
312+
uint64_t timeout_time;
313+
314+
timeout_time = loop->time + timeout;
315+
316+
for (repeat = 0; ; repeat++) {
317+
success = pGetQueuedCompletionStatusEx(loop->iocp,
318+
overlappeds,
319+
ARRAY_SIZE(overlappeds),
320+
&count,
321+
timeout,
322+
FALSE);
323+
324+
if (success) {
325+
for (i = 0; i < count; i++) {
326+
/* Package was dequeued */
327+
req = uv_overlapped_to_req(overlappeds[i].lpOverlapped);
328+
uv_insert_pending_req(loop, req);
329+
}
330+
331+
/* Some time might have passed waiting for I/O,
332+
* so update the loop time here.
333+
*/
334+
uv_update_time(loop);
335+
} else if (GetLastError() != WAIT_TIMEOUT) {
336+
/* Serious error */
337+
uv_fatal_error(GetLastError(), "GetQueuedCompletionStatusEx");
338+
} else if (timeout > 0) {
339+
/* GetQueuedCompletionStatus can occasionally return a little early.
340+
* Make sure that the desired timeout target time is reached.
341+
*/
342+
uv_update_time(loop);
343+
if (timeout_time > loop->time) {
344+
timeout = (DWORD)(timeout_time - loop->time);
345+
/* The first call to GetQueuedCompletionStatus should return very
346+
* close to the target time and the second should reach it, but
347+
* this is not stated in the documentation. To make sure a busy
348+
* loop cannot happen, the timeout is increased exponentially
349+
* starting on the third round.
350+
*/
351+
timeout += repeat ? (1 << (repeat - 1)) : 0;
352+
continue;
353+
}
306354
}
307-
308-
/* Some time might have passed waiting for I/O,
309-
* so update the loop time here.
310-
*/
311-
uv_update_time(loop);
312-
} else if (GetLastError() != WAIT_TIMEOUT) {
313-
/* Serious error */
314-
uv_fatal_error(GetLastError(), "GetQueuedCompletionStatusEx");
315-
} else if (timeout > 0) {
316-
/* GetQueuedCompletionStatus can occasionally return a little early.
317-
* Make sure that the desired timeout is reflected in the loop time.
318-
*/
319-
uv__time_forward(loop, timeout);
355+
break;
320356
}
321357
}
322358

src/win/internal.h

-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,6 @@ void uv_poll_endgame(uv_loop_t* loop, uv_poll_t* handle);
246246
void uv_timer_endgame(uv_loop_t* loop, uv_timer_t* handle);
247247

248248
DWORD uv__next_timeout(const uv_loop_t* loop);
249-
void uv__time_forward(uv_loop_t* loop, uint64_t msecs);
250249
void uv_process_timers(uv_loop_t* loop);
251250

252251

src/win/timer.c

+2-7
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,8 @@
3434

3535
void uv_update_time(uv_loop_t* loop) {
3636
uint64_t new_time = uv__hrtime(UV__MILLISEC);
37-
if (new_time > loop->time) {
38-
loop->time = new_time;
39-
}
40-
}
41-
42-
void uv__time_forward(uv_loop_t* loop, uint64_t msecs) {
43-
loop->time += msecs;
37+
assert(new_time >= loop->time);
38+
loop->time = new_time;
4439
}
4540

4641

test/test-list.h

+2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ TEST_DECLARE (timer_huge_repeat)
154154
TEST_DECLARE (timer_run_once)
155155
TEST_DECLARE (timer_from_check)
156156
TEST_DECLARE (timer_null_callback)
157+
TEST_DECLARE (timer_early_check)
157158
TEST_DECLARE (idle_starvation)
158159
TEST_DECLARE (loop_handles)
159160
TEST_DECLARE (get_loadavg)
@@ -522,6 +523,7 @@ TASK_LIST_START
522523
TEST_ENTRY (timer_run_once)
523524
TEST_ENTRY (timer_from_check)
524525
TEST_ENTRY (timer_null_callback)
526+
TEST_ENTRY (timer_early_check)
525527

526528
TEST_ENTRY (idle_starvation)
527529

test/test-timer.c

+27
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,30 @@ TEST_IMPL(timer_null_callback) {
301301
MAKE_VALGRIND_HAPPY();
302302
return 0;
303303
}
304+
305+
306+
static uint64_t timer_early_check_expected_time;
307+
308+
309+
static void timer_early_check_cb(uv_timer_t* handle) {
310+
uint64_t hrtime = uv_hrtime() / 1000000;
311+
ASSERT(hrtime >= timer_early_check_expected_time);
312+
}
313+
314+
315+
TEST_IMPL(timer_early_check) {
316+
uv_timer_t timer_handle;
317+
const uint64_t timeout_ms = 10;
318+
319+
timer_early_check_expected_time = uv_now(uv_default_loop()) + timeout_ms;
320+
321+
ASSERT(0 == uv_timer_init(uv_default_loop(), &timer_handle));
322+
ASSERT(0 == uv_timer_start(&timer_handle, timer_early_check_cb, timeout_ms, 0));
323+
ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT));
324+
325+
uv_close((uv_handle_t*) &timer_handle, NULL);
326+
ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT));
327+
328+
MAKE_VALGRIND_HAPPY();
329+
return 0;
330+
}

0 commit comments

Comments
 (0)