From 0971598d025a4ef13430912e7b5cb931438a2333 Mon Sep 17 00:00:00 2001 From: Ben Noordhuis Date: Fri, 29 Jun 2012 19:16:40 +0200 Subject: [PATCH] unix: fix EINPROGRESS busy loop Don't make the event loop spin when connect() returns EINPROGRESS. Test case: #include "uv.h" static void connect_cb(uv_connect_t* req, int status) { // ... } int main() { uv_tcp_t handle; uv_connect_t req; struct sockaddr_in addr; addr = uv_ip4_addr("8.8.8.8", 1234); // unreachable uv_tcp_init(uv_default_loop(), &handle); uv_tcp_connect(&req, (uv_stream_t*)&handle, addr, connect_cb); uv_run(uv_default_loop()); // busy loops until connection times out return 0; } After EINPROGRESS, there are zero active handles and one active request. That in turn makes uv__poll_timeout() believe that it should merely poll, not block, in epoll() / kqueue() / port_getn(). Sidestep that by artificially starting the handle on connect() and stopping it again once the TCP handshake completes / is rejected / times out. It's a slightly hacky approach because I don't want to change the ABI of the stable branch. I'll address it properly in the master branch. --- src/unix/internal.h | 3 ++- src/unix/stream.c | 6 ++++++ src/unix/tcp.c | 13 +++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/unix/internal.h b/src/unix/internal.h index dd46c95e..382c2722 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -93,7 +93,8 @@ enum { UV_STREAM_WRITABLE = 0x40, /* The stream is writable */ UV_STREAM_BLOCKING = 0x80, /* Synchronous writes. */ UV_TCP_NODELAY = 0x100, /* Disable Nagle. */ - UV_TCP_KEEPALIVE = 0x200 /* Turn on keep-alive. */ + UV_TCP_KEEPALIVE = 0x200, /* Turn on keep-alive. */ + UV_TCP_CONNECTING = 0x400 /* Not alway set. See uv_connect() in tcp.c */ }; inline static void uv__req_init(uv_loop_t* loop, diff --git a/src/unix/stream.c b/src/unix/stream.c index e26b8723..0dbf1d78 100644 --- a/src/unix/stream.c +++ b/src/unix/stream.c @@ -790,6 +790,12 @@ static void uv__stream_connect(uv_stream_t* stream) { stream->connect_req = NULL; uv__req_unregister(stream->loop, req); + /* Hack. See uv__connect() in tcp.c */ + if (stream->flags & UV_TCP_CONNECTING) { + assert(stream->type == UV_TCP); + uv__handle_stop(stream); + } + if (req->cb) { uv__set_sys_error(stream->loop, error); req->cb(req, error ? -1 : 0); diff --git a/src/unix/tcp.c b/src/unix/tcp.c index 233be825..1aeba0ef 100644 --- a/src/unix/tcp.c +++ b/src/unix/tcp.c @@ -109,8 +109,17 @@ static int uv__connect(uv_connect_t* req, while (r == -1 && errno == EINTR); if (r == -1) { - if (errno == EINPROGRESS) - ; /* not an error */ + if (errno == EINPROGRESS) { + /* Not an error. However, we need to keep the event loop from spinning + * while the connection is in progress. Artificially start the handle + * and stop it again in uv__stream_connect() in stream.c. Yes, it's a + * hack but there's no good alternative, the v0.8 ABI is frozen. + */ + if (!uv__is_active(handle)) { + handle->flags |= UV_TCP_CONNECTING; + uv__handle_start(handle); + } + } else if (errno == ECONNREFUSED) /* If we get a ECONNREFUSED wait until the next tick to report the * error. Solaris wants to report immediately--other unixes want to