Elliot Saba <staticfloat@gmail.com>
Wynn Wilkes <wynnw@movenetworks.com>
Andrei Sedoi <bsnote@gmail.com>
+Chris Bank <cbank@adobe.com>
+Geert Jansen <geertj@gmail.com>
-2013.10.19, Version 0.10.18 (Stable)
+2013.11.13, Version 0.10.19 (Stable)
+
+Changes since version 0.10.18:
+
+* darwin: avoid calling GetCurrentProcess (Fedor Indutny)
+
+* unix: update events from pevents between polls (Fedor Indutny)
+
+* fsevents: support japaneese characters in path (Chris Bank)
+
+* linux: don't turn on SO_REUSEPORT socket option (Ben Noordhuis)
+
+* build: fix windows smp build with gyp (Geert Jansen)
+
+* linux: handle EPOLLHUP without EPOLLIN/EPOLLOUT (Ben Noordhuis)
+
+* unix: fix reopened fd bug (Fedor Indutny)
+
+* core: fix fake watcher list and count preservation (Fedor Indutny)
+
+
+2013.10.19, Version 0.10.18 (Stable), 9ec52963b585e822e87bdc5de28d6143aff0d2e5
Changes since version 0.10.17:
Unix users run
- ./gyp_uv -f make
+ ./gyp_uv.py -f make
make -C out
Macintosh users run
- ./gyp_uv -f xcode
+ ./gyp_uv.py -f xcode
xcodebuild -project uv.xcodeproj -configuration Release -target All
Note for UNIX users: compile your project with `-D_LARGEFILE_SOURCE` and
test/test-tcp-bind6-error.o \
test/test-tcp-bind-error.o \
test/test-tcp-close.o \
+ test/test-tcp-close-accept.o \
test/test-tcp-close-while-connecting.o \
test/test-tcp-connect6-error.o \
test/test-tcp-connect-error-after-write.o \
test/test-udp-send-and-recv.o \
test/test-util.o \
test/test-walk-handles.o \
+ test/test-watcher-cross-stop.o \
.PHONY: all bench clean clean-platform distclean test
test/test-tcp-bind-error.c
test/test-tcp-bind6-error.c
test/test-tcp-close-while-connecting.c
+test/test-tcp-close-accept.c
test/test-tcp-close.c
test/test-tcp-connect-error-after-write.c
test/test-tcp-connect-error.c
test/test-udp-send-and-recv.c
test/test-util.c
test/test-walk-handles.c
+test/test-watcher-cross-stop.c
"
case `uname -s` in
static void maybe_resize(uv_loop_t* loop, unsigned int len) {
uv__io_t** watchers;
+ void* fake_watcher_list;
+ void* fake_watcher_count;
unsigned int nwatchers;
unsigned int i;
if (len <= loop->nwatchers)
return;
- nwatchers = next_power_of_two(len);
- watchers = realloc(loop->watchers, nwatchers * sizeof(loop->watchers[0]));
+ /* Preserve fake watcher list and count at the end of the watchers */
+ if (loop->watchers != NULL) {
+ fake_watcher_list = loop->watchers[loop->nwatchers];
+ fake_watcher_count = loop->watchers[loop->nwatchers + 1];
+ } else {
+ fake_watcher_list = NULL;
+ fake_watcher_count = NULL;
+ }
+
+ nwatchers = next_power_of_two(len + 2) - 2;
+ watchers = realloc(loop->watchers,
+ (nwatchers + 2) * sizeof(loop->watchers[0]));
if (watchers == NULL)
abort();
-
for (i = loop->nwatchers; i < nwatchers; i++)
watchers[i] = NULL;
+ watchers[nwatchers] = fake_watcher_list;
+ watchers[nwatchers + 1] = fake_watcher_count;
loop->watchers = watchers;
loop->nwatchers = nwatchers;
void uv__io_close(uv_loop_t* loop, uv__io_t* w) {
uv__io_stop(loop, w, UV__POLLIN | UV__POLLOUT);
ngx_queue_remove(&w->pending_queue);
+
+ /* Remove stale events for this file descriptor */
+ uv__platform_invalidate_fd(loop, w->fd);
}
CFStringRef,
CFStringRef,
CFDictionaryRef*);
+ typedef CFDictionaryRef (*LSApplicationCheckInType)(int, CFDictionaryRef);
+ typedef OSStatus (*SetApplicationIsDaemonType)(int);
+ typedef void (*LSSetApplicationLaunchServicesServerConnectionStatusType)(
+ uint64_t, void*);
CFBundleRef launch_services_bundle;
LSGetCurrentApplicationASNType ls_get_current_application_asn;
LSSetApplicationInformationItemType ls_set_application_information_item;
CFStringRef* display_name_key;
- ProcessSerialNumber psn;
CFTypeRef asn;
CFStringRef display_name;
OSStatus err;
+ CFBundleRef hi_services_bundle;
+ LSApplicationCheckInType ls_application_check_in;
+ SetApplicationIsDaemonType set_application_is_daemon;
+ LSSetApplicationLaunchServicesServerConnectionStatusType
+ ls_set_application_launch_services_server_connection_status;
launch_services_bundle =
CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
if (display_name_key == NULL || *display_name_key == NULL)
return -1;
- /* Force the process manager to initialize. */
- GetCurrentProcess(&psn);
+ /* Black 10.9 magic, to remove (Not responding) mark in Activity Monitor */
+ hi_services_bundle =
+ CFBundleGetBundleWithIdentifier(CFSTR("com.apple.HIServices"));
+ if (hi_services_bundle == NULL)
+ return -1;
+
+ set_application_is_daemon = CFBundleGetFunctionPointerForName(
+ hi_services_bundle,
+ CFSTR("SetApplicationIsDaemon"));
+ ls_application_check_in = CFBundleGetFunctionPointerForName(
+ launch_services_bundle,
+ CFSTR("_LSApplicationCheckIn"));
+ ls_set_application_launch_services_server_connection_status =
+ CFBundleGetFunctionPointerForName(
+ launch_services_bundle,
+ CFSTR("_LSSetApplicationLaunchServicesServerConnectionStatus"));
+ if (set_application_is_daemon == NULL ||
+ ls_application_check_in == NULL ||
+ ls_set_application_launch_services_server_connection_status == NULL) {
+ return -1;
+ }
+
+ if (set_application_is_daemon(1) != noErr)
+ return -1;
+
+ ls_set_application_launch_services_server_connection_status(0, NULL);
+
+ /* Check into process manager?! */
+ ls_application_check_in(-2,
+ CFBundleGetInfoDictionary(CFBundleGetMainBundle()));
display_name = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8);
asn = ls_get_current_application_asn();
}
+void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) {
+ struct kevent* events;
+ uintptr_t i;
+ uintptr_t nfds;
+
+ assert(loop->watchers != NULL);
+
+ events = (struct kevent*) loop->watchers[loop->nwatchers];
+ nfds = (uintptr_t) loop->watchers[loop->nwatchers + 1];
+ if (events == NULL)
+ return;
+
+ /* Invalidate events with same file descriptor */
+ for (i = 0; i < nfds; i++)
+ if ((int) events[i].ident == fd)
+ events[i].ident = -1;
+}
+
+
static void uv__cf_loop_runner(void* arg) {
uv_loop_t* loop;
handle->realpath_len = strlen(handle->realpath);
/* Initialize paths array */
- path = CFStringCreateWithCString(NULL,
- handle->filename,
- CFStringGetSystemEncoding());
+ path = CFStringCreateWithFileSystemRepresentation(NULL, handle->filename);
paths = CFArrayCreate(NULL, (const void**)&path, 1, NULL);
latency = 0.15;
int uv__kqueue_init(uv_loop_t* loop);
int uv__platform_loop_init(uv_loop_t* loop, int default_loop);
void uv__platform_loop_delete(uv_loop_t* loop);
+void uv__platform_invalidate_fd(uv_loop_t* loop, int fd);
/* various */
void uv__async_close(uv_async_t* handle);
nevents = 0;
+ assert(loop->watchers != NULL);
+ loop->watchers[loop->nwatchers] = (void*) events;
+ loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds;
for (i = 0; i < nfds; i++) {
ev = events + i;
fd = ev->ident;
w = loop->watchers[fd];
+ /* Skip invalidated events, see uv__platform_invalidate_fd */
+ if (fd == -1)
+ continue;
+
if (w == NULL) {
/* File descriptor that we've stopped watching, disarm it. */
/* TODO batch up */
revents = 0;
if (ev->filter == EVFILT_READ) {
- if (w->events & UV__POLLIN) {
+ if (w->pevents & UV__POLLIN) {
revents |= UV__POLLIN;
w->rcount = ev->data;
} else {
}
if (ev->filter == EVFILT_WRITE) {
- if (w->events & UV__POLLOUT) {
+ if (w->pevents & UV__POLLOUT) {
revents |= UV__POLLOUT;
w->wcount = ev->data;
} else {
w->cb(loop, w, revents);
nevents++;
}
+ loop->watchers[loop->nwatchers] = NULL;
+ loop->watchers[loop->nwatchers + 1] = NULL;
if (nevents != 0) {
if (nfds == ARRAY_SIZE(events) && --count != 0) {
}
+void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) {
+ struct uv__epoll_event* events;
+ uintptr_t i;
+ uintptr_t nfds;
+
+ assert(loop->watchers != NULL);
+
+ events = (struct uv__epoll_event*) loop->watchers[loop->nwatchers];
+ nfds = (uintptr_t) loop->watchers[loop->nwatchers + 1];
+ if (events == NULL)
+ return;
+
+ /* Invalidate events with same file descriptor */
+ for (i = 0; i < nfds; i++)
+ if ((int) events[i].data == fd)
+ events[i].data = -1;
+}
+
+
void uv__io_poll(uv_loop_t* loop, int timeout) {
struct uv__epoll_event events[1024];
struct uv__epoll_event* pe;
nevents = 0;
+ assert(loop->watchers != NULL);
+ loop->watchers[loop->nwatchers] = (void*) events;
+ loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds;
for (i = 0; i < nfds; i++) {
pe = events + i;
fd = pe->data;
+ /* Skip invalidated events, see uv__platform_invalidate_fd */
+ if (fd == -1)
+ continue;
+
assert(fd >= 0);
assert((unsigned) fd < loop->nwatchers);
continue;
}
- w->cb(loop, w, pe->events);
- nevents++;
+ /* Give users only events they're interested in. Prevents spurious
+ * callbacks when previous callback invocation in this loop has stopped
+ * the current watcher. Also, filters out events that users has not
+ * requested us to watch.
+ */
+ pe->events &= w->pevents | UV__POLLERR | UV__POLLHUP;
+
+ /* Work around an epoll quirk where it sometimes reports just the
+ * EPOLLERR or EPOLLHUP event. In order to force the event loop to
+ * move forward, we merge in the read/write events that the watcher
+ * is interested in; uv__read() and uv__write() will then deal with
+ * the error or hangup in the usual fashion.
+ *
+ * Note to self: happens when epoll reports EPOLLIN|EPOLLHUP, the user
+ * reads the available data, calls uv_read_stop(), then sometime later
+ * calls uv_read_start() again. By then, libuv has forgotten about the
+ * hangup and the kernel won't report EPOLLIN again because there's
+ * nothing left to read. If anything, libuv is to blame here. The
+ * current hack is just a quick bandaid; to properly fix it, libuv
+ * needs to remember the error/hangup event. We should get that for
+ * free when we switch over to edge-triggered I/O.
+ */
+ if (pe->events == UV__EPOLLERR || pe->events == UV__EPOLLHUP)
+ pe->events |= w->pevents & (UV__EPOLLIN | UV__EPOLLOUT);
+
+ if (pe->events != 0) {
+ w->cb(loop, w, pe->events);
+ nevents++;
+ }
}
+ loop->watchers[loop->nwatchers] = NULL;
+ loop->watchers[loop->nwatchers + 1] = NULL;
if (nevents != 0) {
if (nfds == ARRAY_SIZE(events) && --count != 0) {
}
+void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) {
+ struct port_event* events;
+ uintptr_t i;
+ uintptr_t nfds;
+
+ assert(loop->watchers != NULL);
+
+ events = (struct port_event*) loop->watchers[loop->nwatchers];
+ nfds = (uintptr_t) loop->watchers[loop->nwatchers + 1];
+ if (events == NULL)
+ return;
+
+ /* Invalidate events with same file descriptor */
+ for (i = 0; i < nfds; i++)
+ if ((int) events[i].portev_object == fd)
+ events[i].portev_object = -1;
+}
+
+
void uv__io_poll(uv_loop_t* loop, int timeout) {
struct port_event events[1024];
struct port_event* pe;
nevents = 0;
+ assert(loop->watchers != NULL);
+ loop->watchers[loop->nwatchers] = (void*) events;
+ loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds;
for (i = 0; i < nfds; i++) {
pe = events + i;
fd = pe->portev_object;
+ /* Skip invalidated events, see uv__platform_invalidate_fd */
+ if (fd == -1)
+ continue;
+
assert(fd >= 0);
assert((unsigned) fd < loop->nwatchers);
if (w->pevents != 0 && ngx_queue_empty(&w->watcher_queue))
ngx_queue_insert_tail(&loop->watcher_queue, &w->watcher_queue);
}
+ loop->watchers[loop->nwatchers] = NULL;
+ loop->watchers[loop->nwatchers + 1] = NULL;
if (nevents != 0) {
if (nfds == ARRAY_SIZE(events) && --count != 0) {
}
+/* On the BSDs, SO_REUSEPORT implies SO_REUSEADDR but with some additional
+ * refinements for programs that use multicast.
+ *
+ * Linux as of 3.9 has a SO_REUSEPORT socket option but with semantics that
+ * are different from the BSDs: it _shares_ the port rather than steal it
+ * from the current listener. While useful, it's not something we can emulate
+ * on other platforms so we don't enable it.
+ */
+static int uv__set_reuse(int fd) {
+ int yes;
+
+#if defined(SO_REUSEPORT) && !defined(__linux__)
+ yes = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)))
+ return -errno;
+#else
+ yes = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)))
+ return -errno;
+#endif
+
+ return 0;
+}
+
+
static int uv__bind(uv_udp_t* handle,
int domain,
struct sockaddr* addr,
unsigned flags) {
int saved_errno;
int status;
+ int err;
int yes;
int fd;
}
fd = handle->io_watcher.fd;
- yes = 1;
- if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) {
- uv__set_sys_error(handle->loop, errno);
+ err = uv__set_reuse(fd);
+ if (err) {
+ uv__set_sys_error(handle->loop, -err);
goto out;
}
- /* On the BSDs, SO_REUSEADDR lets you reuse an address that's in the TIME_WAIT
- * state (i.e. was until recently tied to a socket) while SO_REUSEPORT lets
- * multiple processes bind to the same address. Yes, it's something of a
- * misnomer but then again, SO_REUSEADDR was already taken.
- *
- * None of the above applies to Linux: SO_REUSEADDR implies SO_REUSEPORT on
- * Linux and hence it does not have SO_REUSEPORT at all.
- */
-#ifdef SO_REUSEPORT
- yes = 1;
- if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof yes) == -1) {
- uv__set_sys_error(handle->loop, errno);
- goto out;
- }
-#endif
-
if (flags & UV_UDP_IPV6ONLY) {
#ifdef IPV6_V6ONLY
yes = 1;
#define UV_VERSION_MAJOR 0
#define UV_VERSION_MINOR 10
-#define UV_VERSION_PATCH 18
+#define UV_VERSION_PATCH 19
#define UV_VERSION_IS_RELEASE 1
TEST_DECLARE (tcp_connect_timeout)
TEST_DECLARE (tcp_close_while_connecting)
TEST_DECLARE (tcp_close)
+TEST_DECLARE (tcp_close_accept)
TEST_DECLARE (tcp_flags)
TEST_DECLARE (tcp_write_to_half_open_connection)
TEST_DECLARE (tcp_unexpected_read)
TEST_DECLARE (loop_handles)
TEST_DECLARE (get_loadavg)
TEST_DECLARE (walk_handles)
+TEST_DECLARE (watcher_cross_stop)
TEST_DECLARE (ref)
TEST_DECLARE (idle_ref)
TEST_DECLARE (async_ref)
TEST_ENTRY (tcp_connect_timeout)
TEST_ENTRY (tcp_close_while_connecting)
TEST_ENTRY (tcp_close)
+ TEST_ENTRY (tcp_close_accept)
TEST_ENTRY (tcp_flags)
TEST_ENTRY (tcp_write_to_half_open_connection)
TEST_ENTRY (tcp_unexpected_read)
TEST_ENTRY (loop_handles)
TEST_ENTRY (walk_handles)
+ TEST_ENTRY (watcher_cross_stop)
+
TEST_ENTRY (active)
TEST_ENTRY (embed)
--- /dev/null
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "uv.h"
+#include "task.h"
+
+#include <stdio.h>
+#include <string.h>
+
+static struct sockaddr_in addr;
+static uv_tcp_t tcp_server;
+static uv_tcp_t tcp_outgoing[2];
+static uv_tcp_t tcp_incoming[ARRAY_SIZE(tcp_outgoing)];
+static uv_connect_t connect_reqs[ARRAY_SIZE(tcp_outgoing)];
+static uv_tcp_t tcp_check;
+static uv_connect_t tcp_check_req;
+static uv_write_t write_reqs[ARRAY_SIZE(tcp_outgoing)];
+static unsigned int got_connections;
+static unsigned int close_cb_called;
+static unsigned int write_cb_called;
+static unsigned int read_cb_called;
+
+static void close_cb(uv_handle_t* handle) {
+ close_cb_called++;
+}
+
+static void write_cb(uv_write_t* req, int status) {
+ ASSERT(status == 0);
+ write_cb_called++;
+}
+
+static void connect_cb(uv_connect_t* req, int status) {
+ unsigned int i;
+ uv_buf_t buf;
+ uv_stream_t* outgoing;
+
+ if (req == &tcp_check_req) {
+ ASSERT(status != 0);
+
+ /* Close check and incoming[0], time to finish test */
+ uv_close((uv_handle_t*) &tcp_incoming[0], close_cb);
+ uv_close((uv_handle_t*) &tcp_check, close_cb);
+ return;
+ }
+
+ ASSERT(status == 0);
+ ASSERT(connect_reqs <= req);
+ ASSERT(req <= connect_reqs + ARRAY_SIZE(connect_reqs));
+ i = req - connect_reqs;
+
+ buf = uv_buf_init("x", 1);
+ outgoing = (uv_stream_t*) &tcp_outgoing[i];
+ ASSERT(0 == uv_write(&write_reqs[i], outgoing, &buf, 1, write_cb));
+}
+
+static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) {
+ static char buf[1];
+
+ return uv_buf_init(buf, sizeof(buf));
+}
+
+static void read_cb(uv_stream_t* stream, ssize_t nread, uv_buf_t b) {
+ uv_loop_t* loop;
+ unsigned int i;
+
+ /* Only first stream should receive read events */
+ ASSERT(stream == (uv_stream_t*) &tcp_incoming[0]);
+ ASSERT(0 == uv_read_stop(stream));
+ ASSERT(1 == nread);
+
+ loop = stream->loop;
+ read_cb_called++;
+
+ /* Close all active incomings, except current one */
+ for (i = 1; i < got_connections; i++)
+ uv_close((uv_handle_t*) &tcp_incoming[i], close_cb);
+
+ /* Create new fd that should be one of the closed incomings */
+ ASSERT(0 == uv_tcp_init(loop, &tcp_check));
+ ASSERT(0 == uv_tcp_connect(&tcp_check_req, &tcp_check, addr, connect_cb));
+ ASSERT(0 == uv_read_start((uv_stream_t*) &tcp_check, alloc_cb, read_cb));
+
+ /* Close server, so no one will connect to it */
+ uv_close((uv_handle_t*) &tcp_server, close_cb);
+}
+
+static void connection_cb(uv_stream_t* server, int status) {
+ unsigned int i;
+ uv_tcp_t* incoming;
+
+ ASSERT(server == (uv_stream_t*) &tcp_server);
+
+ /* Ignore tcp_check connection */
+ if (got_connections == ARRAY_SIZE(tcp_incoming))
+ return;
+
+ /* Accept everyone */
+ incoming = &tcp_incoming[got_connections++];
+ ASSERT(0 == uv_tcp_init(server->loop, incoming));
+ ASSERT(0 == uv_accept(server, (uv_stream_t*) incoming));
+
+ if (got_connections != ARRAY_SIZE(tcp_incoming))
+ return;
+
+ /* Once all clients are accepted - start reading */
+ for (i = 0; i < ARRAY_SIZE(tcp_incoming); i++) {
+ incoming = &tcp_incoming[i];
+ ASSERT(0 == uv_read_start((uv_stream_t*) incoming, alloc_cb, read_cb));
+ }
+}
+
+TEST_IMPL(tcp_close_accept) {
+ unsigned int i;
+ uv_loop_t* loop;
+ uv_tcp_t* client;
+
+ /*
+ * A little explanation of what goes on below:
+ *
+ * We'll create server and connect to it using two clients, each writing one
+ * byte once connected.
+ *
+ * When all clients will be accepted by server - we'll start reading from them
+ * and, on first client's first byte, will close second client and server.
+ * After that, we'll immediately initiate new connection to server using
+ * tcp_check handle (thus, reusing fd from second client).
+ *
+ * In this situation uv__io_poll()'s event list should still contain read
+ * event for second client, and, if not cleaned up properly, `tcp_check` will
+ * receive stale event of second incoming and invoke `connect_cb` with zero
+ * status.
+ */
+
+ loop = uv_default_loop();
+ addr = uv_ip4_addr("0.0.0.0", TEST_PORT);
+
+ ASSERT(0 == uv_tcp_init(loop, &tcp_server));
+ ASSERT(0 == uv_tcp_bind(&tcp_server, addr));
+ ASSERT(0 == uv_listen((uv_stream_t*) &tcp_server,
+ ARRAY_SIZE(tcp_outgoing),
+ connection_cb));
+
+ for (i = 0; i < ARRAY_SIZE(tcp_outgoing); i++) {
+ client = tcp_outgoing + i;
+
+ ASSERT(0 == uv_tcp_init(loop, client));
+ ASSERT(0 == uv_tcp_connect(&connect_reqs[i], client, addr, connect_cb));
+ }
+
+ uv_run(loop, UV_RUN_DEFAULT);
+
+ ASSERT(ARRAY_SIZE(tcp_outgoing) == got_connections);
+ ASSERT((ARRAY_SIZE(tcp_outgoing) + 2) == close_cb_called);
+ ASSERT(ARRAY_SIZE(tcp_outgoing) == write_cb_called);
+ ASSERT(1 == read_cb_called);
+
+ MAKE_VALGRIND_HAPPY();
+ return 0;
+}
--- /dev/null
+/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include "uv.h"
+#include "task.h"
+
+#include <string.h>
+#include <errno.h>
+
+#if !defined(_WIN32)
+#include <sys/time.h>
+#include <sys/resource.h> /* setrlimit() */
+#endif
+
+/* NOTE: Number should be big enough to trigger this problem */
+static uv_udp_t sockets[2500];
+static uv_udp_send_t reqs[ARRAY_SIZE(sockets)];
+static char buf[1];
+static unsigned int recv_cb_called;
+static unsigned int send_cb_called;
+static unsigned int close_cb_called;
+
+static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) {
+ return uv_buf_init(buf, sizeof(buf));
+}
+
+
+static void recv_cb(uv_udp_t* handle,
+ ssize_t nread,
+ uv_buf_t buf,
+ struct sockaddr* addr,
+ unsigned flags) {
+ recv_cb_called++;
+}
+
+
+static void send_cb(uv_udp_send_t* req, int status) {
+ send_cb_called++;
+}
+
+
+static void close_cb(uv_handle_t* handle) {
+ close_cb_called++;
+}
+
+
+TEST_IMPL(watcher_cross_stop) {
+ uv_loop_t* loop = uv_default_loop();
+ unsigned int i;
+ struct sockaddr_in addr;
+ uv_buf_t buf;
+ char big_string[1024];
+
+#if !defined(_WIN32)
+ {
+ struct rlimit lim;
+ lim.rlim_cur = ARRAY_SIZE(sockets) + 32;
+ lim.rlim_max = ARRAY_SIZE(sockets) + 32;
+ if (setrlimit(RLIMIT_NOFILE, &lim))
+ RETURN_SKIP("File descriptor limit too low.");
+ }
+#endif
+
+ addr = uv_ip4_addr("127.0.0.1", TEST_PORT);
+ memset(big_string, 'A', sizeof(big_string));
+ buf = uv_buf_init(big_string, sizeof(big_string));
+
+ for (i = 0; i < ARRAY_SIZE(sockets); i++) {
+ ASSERT(0 == uv_udp_init(loop, &sockets[i]));
+ ASSERT(0 == uv_udp_bind(&sockets[i], addr, 0));
+ ASSERT(0 == uv_udp_recv_start(&sockets[i], alloc_cb, recv_cb));
+ ASSERT(0 == uv_udp_send(&reqs[i], &sockets[i], &buf, 1, addr, send_cb));
+ }
+
+ while (recv_cb_called == 0)
+ uv_run(loop, UV_RUN_ONCE);
+
+ for (i = 0; i < ARRAY_SIZE(sockets); i++)
+ uv_close((uv_handle_t*) &sockets[i], close_cb);
+
+ ASSERT(0 < recv_cb_called && recv_cb_called <= ARRAY_SIZE(sockets));
+ ASSERT(ARRAY_SIZE(sockets) == send_cb_called);
+
+ uv_run(loop, UV_RUN_DEFAULT);
+
+ ASSERT(ARRAY_SIZE(sockets) == close_cb_called);
+
+ MAKE_VALGRIND_HAPPY();
+ return 0;
+}
'test/test-loop-handles.c',
'test/test-loop-stop.c',
'test/test-walk-handles.c',
+ 'test/test-watcher-cross-stop.c',
'test/test-multiple-listen.c',
'test/test-osx-select.c',
'test/test-pass-always.c',
'test/test-tcp-bind-error.c',
'test/test-tcp-bind6-error.c',
'test/test-tcp-close.c',
+ 'test/test-tcp-close-accept.c',
'test/test-tcp-close-while-connecting.c',
'test/test-tcp-connect-error-after-write.c',
'test/test-tcp-shutdown-after-write.c',
:have_gyp
if not defined PYTHON set PYTHON="python"
-%PYTHON% gyp_uv -Dtarget_arch=%target_arch% -Dlibrary=%library%
+%PYTHON% gyp_uv.py -Dtarget_arch=%target_arch% -Dlibrary=%library%
if errorlevel 1 goto create-msvs-files-failed
if not exist uv.sln goto create-msvs-files-failed
echo Project files generated.