Browse files

unix: update loop->time after poll

Fixes a bug where timers expire prematurely when the following
conditions hold:

  a) libuv first spends some time blocked in the platform poll function
  b) a callback then calls uv_timer_start()

Cause: uv_timer_start() uses an out-of-date loop->time in its
'when should the timer callback run?' calculations.

Solution: Update loop->time before invoking any callbacks.

Fixes #678.
  • Loading branch information...
1 parent 339033a commit 2f55353490506ed08709c65205bedeb67342b194 @bnoordhuis bnoordhuis committed Jan 6, 2013
Showing with 36 additions and 22 deletions.
  1. +2 −2 src/unix/core.c
  2. +13 −8 src/unix/internal.h
  3. +7 −4 src/unix/kqueue.c
  4. +7 −4 src/unix/linux/linux-core.c
  5. +7 −4 src/unix/sunos.c
View
4 src/unix/core.c
@@ -286,7 +286,7 @@ int uv_run2(uv_loop_t* loop, uv_run_mode mode) {
return 0;
do {
- uv_update_time(loop);
+ uv__update_time(loop);
uv__run_timers(loop);
uv__run_idle(loop);
uv__run_prepare(loop);
@@ -307,7 +307,7 @@ int uv_run(uv_loop_t* loop) {
void uv_update_time(uv_loop_t* loop) {
- loop->time = uv__hrtime() / 1000000;
+ uv__update_time(loop);
}
View
21 src/unix/internal.h
@@ -105,14 +105,6 @@ enum {
UV_TCP_SINGLE_ACCEPT = 0x400 /* Only accept() when idle. */
};
-__attribute__((unused))
-static void uv__req_init(uv_loop_t* loop, uv_req_t* req, uv_req_type type) {
- req->type = type;
- uv__req_register(loop, req);
-}
-#define uv__req_init(loop, req, type) \
- uv__req_init((loop), (uv_req_t*)(req), (type))
-
/* core */
void uv__handle_init(uv_loop_t* loop, uv_handle_t* handle, uv_handle_type type);
int uv__nonblock(int fd, int set);
@@ -238,4 +230,17 @@ static const int kFSEventStreamEventFlagItemIsSymlink = 0x00040000;
#endif /* defined(__APPLE__) */
+__attribute__((unused))
+static void uv__req_init(uv_loop_t* loop, uv_req_t* req, uv_req_type type) {
+ req->type = type;
+ uv__req_register(loop, req);
+}
+#define uv__req_init(loop, req, type) \
+ uv__req_init((loop), (uv_req_t*)(req), (type))
+
+__attribute__((unused))
+static void uv__update_time(uv_loop_t* loop) {
+ loop->time = uv__hrtime() / 1000000;
+}
+
#endif /* UV_UNIX_INTERNAL_H_ */
View
11 src/unix/kqueue.c
@@ -141,6 +141,12 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
ARRAY_SIZE(events),
timeout == -1 ? NULL : &spec);
+ /* Update loop->time unconditionally. It's tempting to skip the update when
+ * timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the
+ * operating system didn't reschedule our process while in the syscall.
+ */
+ SAVE_ERRNO(uv__update_time(loop));
+
if (nfds == 0) {
assert(timeout != -1);
return;
@@ -244,10 +250,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
update_timeout:
assert(timeout > 0);
- diff = uv__hrtime() / 1000000;
- assert(diff >= base);
- diff -= base;
-
+ diff = loop->time - base;
if (diff >= (uint64_t) timeout)
return;
View
11 src/unix/linux/linux-core.c
@@ -182,6 +182,12 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
ARRAY_SIZE(events),
timeout);
+ /* Update loop->time unconditionally. It's tempting to skip the update when
+ * timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the
+ * operating system didn't reschedule our process while in the syscall.
+ */
+ SAVE_ERRNO(uv__update_time(loop));
+
if (nfds == 0) {
assert(timeout != -1);
return;
@@ -243,10 +249,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
update_timeout:
assert(timeout > 0);
- diff = uv__hrtime() / 1000000;
- assert(diff >= base);
- diff -= base;
-
+ diff = loop->time - base;
if (diff >= (uint64_t) timeout)
return;
View
11 src/unix/sunos.c
@@ -150,6 +150,12 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
abort();
}
+ /* Update loop->time unconditionally. It's tempting to skip the update when
+ * timeout == 0 (i.e. non-blocking poll) but there is no guarantee that the
+ * operating system didn't reschedule our process while in the syscall.
+ */
+ SAVE_ERRNO(uv__update_time(loop));
+
if (events[0].portev_source == 0) {
if (timeout == 0)
return;
@@ -211,10 +217,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
update_timeout:
assert(timeout > 0);
- diff = uv__hrtime() / 1000000;
- assert(diff >= base);
- diff -= base;
-
+ diff = loop->time - base;
if (diff >= (uint64_t) timeout)
return;

0 comments on commit 2f55353

Please sign in to comment.