diff --git a/AUTHORS b/AUTHORS index 2b853cacc6..1281159535 100644 --- a/AUTHORS +++ b/AUTHORS @@ -357,3 +357,10 @@ Gil Pedersen Tyler Neylon Golo Roden Ron Korving +Brandon Wilson +Ian Babrou +Bearice Ren +Ankur Oberoi +Atsuya Takagi +Pooya Karimian +Frédéric Germain diff --git a/ChangeLog b/ChangeLog index a26c22f2f4..3d4ba81b9c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,47 @@ -2012.08.28, Version 0.9.1 (Unstable) +2012.09.17, Version 0.9.2 (Unstable) + +* http_parser: upgrade to ad3b631 + +* openssl: upgrade 1.0.1c + +* darwin: use FSEvents to watch directory changes (Fedor Indutny) + +* unix: support missing API on NetBSD (Shigeki Ohtsu) + +* unix: fix EMFILE busy loop (Ben Noordhuis) + +* windows: un-break writable tty handles (Bert Belder) + +* windows: map WSAESHUTDOWN to UV_EPIPE (Bert Belder) + +* windows: make spawn with custom environment work again (Bert Belder) + +* windows: map ERROR_DIRECTORY to UV_ENOENT (Bert Belder) + +* tls, https: validate server certificate by default (Ben Noordhuis) + +* tls, https: throw exception on missing key/cert (Ben Noordhuis) + +* tls: async session storage (Fedor Indutny) + +* installer: don't install header files (Ben Noordhuis) + +* buffer: implement Buffer.prototype.toJSON() (Nathan Rajlich) + +* buffer: added support for writing NaN and Infinity (koichik) + +* http: make http.ServerResponse emit 'end' (Ben Noordhuis) + +* build: ./configure --ninja (Ben Noordhuis, Timothy J Fontaine) + +* installer: fix --without-npm (Ben Noordhuis) + +* cli: make -p equivalent to -pe (Ben Noordhuis) + +* url: Go much faster by using Url class (isaacs) + + +2012.08.28, Version 0.9.1 (Unstable), e6ce259d2caf338fec991c2dd447de763ce99ab7 * buffer: Add Buffer.isEncoding(enc) to test for valid encoding values (isaacs) diff --git a/deps/openssl/openssl.gyp b/deps/openssl/openssl.gyp index 224c73bcb8..2acb0ac826 100644 --- a/deps/openssl/openssl.gyp +++ b/deps/openssl/openssl.gyp @@ -18,6 +18,7 @@ 'OPENSSL_NO_SCTP', 'OPENSSL_NO_SOCK', # Work around brain dead SunOS linker. + 'OPENSSL_NO_RDRAND', 'OPENSSL_NO_GOST', 'OPENSSL_NO_HW_PADLOCK', 'OPENSSL_NO_TTY', @@ -353,7 +354,7 @@ 'openssl/crypto/engine/eng_list.c', 'openssl/crypto/engine/eng_openssl.c', 'openssl/crypto/engine/eng_pkey.c', - 'openssl/crypto/engine/eng_rdrand.c', + #'openssl/crypto/engine/eng_rdrand.c', 'openssl/crypto/engine/eng_rsax.c', 'openssl/crypto/engine/eng_table.c', 'openssl/crypto/engine/tb_asnmth.c', @@ -557,7 +558,6 @@ 'openssl/crypto/sha/sha_one.c', 'openssl/crypto/srp/srp_lib.c', 'openssl/crypto/srp/srp_vfy.c', - 'openssl/crypto/srp/srptest.c', 'openssl/crypto/stack/stack.c', 'openssl/crypto/store/str_err.c', 'openssl/crypto/store/str_lib.c', diff --git a/deps/uv/include/uv-private/uv-darwin.h b/deps/uv/include/uv-private/uv-darwin.h index 397c6a97a4..4d68d01321 100644 --- a/deps/uv/include/uv-private/uv-darwin.h +++ b/deps/uv/include/uv-private/uv-darwin.h @@ -41,6 +41,9 @@ ev_io event_watcher; \ int fflags; \ int fd; \ + char* realpath; \ + int realpath_len; \ + int cf_flags; \ void* cf_eventstream; \ uv_async_t* cf_cb; \ ngx_queue_t cf_events; \ diff --git a/deps/uv/include/uv-private/uv-unix.h b/deps/uv/include/uv-private/uv-unix.h index 91ffbe4c9e..71aee366c0 100644 --- a/deps/uv/include/uv-private/uv-unix.h +++ b/deps/uv/include/uv-private/uv-unix.h @@ -185,8 +185,7 @@ typedef struct { int fd; \ UV_STREAM_PRIVATE_PLATFORM_FIELDS \ -#define UV_TCP_PRIVATE_FIELDS \ - uv_idle_t* idle_handle; /* for UV_TCP_SINGLE_ACCEPT handles */ \ +#define UV_TCP_PRIVATE_FIELDS /* empty */ #define UV_UDP_PRIVATE_FIELDS \ int fd; \ diff --git a/deps/uv/include/uv.h b/deps/uv/include/uv.h index 582e003e95..72da68d68b 100644 --- a/deps/uv/include/uv.h +++ b/deps/uv/include/uv.h @@ -599,6 +599,11 @@ struct uv_tcp_s { UV_EXTERN int uv_tcp_init(uv_loop_t*, uv_tcp_t* handle); +/* + * Opens an existing file descriptor or SOCKET as a tcp handle. + */ +UV_EXTERN int uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock); + /* Enable/disable Nagle's algorithm. */ UV_EXTERN int uv_tcp_nodelay(uv_tcp_t* handle, int enable); @@ -703,6 +708,11 @@ struct uv_udp_send_s { */ UV_EXTERN int uv_udp_init(uv_loop_t*, uv_udp_t* handle); +/* + * Opens an existing file descriptor or SOCKET as a udp handle. + */ +UV_EXTERN int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock); + /* * Bind to a IPv4 address and port. * @@ -940,7 +950,7 @@ UV_EXTERN int uv_pipe_init(uv_loop_t*, uv_pipe_t* handle, int ipc); /* * Opens an existing file descriptor or HANDLE as a pipe. */ -UV_EXTERN void uv_pipe_open(uv_pipe_t*, uv_file file); +UV_EXTERN int uv_pipe_open(uv_pipe_t*, uv_file file); UV_EXTERN int uv_pipe_bind(uv_pipe_t* handle, const char* name); @@ -1665,7 +1675,14 @@ enum uv_fs_event_flags { * regular interval. * This flag is currently not implemented yet on any backend. */ - UV_FS_EVENT_STAT = 2 + UV_FS_EVENT_STAT = 2, + + /* + * By default, event watcher, when watching directory, is not registering + * (is ignoring) changes in it's subdirectories. + * This flag will override this behaviour on platforms that support it. + */ + UV_FS_EVENT_RECURSIVE = 3 }; diff --git a/deps/uv/src/unix/fsevents.c b/deps/uv/src/unix/fsevents.c index 1a7e06e46c..17dd11afa8 100644 --- a/deps/uv/src/unix/fsevents.c +++ b/deps/uv/src/unix/fsevents.c @@ -66,8 +66,13 @@ void uv__fsevents_cb(uv_async_t* cb, int status) { handle = cb->data; UV__FSEVENTS_WALK(handle, { - if (handle->fd != -1) + if (handle->fd != -1) { +#ifdef MAC_OS_X_VERSION_10_7 handle->cb(handle, event->path, event->events, 0); +#else + handle->cb(handle, NULL, event->events, 0); +#endif /* MAC_OS_X_VERSION_10_7 */ + } }) if ((handle->flags & (UV_CLOSING | UV_CLOSED)) == 0 && handle->fd == -1) @@ -84,6 +89,8 @@ void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef, size_t i; int len; char** paths; + char* path; + char* pos; uv_fs_event_t* handle; uv__fsevents_event_t* event; ngx_queue_t add_list; @@ -99,17 +106,50 @@ void uv__fsevents_event_cb(ConstFSEventStreamRef streamRef, kFSEventStreamEventFlagEventIdsWrapped | kFSEventStreamEventFlagHistoryDone | kFSEventStreamEventFlagMount | - kFSEventStreamEventFlagUnmount)) { + kFSEventStreamEventFlagUnmount | + kFSEventStreamEventFlagRootChanged)) { continue; } /* TODO: Report errors */ - len = strlen(paths[i]); + path = paths[i]; + len = strlen(path); + + /* Remove absolute path prefix */ + if (strstr(path, handle->realpath) == path) { + path += handle->realpath_len; + len -= handle->realpath_len; + + /* Skip back slash */ + if (*path != 0) { + path++; + len--; + } + } + +#ifdef MAC_OS_X_VERSION_10_7 + /* Ignore events with path equal to directory itself */ + if (len == 0) + continue; +#endif /* MAC_OS_X_VERSION_10_7 */ + + /* Do not emit events from subdirectories (without option set) */ + pos = strchr(path, '/'); + if ((handle->cf_flags & UV_FS_EVENT_RECURSIVE) == 0 && + pos != NULL && + pos != path + 1) + continue; + +#ifndef MAC_OS_X_VERSION_10_7 + path = ""; + len = 0; +#endif /* MAC_OS_X_VERSION_10_7 */ + event = malloc(sizeof(*event) + len); if (event == NULL) break; - memcpy(event->path, paths[i], len + 1); + memcpy(event->path, path, len + 1); if (eventFlags[i] & kFSEventStreamEventFlagItemModified) event->events = UV_CHANGE; @@ -153,6 +193,11 @@ int uv__fsevents_init(uv_fs_event_t* handle) { ctx.release = NULL; ctx.copyDescription = NULL; + /* Get absolute path to file */ + handle->realpath = realpath(handle->filename, NULL); + if (handle->realpath != NULL) + handle->realpath_len = strlen(handle->realpath); + /* Initialize paths array */ path = CFStringCreateWithCString(NULL, handle->filename, @@ -220,6 +265,9 @@ int uv__fsevents_close(uv_fs_event_t* handle) { uv_mutex_destroy(&handle->cf_mutex); uv_sem_destroy(&handle->cf_sem); + free(handle->realpath); + handle->realpath = NULL; + handle->realpath_len = 0; return 0; } diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c index b79dce3c28..46b9da2a27 100644 --- a/deps/uv/src/unix/kqueue.c +++ b/deps/uv/src/unix/kqueue.c @@ -93,9 +93,6 @@ int uv_fs_event_init(uv_loop_t* loop, struct stat statbuf; #endif /* defined(__APPLE__) */ - /* We don't support any flags yet. */ - assert(!flags); - /* TODO open asynchronously - but how do we report back errors? */ if ((fd = open(filename, O_RDONLY)) == -1) { uv__set_sys_error(loop, errno); @@ -112,6 +109,9 @@ int uv_fs_event_init(uv_loop_t* loop, #if defined(__APPLE__) /* Nullify field to perform checks later */ handle->cf_eventstream = NULL; + handle->realpath = NULL; + handle->realpath_len = 0; + handle->cf_flags = flags; if (fstat(fd, &statbuf)) goto fallback; diff --git a/deps/uv/src/unix/linux/inotify.c b/deps/uv/src/unix/linux/inotify.c index 3c2908832b..97231db9a8 100644 --- a/deps/uv/src/unix/linux/inotify.c +++ b/deps/uv/src/unix/linux/inotify.c @@ -176,9 +176,6 @@ int uv_fs_event_init(uv_loop_t* loop, int events; int wd; - /* We don't support any flags yet. */ - assert(!flags); - if (init_inotify(loop)) return -1; events = UV__IN_ATTRIB diff --git a/deps/uv/src/unix/pipe.c b/deps/uv/src/unix/pipe.c index 30809dc22c..411a6563e6 100644 --- a/deps/uv/src/unix/pipe.c +++ b/deps/uv/src/unix/pipe.c @@ -156,10 +156,10 @@ void uv__pipe_close(uv_pipe_t* handle) { } -void uv_pipe_open(uv_pipe_t* handle, uv_file fd) { - uv__stream_open((uv_stream_t*)handle, - fd, - UV_STREAM_READABLE | UV_STREAM_WRITABLE); +int uv_pipe_open(uv_pipe_t* handle, uv_file fd) { + return uv__stream_open((uv_stream_t*)handle, + fd, + UV_STREAM_READABLE | UV_STREAM_WRITABLE); } diff --git a/deps/uv/src/unix/stream.c b/deps/uv/src/unix/stream.c index 5321d90f4e..b00cfb5b86 100644 --- a/deps/uv/src/unix/stream.c +++ b/deps/uv/src/unix/stream.c @@ -386,16 +386,6 @@ void uv__stream_destroy(uv_stream_t* stream) { } -static void uv__next_accept(uv_idle_t* idle, int status) { - uv_stream_t* stream = idle->data; - - uv_idle_stop(idle); - - if (stream->accepted_fd == -1) - uv__io_start(stream->loop, &stream->read_watcher); -} - - /* Implements a best effort approach to mitigating accept() EMFILE errors. * We have a spare file descriptor stashed away that we close to get below * the EMFILE limit. Next, we accept all pending connections and close them @@ -497,40 +487,17 @@ void uv__server_io(uv_loop_t* loop, uv__io_t* w, int events) { stream->accepted_fd = fd; stream->connection_cb(stream, 0); - if (stream->accepted_fd != -1 || - (stream->type == UV_TCP && stream->flags == UV_TCP_SINGLE_ACCEPT)) { + if (stream->accepted_fd != -1) { /* The user hasn't yet accepted called uv_accept() */ uv__io_stop(loop, &stream->read_watcher); - break; + return; } - } - if (stream->fd != -1 && - stream->accepted_fd == -1 && - (stream->type == UV_TCP && stream->flags == UV_TCP_SINGLE_ACCEPT)) - { - /* Defer the next accept() syscall to the next event loop tick. - * This lets us guarantee fair load balancing in in multi-process setups. - * The problem is as follows: - * - * 1. Multiple processes listen on the same socket. - * 2. The OS scheduler commonly gives preference to one process to - * avoid task switches. - * 3. That process therefore accepts most of the new connections, - * leading to a (sometimes very) unevenly distributed load. - * - * Here is how we mitigate this issue: - * - * 1. Accept a connection. - * 2. Start an idle watcher. - * 3. Don't accept new connections until the idle callback fires. - * - * This works because the callback only fires when there have been - * no recent events, i.e. none of the watched file descriptors have - * recently been readable or writable. - */ - uv_tcp_t* tcp = (uv_tcp_t*) stream; - uv_idle_start(tcp->idle_handle, uv__next_accept); + if (stream->type == UV_TCP && (stream->flags & UV_TCP_SINGLE_ACCEPT)) { + /* Give other processes a chance to accept connections. */ + struct timespec timeout = { 0, 1 }; + nanosleep(&timeout, NULL); + } } } diff --git a/deps/uv/src/unix/sunos.c b/deps/uv/src/unix/sunos.c index 18412b8d0b..bf1352480d 100644 --- a/deps/uv/src/unix/sunos.c +++ b/deps/uv/src/unix/sunos.c @@ -196,8 +196,6 @@ int uv_fs_event_init(uv_loop_t* loop, int portfd; int first_run = 0; - /* We don't support any flags yet. */ - assert(!flags); if (loop->fs_fd == -1) { if ((portfd = port_create()) == -1) { uv__set_sys_error(loop, errno); diff --git a/deps/uv/src/unix/tcp.c b/deps/uv/src/unix/tcp.c index 186150be57..4325c5b120 100644 --- a/deps/uv/src/unix/tcp.c +++ b/deps/uv/src/unix/tcp.c @@ -30,7 +30,6 @@ int uv_tcp_init(uv_loop_t* loop, uv_tcp_t* tcp) { uv__stream_init(loop, (uv_stream_t*)tcp, UV_TCP); - tcp->idle_handle = NULL; return 0; } @@ -153,6 +152,13 @@ int uv__tcp_bind6(uv_tcp_t* handle, struct sockaddr_in6 addr) { } +int uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock) { + return uv__stream_open((uv_stream_t*)handle, + sock, + UV_STREAM_READABLE | UV_STREAM_WRITABLE); +} + + int uv_tcp_getsockname(uv_tcp_t* handle, struct sockaddr* name, int* namelen) { socklen_t socklen; @@ -238,20 +244,9 @@ int uv_tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb) { single_accept = (val == NULL) || (atoi(val) != 0); /* on by default */ } - if (!single_accept) - goto no_single_accept; - - tcp->idle_handle = malloc(sizeof(*tcp->idle_handle)); - if (tcp->idle_handle == NULL) - return uv__set_sys_error(tcp->loop, ENOMEM); + if (single_accept) + tcp->flags |= UV_TCP_SINGLE_ACCEPT; - if (uv_idle_init(tcp->loop, tcp->idle_handle)) - abort(); - tcp->idle_handle->flags |= UV__HANDLE_INTERNAL; - - tcp->flags |= UV_TCP_SINGLE_ACCEPT; - -no_single_accept: if (maybe_new_socket(tcp, AF_INET, UV_STREAM_READABLE)) return -1; @@ -390,8 +385,5 @@ int uv_tcp_simultaneous_accepts(uv_tcp_t* handle, int enable) { void uv__tcp_close(uv_tcp_t* handle) { - if (handle->idle_handle) - uv_close((uv_handle_t*)handle->idle_handle, (uv_close_cb)free); - uv__stream_close((uv_stream_t*)handle); } diff --git a/deps/uv/src/unix/udp.c b/deps/uv/src/unix/udp.c index c496687442..6f5a3f14e7 100644 --- a/deps/uv/src/unix/udp.c +++ b/deps/uv/src/unix/udp.c @@ -316,17 +316,15 @@ static int uv__bind(uv_udp_t* handle, goto out; } - /* Check for already active socket. */ - if (handle->fd != -1) { - uv__set_artificial_error(handle->loop, UV_EALREADY); - goto out; - } - - if ((fd = uv__socket(domain, SOCK_DGRAM, 0)) == -1) { - uv__set_sys_error(handle->loop, errno); - goto out; + if (handle->fd == -1) { + if ((fd = uv__socket(domain, SOCK_DGRAM, 0)) == -1) { + uv__set_sys_error(handle->loop, errno); + goto out; + } + handle->fd = fd; } + fd = handle->fd; yes = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { uv__set_sys_error(handle->loop, errno); @@ -367,12 +365,13 @@ static int uv__bind(uv_udp_t* handle, goto out; } - handle->fd = fd; status = 0; out: - if (status) - close(fd); + if (status) { + close(handle->fd); + handle->fd = -1; + } errno = saved_errno; return status; @@ -486,6 +485,51 @@ int uv__udp_bind6(uv_udp_t* handle, struct sockaddr_in6 addr, unsigned flags) { } +int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { + int saved_errno; + int status; + int yes; + + saved_errno = errno; + status = -1; + + /* Check for already active socket. */ + if (handle->fd != -1) { + uv__set_artificial_error(handle->loop, UV_EALREADY); + goto out; + } + + yes = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) == -1) { + uv__set_sys_error(handle->loop, errno); + 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(sock, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof yes) == -1) { + uv__set_sys_error(handle->loop, errno); + goto out; + } +#endif + + handle->fd = sock; + status = 0; + +out: + errno = saved_errno; + return status; +} + + int uv_udp_set_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, uv_membership membership) { diff --git a/deps/uv/src/win/fs-event.c b/deps/uv/src/win/fs-event.c index c20c07308f..1954cb0916 100644 --- a/deps/uv/src/win/fs-event.c +++ b/deps/uv/src/win/fs-event.c @@ -138,9 +138,6 @@ int uv_fs_event_init(uv_loop_t* loop, uv_fs_event_t* handle, WCHAR* dir = NULL, *dir_to_watch, *filenamew = NULL; WCHAR short_path[MAX_PATH]; - /* We don't support any flags yet. */ - assert(!flags); - uv_fs_event_init_handle(loop, handle, filename, cb); /* Convert name to UTF16. */ diff --git a/deps/uv/src/win/pipe.c b/deps/uv/src/win/pipe.c index 59138224c2..2436b036f9 100644 --- a/deps/uv/src/win/pipe.c +++ b/deps/uv/src/win/pipe.c @@ -1634,12 +1634,13 @@ static void eof_timer_close_cb(uv_handle_t* handle) { } -void uv_pipe_open(uv_pipe_t* pipe, uv_file file) { +int uv_pipe_open(uv_pipe_t* pipe, uv_file file) { HANDLE os_handle = (HANDLE)_get_osfhandle(file); if (os_handle == INVALID_HANDLE_VALUE || uv_set_pipe_handle(pipe->loop, pipe, os_handle, 0) == -1) { - return; + uv__set_sys_error(pipe->loop, WSAEINVAL); + return -1; } uv_pipe_connection_init(pipe); @@ -1651,4 +1652,5 @@ void uv_pipe_open(uv_pipe_t* pipe, uv_file file) { pipe->ipc_pid = uv_parent_pid(); assert(pipe->ipc_pid != -1); } + return 0; } diff --git a/deps/uv/src/win/tcp.c b/deps/uv/src/win/tcp.c index 1be1d186f2..ff7b27b805 100644 --- a/deps/uv/src/win/tcp.c +++ b/deps/uv/src/win/tcp.c @@ -1378,3 +1378,18 @@ void uv_tcp_close(uv_loop_t* loop, uv_tcp_t* tcp) { uv_want_endgame(tcp->loop, (uv_handle_t*)tcp); } } + + +int uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock) { + /* Make the socket non-inheritable */ + if (!SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0)) { + uv__set_sys_error(handle->loop, GetLastError()); + return -1; + } + + if (uv_tcp_set_socket(handle->loop, handle, sock, 0) == -1) { + return -1; + } + + return 0; +} diff --git a/deps/uv/src/win/udp.c b/deps/uv/src/win/udp.c index 2436799b57..56b46239a2 100644 --- a/deps/uv/src/win/udp.c +++ b/deps/uv/src/win/udp.c @@ -653,6 +653,28 @@ int uv_udp_set_broadcast(uv_udp_t* handle, int value) { } +int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock) { + int r; + DWORD yes = 1; + + if (uv_udp_set_socket(handle->loop, handle, sock) == -1) { + return -1; + } + + r = setsockopt(handle->socket, + SOL_SOCKET, + SO_REUSEADDR, + (char*) &yes, + sizeof yes); + if (r == SOCKET_ERROR) { + uv__set_sys_error(handle->loop, WSAGetLastError()); + return -1; + } + + return 0; +} + + #define SOCKOPT_SETTER(name, option4, option6, validate) \ int uv_udp_set_##name(uv_udp_t* handle, int value) { \ DWORD optval = (DWORD) value; \ diff --git a/deps/uv/test/benchmark-list.h b/deps/uv/test/benchmark-list.h index 8b6efeef14..ed6d14158d 100644 --- a/deps/uv/test/benchmark-list.h +++ b/deps/uv/test/benchmark-list.h @@ -33,6 +33,10 @@ BENCHMARK_DECLARE (tcp_pump1_client) BENCHMARK_DECLARE (pipe_pump100_client) BENCHMARK_DECLARE (pipe_pump1_client) +BENCHMARK_DECLARE (tcp_multi_accept2) +BENCHMARK_DECLARE (tcp_multi_accept4) +BENCHMARK_DECLARE (tcp_multi_accept8) + /* Run until X packets have been sent/received. */ BENCHMARK_DECLARE (udp_pummel_1v1) BENCHMARK_DECLARE (udp_pummel_1v10) @@ -112,6 +116,10 @@ TASK_LIST_START BENCHMARK_ENTRY (pipe_pound_1000) BENCHMARK_HELPER (pipe_pound_1000, pipe_echo_server) + BENCHMARK_ENTRY (tcp_multi_accept2) + BENCHMARK_ENTRY (tcp_multi_accept4) + BENCHMARK_ENTRY (tcp_multi_accept8) + BENCHMARK_ENTRY (udp_pummel_1v1) BENCHMARK_ENTRY (udp_pummel_1v10) BENCHMARK_ENTRY (udp_pummel_1v100) diff --git a/deps/uv/test/benchmark-multi-accept.c b/deps/uv/test/benchmark-multi-accept.c new file mode 100644 index 0000000000..ce3f353d24 --- /dev/null +++ b/deps/uv/test/benchmark-multi-accept.c @@ -0,0 +1,436 @@ +/* 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 "task.h" +#include "uv.h" + +#define IPC_PIPE_NAME TEST_PIPENAME +#define NUM_CONNECTS (250 * 1000) + +union stream_handle { + uv_pipe_t pipe; + uv_tcp_t tcp; +}; + +/* Use as (uv_stream_t *) &handle_storage -- it's kind of clunky but it + * avoids aliasing warnings. + */ +typedef unsigned char handle_storage_t[sizeof(union stream_handle)]; + +/* Used for passing around the listen handle, not part of the benchmark proper. + * We have an overabundance of server types here. It works like this: + * + * 1. The main thread starts an IPC pipe server. + * 2. The worker threads connect to the IPC server and obtain a listen handle. + * 3. The worker threads start accepting requests on the listen handle. + * 4. The main thread starts connecting repeatedly. + * + * Step #4 should perhaps be farmed out over several threads. + */ +struct ipc_server_ctx { + handle_storage_t server_handle; + unsigned int num_connects; + uv_pipe_t ipc_pipe; +}; + +struct ipc_peer_ctx { + handle_storage_t peer_handle; + uv_write_t write_req; +}; + +struct ipc_client_ctx { + uv_connect_t connect_req; + uv_stream_t* server_handle; + uv_pipe_t ipc_pipe; + char scratch[16]; +}; + +/* Used in the actual benchmark. */ +struct server_ctx { + handle_storage_t server_handle; + unsigned int num_connects; + uv_async_t async_handle; + uv_thread_t thread_id; + uv_sem_t semaphore; +}; + +struct client_ctx { + handle_storage_t client_handle; + unsigned int num_connects; + uv_connect_t connect_req; + uv_idle_t idle_handle; +}; + +static void ipc_connection_cb(uv_stream_t* ipc_pipe, int status); +static void ipc_write_cb(uv_write_t* req, int status); +static void ipc_close_cb(uv_handle_t* handle); +static void ipc_connect_cb(uv_connect_t* req, int status); +static void ipc_read2_cb(uv_pipe_t* ipc_pipe, + ssize_t nread, + uv_buf_t buf, + uv_handle_type type); +static uv_buf_t ipc_alloc_cb(uv_handle_t* handle, size_t suggested_size); + +static void sv_async_cb(uv_async_t* handle, int status); +static void sv_connection_cb(uv_stream_t* server_handle, int status); +static void sv_read_cb(uv_stream_t* handle, ssize_t nread, uv_buf_t buf); +static uv_buf_t sv_alloc_cb(uv_handle_t* handle, size_t suggested_size); + +static void cl_connect_cb(uv_connect_t* req, int status); +static void cl_idle_cb(uv_idle_t* handle, int status); +static void cl_close_cb(uv_handle_t* handle); + +static struct sockaddr_in listen_addr; + + +static void ipc_connection_cb(uv_stream_t* ipc_pipe, int status) { + struct ipc_server_ctx* sc; + struct ipc_peer_ctx* pc; + uv_loop_t* loop; + uv_buf_t buf; + + loop = ipc_pipe->loop; + buf = uv_buf_init("PING", 4); + sc = container_of(ipc_pipe, struct ipc_server_ctx, ipc_pipe); + pc = calloc(1, sizeof(*pc)); + ASSERT(pc != NULL); + + if (ipc_pipe->type == UV_TCP) + ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) &pc->peer_handle)); + else if (ipc_pipe->type == UV_NAMED_PIPE) + ASSERT(0 == uv_pipe_init(loop, (uv_pipe_t*) &pc->peer_handle, 1)); + else + ASSERT(0); + + ASSERT(0 == uv_accept(ipc_pipe, (uv_stream_t*) &pc->peer_handle)); + ASSERT(0 == uv_write2(&pc->write_req, + (uv_stream_t*) &pc->peer_handle, + &buf, + 1, + (uv_stream_t*) &sc->server_handle, + ipc_write_cb)); + + if (--sc->num_connects == 0) + uv_close((uv_handle_t*) ipc_pipe, NULL); +} + + +static void ipc_write_cb(uv_write_t* req, int status) { + struct ipc_peer_ctx* ctx; + ctx = container_of(req, struct ipc_peer_ctx, write_req); + uv_close((uv_handle_t*) &ctx->peer_handle, ipc_close_cb); +} + + +static void ipc_close_cb(uv_handle_t* handle) { + struct ipc_peer_ctx* ctx; + ctx = container_of(handle, struct ipc_peer_ctx, peer_handle); + free(ctx); +} + + +static void ipc_connect_cb(uv_connect_t* req, int status) { + struct ipc_client_ctx* ctx; + ctx = container_of(req, struct ipc_client_ctx, connect_req); + ASSERT(0 == status); + ASSERT(0 == uv_read2_start((uv_stream_t*) &ctx->ipc_pipe, + ipc_alloc_cb, + ipc_read2_cb)); +} + + +static uv_buf_t ipc_alloc_cb(uv_handle_t* handle, size_t suggested_size) { + struct ipc_client_ctx* ctx; + ctx = container_of(handle, struct ipc_client_ctx, ipc_pipe); + return uv_buf_init(ctx->scratch, sizeof(ctx->scratch)); +} + + +static void ipc_read2_cb(uv_pipe_t* ipc_pipe, + ssize_t nread, + uv_buf_t buf, + uv_handle_type type) { + struct ipc_client_ctx* ctx; + uv_loop_t* loop; + + ctx = container_of(ipc_pipe, struct ipc_client_ctx, ipc_pipe); + loop = ipc_pipe->loop; + + if (type == UV_TCP) + ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) ctx->server_handle)); + else if (type == UV_NAMED_PIPE) + ASSERT(0 == uv_pipe_init(loop, (uv_pipe_t*) ctx->server_handle, 0)); + else + ASSERT(0); + + ASSERT(0 == uv_accept((uv_stream_t*) &ctx->ipc_pipe, ctx->server_handle)); + uv_close((uv_handle_t*) &ctx->ipc_pipe, NULL); +} + + +/* Set up an IPC pipe server that hands out listen sockets to the worker + * threads. It's kind of cumbersome for such a simple operation, maybe we + * should revive uv_import() and uv_export(). + */ +static void send_listen_handles(uv_handle_type type, + unsigned int num_servers, + struct server_ctx* servers) { + struct ipc_server_ctx ctx; + uv_loop_t* loop; + unsigned int i; + + loop = uv_default_loop(); + ctx.num_connects = num_servers; + + if (type == UV_TCP) { + ASSERT(0 == uv_tcp_init(loop, (uv_tcp_t*) &ctx.server_handle)); + ASSERT(0 == uv_tcp_bind((uv_tcp_t*) &ctx.server_handle, listen_addr)); + } + else if (type == UV_NAMED_PIPE) { + ASSERT(0 == uv_pipe_init(loop, (uv_pipe_t*) &ctx.server_handle, 0)); + ASSERT(0 == uv_pipe_bind((uv_pipe_t*) &ctx.server_handle, IPC_PIPE_NAME)); + } + else + ASSERT(0); + + ASSERT(0 == uv_pipe_init(loop, &ctx.ipc_pipe, 1)); + ASSERT(0 == uv_pipe_bind(&ctx.ipc_pipe, IPC_PIPE_NAME)); + ASSERT(0 == uv_listen((uv_stream_t*) &ctx.ipc_pipe, 128, ipc_connection_cb)); + + for (i = 0; i < num_servers; i++) + uv_sem_post(&servers[i].semaphore); + + ASSERT(0 == uv_run(loop)); + uv_close((uv_handle_t*) &ctx.server_handle, NULL); + ASSERT(0 == uv_run(loop)); + + for (i = 0; i < num_servers; i++) + uv_sem_wait(&servers[i].semaphore); +} + + +static void get_listen_handle(uv_loop_t* loop, uv_stream_t* server_handle) { + struct ipc_client_ctx ctx; + + ctx.server_handle = server_handle; + ctx.server_handle->data = "server handle"; + + ASSERT(0 == uv_pipe_init(loop, &ctx.ipc_pipe, 1)); + uv_pipe_connect(&ctx.connect_req, + &ctx.ipc_pipe, + IPC_PIPE_NAME, + ipc_connect_cb); + ASSERT(0 == uv_run(loop)); +} + + +static void server_cb(void *arg) { + struct server_ctx *ctx; + uv_loop_t* loop; + + ctx = arg; + loop = uv_loop_new(); + ASSERT(loop != NULL); + + ASSERT(0 == uv_async_init(loop, &ctx->async_handle, sv_async_cb)); + uv_unref((uv_handle_t*) &ctx->async_handle); + + /* Wait until the main thread is ready. */ + uv_sem_wait(&ctx->semaphore); + get_listen_handle(loop, (uv_stream_t*) &ctx->server_handle); + uv_sem_post(&ctx->semaphore); + + /* Now start the actual benchmark. */ + ASSERT(0 == uv_listen((uv_stream_t*) &ctx->server_handle, + 128, + sv_connection_cb)); + ASSERT(0 == uv_run(loop)); + + uv_loop_delete(loop); +} + + +static void sv_async_cb(uv_async_t* handle, int status) { + struct server_ctx* ctx; + ctx = container_of(handle, struct server_ctx, async_handle); + uv_close((uv_handle_t*) &ctx->server_handle, NULL); + uv_close((uv_handle_t*) &ctx->async_handle, NULL); +} + + +static void sv_connection_cb(uv_stream_t* server_handle, int status) { + handle_storage_t* storage; + struct server_ctx* ctx; + + ctx = container_of(server_handle, struct server_ctx, server_handle); + ASSERT(status == 0); + + storage = malloc(sizeof(*storage)); + ASSERT(storage != NULL); + + if (server_handle->type == UV_TCP) + ASSERT(0 == uv_tcp_init(server_handle->loop, (uv_tcp_t*) storage)); + else if (server_handle->type == UV_NAMED_PIPE) + ASSERT(0 == uv_pipe_init(server_handle->loop, (uv_pipe_t*) storage, 0)); + else + ASSERT(0); + + ASSERT(0 == uv_accept(server_handle, (uv_stream_t*) storage)); + ASSERT(0 == uv_read_start((uv_stream_t*) storage, sv_alloc_cb, sv_read_cb)); + ctx->num_connects++; +} + + +static uv_buf_t sv_alloc_cb(uv_handle_t* handle, size_t suggested_size) { + static char buf[32]; + return uv_buf_init(buf, sizeof(buf)); +} + + +static void sv_read_cb(uv_stream_t* handle, ssize_t nread, uv_buf_t buf) { + ASSERT(nread == -1); + ASSERT(uv_last_error(handle->loop).code == UV_EOF); + uv_close((uv_handle_t*) handle, (uv_close_cb) free); +} + + +static void cl_connect_cb(uv_connect_t* req, int status) { + struct client_ctx* ctx = container_of(req, struct client_ctx, connect_req); + uv_idle_start(&ctx->idle_handle, cl_idle_cb); + ASSERT(0 == status); +} + + +static void cl_idle_cb(uv_idle_t* handle, int status) { + struct client_ctx* ctx = container_of(handle, struct client_ctx, idle_handle); + uv_close((uv_handle_t*) &ctx->client_handle, cl_close_cb); + uv_idle_stop(&ctx->idle_handle); +} + + +static void cl_close_cb(uv_handle_t* handle) { + struct client_ctx* ctx; + + ctx = container_of(handle, struct client_ctx, client_handle); + + if (--ctx->num_connects == 0) { + uv_close((uv_handle_t*) &ctx->idle_handle, NULL); + return; + } + + ASSERT(0 == uv_tcp_init(handle->loop, (uv_tcp_t*) &ctx->client_handle)); + ASSERT(0 == uv_tcp_connect(&ctx->connect_req, + (uv_tcp_t*) &ctx->client_handle, + listen_addr, + cl_connect_cb)); +} + + +static int test_tcp(unsigned int num_servers, unsigned int num_clients) { + struct server_ctx* servers; + struct client_ctx* clients; + uv_loop_t* loop; + uv_tcp_t* handle; + unsigned int i; + double time; + + listen_addr = uv_ip4_addr("127.0.0.1", TEST_PORT); + loop = uv_default_loop(); + + servers = calloc(num_servers, sizeof(servers[0])); + clients = calloc(num_clients, sizeof(clients[0])); + ASSERT(servers != NULL); + ASSERT(clients != NULL); + + /* We're making the assumption here that from the perspective of the + * OS scheduler, threads are functionally equivalent to and interchangeable + * with full-blown processes. + */ + for (i = 0; i < num_servers; i++) { + struct server_ctx* ctx = servers + i; + ASSERT(0 == uv_sem_init(&ctx->semaphore, 0)); + ASSERT(0 == uv_thread_create(&ctx->thread_id, server_cb, ctx)); + } + + send_listen_handles(UV_TCP, num_servers, servers); + + for (i = 0; i < num_clients; i++) { + struct client_ctx* ctx = clients + i; + ctx->num_connects = NUM_CONNECTS / num_clients; + handle = (uv_tcp_t*) &ctx->client_handle; + handle->data = "client handle"; + ASSERT(0 == uv_tcp_init(loop, handle)); + ASSERT(0 == uv_tcp_connect(&ctx->connect_req, + handle, + listen_addr, + cl_connect_cb)); + ASSERT(0 == uv_idle_init(loop, &ctx->idle_handle)); + } + + { + uint64_t t = uv_hrtime(); + ASSERT(0 == uv_run(loop)); + t = uv_hrtime() - t; + time = t / 1e9; + } + + for (i = 0; i < num_servers; i++) { + struct server_ctx* ctx = servers + i; + uv_async_send(&ctx->async_handle); + ASSERT(0 == uv_thread_join(&ctx->thread_id)); + uv_sem_destroy(&ctx->semaphore); + } + + printf("accept%u: %.0f accepts/sec (%u total)\n", + num_servers, + NUM_CONNECTS / time, + NUM_CONNECTS); + + for (i = 0; i < num_servers; i++) { + struct server_ctx* ctx = servers + i; + printf(" thread #%u: %.0f accepts/sec (%u total, %.1f%%)\n", + i, + ctx->num_connects / time, + ctx->num_connects, + ctx->num_connects * 100.0 / NUM_CONNECTS); + } + + free(clients); + free(servers); + uv_loop_delete(uv_default_loop()); /* Silence valgrind. */ + + return 0; +} + + +BENCHMARK_IMPL(tcp_multi_accept2) { + return test_tcp(2, 40); +} + + +BENCHMARK_IMPL(tcp_multi_accept4) { + return test_tcp(4, 40); +} + + +BENCHMARK_IMPL(tcp_multi_accept8) { + return test_tcp(8, 40); +} diff --git a/deps/uv/test/dns-server.c b/deps/uv/test/dns-server.c index d885f4c86a..e541e781c6 100644 --- a/deps/uv/test/dns-server.c +++ b/deps/uv/test/dns-server.c @@ -153,7 +153,6 @@ static void process_req(uv_stream_t* handle, ssize_t nread, uv_buf_t buf) { hdrbuf_remaining = DNSREC_LEN - readbuf_remaining; break; } else { - short int reclen_n; /* save header */ memcpy(&hdrbuf[DNSREC_LEN - hdrbuf_remaining], dnsreq, hdrbuf_remaining); dnsreq += hdrbuf_remaining; @@ -161,8 +160,8 @@ static void process_req(uv_stream_t* handle, ssize_t nread, uv_buf_t buf) { hdrbuf_remaining = 0; /* get record length */ - reclen_n = *((short int*)hdrbuf); - rec_remaining = ntohs(reclen_n) - (DNSREC_LEN - 2); + rec_remaining = (unsigned) hdrbuf[0] * 256 + (unsigned) hdrbuf[1]; + rec_remaining -= (DNSREC_LEN - 2); } } diff --git a/deps/uv/test/test-fs-event.c b/deps/uv/test/test-fs-event.c index 257d64ca54..4d4cfbc31b 100644 --- a/deps/uv/test/test-fs-event.c +++ b/deps/uv/test/test-fs-event.c @@ -98,8 +98,7 @@ static void fs_event_cb_dir(uv_fs_event_t* handle, const char* filename, ASSERT(handle == &fs_event); ASSERT(status == 0); ASSERT(events == UV_RENAME); - ASSERT(filename == NULL || strcmp(filename, "file1") == 0 || - strstr(filename, "watch_dir") != NULL); + ASSERT(filename == NULL || strcmp(filename, "file1") == 0); uv_close((uv_handle_t*)handle, close_cb); } diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h index 1423f806a6..ce70c1f915 100644 --- a/deps/uv/test/test-list.h +++ b/deps/uv/test/test-list.h @@ -40,6 +40,7 @@ TEST_DECLARE (pipe_ping_pong) TEST_DECLARE (delayed_accept) TEST_DECLARE (multiple_listen) TEST_DECLARE (tcp_writealot) +TEST_DECLARE (tcp_open) TEST_DECLARE (tcp_connect_error_after_write) TEST_DECLARE (tcp_shutdown_after_write) TEST_DECLARE (tcp_bind_error_addrinuse) @@ -69,6 +70,7 @@ TEST_DECLARE (udp_dgram_too_big) TEST_DECLARE (udp_dual_stack) TEST_DECLARE (udp_ipv6_only) TEST_DECLARE (udp_options) +TEST_DECLARE (udp_open) TEST_DECLARE (pipe_bind_error_addrinuse) TEST_DECLARE (pipe_bind_error_addrnotavail) TEST_DECLARE (pipe_bind_error_inval) @@ -237,6 +239,9 @@ TASK_LIST_START TEST_ENTRY (tcp_writealot) TEST_HELPER (tcp_writealot, tcp4_echo_server) + TEST_ENTRY (tcp_open) + TEST_HELPER (tcp_open, tcp4_echo_server) + TEST_ENTRY (tcp_shutdown_after_write) TEST_HELPER (tcp_shutdown_after_write, tcp4_echo_server) @@ -271,6 +276,9 @@ TASK_LIST_START TEST_ENTRY (udp_multicast_join) TEST_ENTRY (udp_multicast_ttl) + TEST_ENTRY (udp_open) + TEST_HELPER (udp_open, udp4_echo_server) + TEST_ENTRY (pipe_bind_error_addrinuse) TEST_ENTRY (pipe_bind_error_addrnotavail) TEST_ENTRY (pipe_bind_error_inval) diff --git a/deps/uv/test/test-tcp-open.c b/deps/uv/test/test-tcp-open.c new file mode 100644 index 0000000000..48188d2458 --- /dev/null +++ b/deps/uv/test/test-tcp-open.c @@ -0,0 +1,175 @@ +/* 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 +#include +#include + +#ifndef _WIN32 +# include +#endif + +static int shutdown_cb_called = 0; +static int connect_cb_called = 0; +static int write_cb_called = 0; +static int close_cb_called = 0; + +static uv_connect_t connect_req; +static uv_shutdown_t shutdown_req; +static uv_write_t write_req; + + +static void startup(void) { +#ifdef _WIN32 + struct WSAData wsa_data; + int r = WSAStartup(MAKEWORD(2, 2), &wsa_data); + ASSERT(r == 0); +#endif +} + + +static uv_os_sock_t create_tcp_socket(void) { + uv_os_sock_t sock; + int r; + + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); +#ifdef _WIN32 + ASSERT(sock != INVALID_SOCKET); +#else + ASSERT(sock >= 0); +#endif + +#ifndef _WIN32 + { + /* Allow reuse of the port. */ + int yes = 1; + r = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + ASSERT(r == 0); + } +#endif + + return sock; +} + + +static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) { + static char slab[65536]; + ASSERT(suggested_size <= sizeof slab); + return uv_buf_init(slab, sizeof slab); +} + + +static void close_cb(uv_handle_t* handle) { + ASSERT(handle != NULL); + close_cb_called++; +} + + +static void shutdown_cb(uv_shutdown_t* req, int status) { + ASSERT(req == &shutdown_req); + ASSERT(status == 0); + + /* Now we wait for the EOF */ + shutdown_cb_called++; +} + + +static void read_cb(uv_stream_t* tcp, ssize_t nread, uv_buf_t buf) { + ASSERT(tcp != NULL); + + if (nread >= 0) { + ASSERT(nread == 4); + ASSERT(memcmp("PING", buf.base, nread) == 0); + } + else { + ASSERT(uv_last_error(uv_default_loop()).code == UV_EOF); + printf("GOT EOF\n"); + uv_close((uv_handle_t*)tcp, close_cb); + } +} + + +static void write_cb(uv_write_t* req, int status) { + ASSERT(req != NULL); + + if (status) { + uv_err_t err = uv_last_error(uv_default_loop()); + fprintf(stderr, "uv_write error: %s\n", uv_strerror(err)); + ASSERT(0); + } + + write_cb_called++; +} + + +static void connect_cb(uv_connect_t* req, int status) { + uv_buf_t buf = uv_buf_init("PING", 4); + uv_stream_t* stream; + int r; + + ASSERT(req == &connect_req); + ASSERT(status == 0); + + stream = req->handle; + connect_cb_called++; + + r = uv_write(&write_req, stream, &buf, 1, write_cb); + ASSERT(r == 0); + + /* Shutdown on drain. */ + r = uv_shutdown(&shutdown_req, stream, shutdown_cb); + ASSERT(r == 0); + + /* Start reading */ + r = uv_read_start(stream, alloc_cb, read_cb); + ASSERT(r == 0); +} + + +TEST_IMPL(tcp_open) { + struct sockaddr_in addr = uv_ip4_addr("127.0.0.1", TEST_PORT); + uv_tcp_t client; + uv_os_sock_t sock; + int r; + + startup(); + sock = create_tcp_socket(); + + r = uv_tcp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + r = uv_tcp_open(&client, sock); + ASSERT(r == 0); + + r = uv_tcp_connect(&connect_req, &client, addr, connect_cb); + ASSERT(r == 0); + + uv_run(uv_default_loop()); + + ASSERT(shutdown_cb_called == 1); + ASSERT(connect_cb_called == 1); + ASSERT(write_cb_called == 1); + ASSERT(close_cb_called == 1); + + return 0; +} diff --git a/deps/uv/test/test-udp-open.c b/deps/uv/test/test-udp-open.c new file mode 100644 index 0000000000..9168549e66 --- /dev/null +++ b/deps/uv/test/test-udp-open.c @@ -0,0 +1,154 @@ +/* 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 +#include +#include + +#ifndef _WIN32 +# include +#endif + +static int send_cb_called = 0; +static int close_cb_called = 0; + +static uv_udp_send_t send_req; + + +static void startup(void) { +#ifdef _WIN32 + struct WSAData wsa_data; + int r = WSAStartup(MAKEWORD(2, 2), &wsa_data); + ASSERT(r == 0); +#endif +} + + +static uv_os_sock_t create_udp_socket(void) { + uv_os_sock_t sock; + int r; + + sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); +#ifdef _WIN32 + ASSERT(sock != INVALID_SOCKET); +#else + ASSERT(sock >= 0); +#endif + +#ifndef _WIN32 + { + /* Allow reuse of the port. */ + int yes = 1; + r = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + ASSERT(r == 0); + } +#endif + + return sock; +} + + +static uv_buf_t alloc_cb(uv_handle_t* handle, size_t suggested_size) { + static char slab[65536]; + ASSERT(suggested_size <= sizeof slab); + return uv_buf_init(slab, sizeof slab); +} + + +static void close_cb(uv_handle_t* handle) { + ASSERT(handle != NULL); + close_cb_called++; +} + + +static void recv_cb(uv_udp_t* handle, + ssize_t nread, + uv_buf_t buf, + struct sockaddr* addr, + unsigned flags) { + int r; + + if (nread < 0) { + ASSERT(0 && "unexpected error"); + } + + if (nread == 0) { + /* Returning unused buffer */ + /* Don't count towards sv_recv_cb_called */ + ASSERT(addr == NULL); + return; + } + + ASSERT(flags == 0); + + ASSERT(addr != NULL); + ASSERT(nread == 4); + ASSERT(memcmp("PING", buf.base, nread) == 0); + + r = uv_udp_recv_stop(handle); + ASSERT(r == 0); + + uv_close((uv_handle_t*) handle, close_cb); +} + + +static void send_cb(uv_udp_send_t* req, int status) { + ASSERT(req != NULL); + ASSERT(status == 0); + + send_cb_called++; +} + + +TEST_IMPL(udp_open) { + struct sockaddr_in addr = uv_ip4_addr("127.0.0.1", TEST_PORT); + uv_buf_t buf = uv_buf_init("PING", 4); + uv_udp_t client; + uv_os_sock_t sock; + int r; + + startup(); + sock = create_udp_socket(); + + r = uv_udp_init(uv_default_loop(), &client); + ASSERT(r == 0); + + r = uv_udp_open(&client, sock); + ASSERT(r == 0); + + r = uv_udp_bind(&client, addr, 0); + ASSERT(r == 0); + + r = uv_udp_recv_start(&client, alloc_cb, recv_cb); + ASSERT(r == 0); + + r = uv_udp_send(&send_req, &client, &buf, 1, addr, send_cb); + ASSERT(r == 0); + + uv_run(uv_default_loop()); + + ASSERT(send_cb_called == 1); + ASSERT(close_cb_called == 1); + + return 0; +} diff --git a/deps/uv/uv.gyp b/deps/uv/uv.gyp index a5dbf153db..a7993e1d69 100644 --- a/deps/uv/uv.gyp +++ b/deps/uv/uv.gyp @@ -305,6 +305,7 @@ 'test/test-tcp-connect-error.c', 'test/test-tcp-connect-timeout.c', 'test/test-tcp-connect6-error.c', + 'test/test-tcp-open.c', 'test/test-tcp-write-error.c', 'test/test-tcp-write-to-half-open-connection.c', 'test/test-tcp-writealot.c', @@ -318,6 +319,7 @@ 'test/test-tty.c', 'test/test-udp-dgram-too-big.c', 'test/test-udp-ipv6.c', + 'test/test-udp-open.c', 'test/test-udp-options.c', 'test/test-udp-send-and-recv.c', 'test/test-udp-multicast-join.c', @@ -370,6 +372,7 @@ 'test/benchmark-list.h', 'test/benchmark-loop-count.c', 'test/benchmark-million-timers.c', + 'test/benchmark-multi-accept.c', 'test/benchmark-ping-pongs.c', 'test/benchmark-pound.c', 'test/benchmark-pump.c', diff --git a/lib/url.js b/lib/url.js index 50eb8b20f6..980a9bb84a 100644 --- a/lib/url.js +++ b/lib/url.js @@ -26,6 +26,22 @@ exports.resolve = urlResolve; exports.resolveObject = urlResolveObject; exports.format = urlFormat; +exports.Url = Url; + +function Url() { + this.protocol = null; + this.slashes = null; + this.auth = null; + this.host = null; + this.port = null; + this.hostname = null; + this.hash = null; + this.search = null; + this.query = null; + this.pathname = null; + this.path = null; +} + // Reference: RFC 3986, RFC 1808, RFC 2396 // define these here so at least they only have to be @@ -90,14 +106,19 @@ var protocolPattern = /^([a-z0-9.+-]+:)/i, querystring = require('querystring'); function urlParse(url, parseQueryString, slashesDenoteHost) { - if (url && typeof(url) === 'object' && url.href) return url; + if (url && typeof(url) === 'object' && url instanceof Url) return url; + + var u = new Url; + u.parse(url, parseQueryString, slashesDenoteHost); + return u; +} +Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { if (typeof url !== 'string') { throw new TypeError("Parameter 'url' must be a string, not " + typeof url); } - var out = {}, - rest = url; + var rest = url; // trim before proceeding. // This is to support parse stuff like " http://foo.com \n" @@ -107,7 +128,7 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { if (proto) { proto = proto[0]; var lowerProto = proto.toLowerCase(); - out.protocol = lowerProto; + this.protocol = lowerProto; rest = rest.substr(proto.length); } @@ -119,7 +140,7 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { var slashes = rest.substr(0, 2) === '//'; if (slashes && !(proto && hostlessProtocol[proto])) { rest = rest.substr(2); - out.slashes = true; + this.slashes = true; } } @@ -149,7 +170,7 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { if (hasAuth) { // pluck off the auth portion. - out.auth = decodeURIComponent(auth); + this.auth = decodeURIComponent(auth); rest = rest.substr(atSign + 1); } } @@ -162,35 +183,28 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { } if (firstNonHost !== -1) { - out.host = rest.substr(0, firstNonHost); + this.host = rest.substr(0, firstNonHost); rest = rest.substr(firstNonHost); } else { - out.host = rest; + this.host = rest; rest = ''; } // pull out port. - var p = parseHost(out.host); - var keys = Object.keys(p); - for (var i = 0, l = keys.length; i < l; i++) { - var key = keys[i]; - out[key] = p[key]; - } + this.parseHost(); // we've indicated that there is a hostname, // so even if it's empty, it has to be present. - out.hostname = out.hostname || ''; + this.hostname = this.hostname || ''; // if hostname begins with [ and ends with ] // assume that it's an IPv6 address. - var ipv6Hostname = out.hostname[0] === '[' && - out.hostname[out.hostname.length - 1] === ']'; + var ipv6Hostname = this.hostname[0] === '[' && + this.hostname[this.hostname.length - 1] === ']'; // validate a little. - if (out.hostname.length > hostnameMaxLen) { - out.hostname = ''; - } else if (!ipv6Hostname) { - var hostparts = out.hostname.split(/\./); + if (!ipv6Hostname) { + var hostparts = this.hostname.split(/\./); for (var i = 0, l = hostparts.length; i < l; i++) { var part = hostparts[i]; if (!part) continue; @@ -218,38 +232,44 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { if (notHost.length) { rest = '/' + notHost.join('.') + rest; } - out.hostname = validParts.join('.'); + this.hostname = validParts.join('.'); break; } } } } - // hostnames are always lower case. - out.hostname = out.hostname.toLowerCase(); + if (this.hostname.length > hostnameMaxLen) { + this.hostname = ''; + } else { + // hostnames are always lower case. + this.hostname = this.hostname.toLowerCase(); + } if (!ipv6Hostname) { // IDNA Support: Returns a puny coded representation of "domain". // It only converts the part of the domain name that // has non ASCII characters. I.e. it dosent matter if // you call it with a domain that already is in ASCII. - var domainArray = out.hostname.split('.'); + var domainArray = this.hostname.split('.'); var newOut = []; for (var i = 0; i < domainArray.length; ++i) { var s = domainArray[i]; newOut.push(s.match(/[^A-Za-z0-9_-]/) ? 'xn--' + punycode.encode(s) : s); } - out.hostname = newOut.join('.'); + this.hostname = newOut.join('.'); } - out.host = (out.hostname || '') + - ((out.port) ? ':' + out.port : ''); - out.href += out.host; + var p = this.port ? ':' + this.port : ''; + var h = this.hostname || ''; + this.host = h + p; + this.href += this.host; // strip [ and ] from the hostname + // the host field still retains them, though if (ipv6Hostname) { - out.hostname = out.hostname.substr(1, out.hostname.length - 2); + this.hostname = this.hostname.substr(1, this.hostname.length - 2); if (rest[0] !== '/') { rest = '/' + rest; } @@ -278,38 +298,39 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { var hash = rest.indexOf('#'); if (hash !== -1) { // got a fragment string. - out.hash = rest.substr(hash); + this.hash = rest.substr(hash); rest = rest.slice(0, hash); } var qm = rest.indexOf('?'); if (qm !== -1) { - out.search = rest.substr(qm); - out.query = rest.substr(qm + 1); + this.search = rest.substr(qm); + this.query = rest.substr(qm + 1); if (parseQueryString) { - out.query = querystring.parse(out.query); + this.query = querystring.parse(this.query); } rest = rest.slice(0, qm); } else if (parseQueryString) { // no query string, but parseQueryString still requested - out.search = ''; - out.query = {}; + this.search = ''; + this.query = {}; } - if (rest) out.pathname = rest; + if (rest) this.pathname = rest; if (slashedProtocol[proto] && - out.hostname && !out.pathname) { - out.pathname = '/'; + this.hostname && !this.pathname) { + this.pathname = '/'; } //to support http.request - if (out.pathname || out.search) { - out.path = (out.pathname ? out.pathname : '') + - (out.search ? out.search : ''); + if (this.pathname || this.search) { + var p = this.pathname || ''; + var s = this.search || ''; + this.path = p + s; } // finally, reconstruct the href based on what has been validated. - out.href = urlFormat(out); - return out; -} + this.href = this.format(); + return this; +}; // format a parsed object into a url string function urlFormat(obj) { @@ -318,43 +339,47 @@ function urlFormat(obj) { // this way, you can call url_format() on strings // to clean up potentially wonky urls. if (typeof(obj) === 'string') obj = urlParse(obj); + if (!(obj instanceof Url)) return Url.prototype.format.call(obj); + return obj.format(); +} - var auth = obj.auth || ''; +Url.prototype.format = function() { + var auth = this.auth || ''; if (auth) { auth = encodeURIComponent(auth); auth = auth.replace(/%3A/i, ':'); auth += '@'; } - var protocol = obj.protocol || '', - pathname = obj.pathname || '', - hash = obj.hash || '', + var protocol = this.protocol || '', + pathname = this.pathname || '', + hash = this.hash || '', host = false, query = ''; - if (obj.host !== undefined) { - host = auth + obj.host; - } else if (obj.hostname !== undefined) { - host = auth + (obj.hostname.indexOf(':') === -1 ? - obj.hostname : - '[' + obj.hostname + ']'); - if (obj.port) { - host += ':' + obj.port; + if (this.host) { + host = auth + this.host; + } else if (this.hostname) { + host = auth + (this.hostname.indexOf(':') === -1 ? + this.hostname : + '[' + this.hostname + ']'); + if (this.port) { + host += ':' + this.port; } } - if (obj.query && typeof obj.query === 'object' && - Object.keys(obj.query).length) { - query = querystring.stringify(obj.query); + if (this.query && typeof this.query === 'object' && + Object.keys(this.query).length) { + query = querystring.stringify(this.query); } - var search = obj.search || (query && ('?' + query)) || ''; + var search = this.search || (query && ('?' + query)) || ''; if (protocol && protocol.substr(-1) !== ':') protocol += ':'; // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. // unless they had them to begin with. - if (obj.slashes || + if (this.slashes || (!protocol || slashedProtocol[protocol]) && host !== false) { host = '//' + (host || ''); if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; @@ -366,39 +391,62 @@ function urlFormat(obj) { if (search && search.charAt(0) !== '?') search = '?' + search; return protocol + host + pathname + search + hash; -} +}; function urlResolve(source, relative) { - return urlFormat(urlResolveObject(source, relative)); + return urlParse(source, false, true).resolve(relative); } +Url.prototype.resolve = function(relative) { + return this.resolveObject(urlParse(relative, false, true)).format(); +}; + function urlResolveObject(source, relative) { if (!source) return relative; + return urlParse(source, false, true).resolveObject(relative); +} + +Url.prototype.resolveObject = function(relative) { + if (typeof relative === 'string') { + var rel = new Url(); + rel.parse(relative, false, true); + relative = rel; + } - source = urlParse(urlFormat(source), false, true); - relative = urlParse(urlFormat(relative), false, true); + var result = new Url(); + Object.keys(this).forEach(function(k) { + result[k] = this[k]; + }, this); // hash is always overridden, no matter what. - source.hash = relative.hash; + // even href="" will remove it. + result.hash = relative.hash; + // if the relative url is empty, then there's nothing left to do here. if (relative.href === '') { - source.href = urlFormat(source); - return source; + result.href = result.format(); + return result; } // hrefs like //foo/bar always cut to the protocol. if (relative.slashes && !relative.protocol) { - relative.protocol = source.protocol; + // take everything except the protocol from relative + Object.keys(relative).forEach(function(k) { + if (k !== 'protocol') + result[k] = relative[k]; + }); + //urlParse appends trailing / to urls like http://www.example.com - if (slashedProtocol[relative.protocol] && - relative.hostname && !relative.pathname) { - relative.path = relative.pathname = '/'; + if (slashedProtocol[result.protocol] && + result.hostname && !result.pathname) { + result.path = result.pathname = '/'; } - relative.href = urlFormat(relative); - return relative; + + result.href = result.format(); + return result; } - if (relative.protocol && relative.protocol !== source.protocol) { + if (relative.protocol && relative.protocol !== result.protocol) { // if it's a known url protocol, then changing // the protocol does weird things // first, if it's not file:, then we MUST have a host, @@ -408,10 +456,14 @@ function urlResolveObject(source, relative) { // because that's known to be hostless. // anything else is assumed to be absolute. if (!slashedProtocol[relative.protocol]) { - relative.href = urlFormat(relative); - return relative; + Object.keys(relative).forEach(function(k) { + result[k] = relative[k]; + }); + result.href = result.format(); + return result; } - source.protocol = relative.protocol; + + result.protocol = relative.protocol; if (!relative.host && !hostlessProtocol[relative.protocol]) { var relPath = (relative.pathname || '').split('/'); while (relPath.length && !(relative.host = relPath.shift())); @@ -419,72 +471,72 @@ function urlResolveObject(source, relative) { if (!relative.hostname) relative.hostname = ''; if (relPath[0] !== '') relPath.unshift(''); if (relPath.length < 2) relPath.unshift(''); - relative.pathname = relPath.join('/'); + result.pathname = relPath.join('/'); + } else { + result.pathname = relative.pathname; } - source.pathname = relative.pathname; - source.search = relative.search; - source.query = relative.query; - source.host = relative.host || ''; - source.auth = relative.auth; - source.hostname = relative.hostname || relative.host; - source.port = relative.port; - //to support http.request - if (source.pathname !== undefined || source.search !== undefined) { - source.path = (source.pathname ? source.pathname : '') + - (source.search ? source.search : ''); + result.search = relative.search; + result.query = relative.query; + result.host = relative.host || ''; + result.auth = relative.auth; + result.hostname = relative.hostname || relative.host; + result.port = relative.port; + // to support http.request + if (result.pathname || result.search) { + var p = result.pathname || ''; + var s = result.search || ''; + result.path = p + s; } - source.slashes = source.slashes || relative.slashes; - source.href = urlFormat(source); - return source; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; } - var isSourceAbs = (source.pathname && source.pathname.charAt(0) === '/'), + var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), isRelAbs = ( - relative.host !== undefined || + relative.host || relative.pathname && relative.pathname.charAt(0) === '/' ), mustEndAbs = (isRelAbs || isSourceAbs || - (source.host && relative.pathname)), + (result.host && relative.pathname)), removeAllDots = mustEndAbs, - srcPath = source.pathname && source.pathname.split('/') || [], + srcPath = result.pathname && result.pathname.split('/') || [], relPath = relative.pathname && relative.pathname.split('/') || [], - psychotic = source.protocol && - !slashedProtocol[source.protocol]; + psychotic = result.protocol && !slashedProtocol[result.protocol]; // if the url is a non-slashed url, then relative // links like ../.. should be able // to crawl up to the hostname, as well. This is strange. - // source.protocol has already been set by now. + // result.protocol has already been set by now. // Later on, put the first path part into the host field. if (psychotic) { - - delete source.hostname; - delete source.port; - if (source.host) { - if (srcPath[0] === '') srcPath[0] = source.host; - else srcPath.unshift(source.host); + result.hostname = ''; + result.port = null; + if (result.host) { + if (srcPath[0] === '') srcPath[0] = result.host; + else srcPath.unshift(result.host); } - delete source.host; + result.host = ''; if (relative.protocol) { - delete relative.hostname; - delete relative.port; + relative.hostname = null; + relative.port = null; if (relative.host) { if (relPath[0] === '') relPath[0] = relative.host; else relPath.unshift(relative.host); } - delete relative.host; + relative.host = null; } mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); } if (isRelAbs) { // it's absolute. - source.host = (relative.host || relative.host === '') ? - relative.host : source.host; - source.hostname = (relative.hostname || relative.hostname === '') ? - relative.hostname : source.hostname; - source.search = relative.search; - source.query = relative.query; + result.host = (relative.host || relative.host === '') ? + relative.host : result.host; + result.hostname = (relative.hostname || relative.hostname === '') ? + relative.hostname : result.hostname; + result.search = relative.search; + result.query = relative.query; srcPath = relPath; // fall through to the dot-handling below. } else if (relPath.length) { @@ -493,53 +545,55 @@ function urlResolveObject(source, relative) { if (!srcPath) srcPath = []; srcPath.pop(); srcPath = srcPath.concat(relPath); - source.search = relative.search; - source.query = relative.query; - } else if ('search' in relative) { + result.search = relative.search; + result.query = relative.query; + } else if (relative.search !== null && relative.search !== undefined) { // just pull out the search. // like href='?foo'. // Put this after the other two cases because it simplifies the booleans if (psychotic) { - source.hostname = source.host = srcPath.shift(); + result.hostname = result.host = srcPath.shift(); //occationaly the auth can get stuck only in host //this especialy happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = source.host && source.host.indexOf('@') > 0 ? - source.host.split('@') : false; + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; if (authInHost) { - source.auth = authInHost.shift(); - source.host = source.hostname = authInHost.shift(); + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); } } - source.search = relative.search; - source.query = relative.query; + result.search = relative.search; + result.query = relative.query; //to support http.request - if (source.pathname !== undefined || source.search !== undefined) { - source.path = (source.pathname ? source.pathname : '') + - (source.search ? source.search : ''); + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); } - source.href = urlFormat(source); - return source; + result.href = result.format(); + return result; } + if (!srcPath.length) { // no path at all. easy. // we've already handled the other stuff above. - delete source.pathname; + result.pathname = null; //to support http.request - if (!source.search) { - source.path = '/' + source.search; + if (result.search) { + result.path = '/' + result.search; } else { - delete source.path; + result.path = null; } - source.href = urlFormat(source); - return source; + result.href = result.format(); + return result; } + // if a url ENDs in . or .., then it must get a trailing slash. // however, if it ends in anything else non-slashy, // then it must NOT get a trailing slash. var last = srcPath.slice(-1)[0]; var hasTrailingSlash = ( - (source.host || relative.host) && (last === '.' || last === '..') || + (result.host || relative.host) && (last === '.' || last === '..') || last === ''); // strip single dots, resolve double dots to parent dir @@ -579,47 +633,52 @@ function urlResolveObject(source, relative) { // put the host back if (psychotic) { - source.hostname = source.host = isAbsolute ? '' : + result.hostname = result.host = isAbsolute ? '' : srcPath.length ? srcPath.shift() : ''; //occationaly the auth can get stuck only in host //this especialy happens in cases like //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = source.host && source.host.indexOf('@') > 0 ? - source.host.split('@') : false; + var authInHost = result.host && result.host.indexOf('@') > 0 ? + result.host.split('@') : false; if (authInHost) { - source.auth = authInHost.shift(); - source.host = source.hostname = authInHost.shift(); + result.auth = authInHost.shift(); + result.host = result.hostname = authInHost.shift(); } } - mustEndAbs = mustEndAbs || (source.host && srcPath.length); + mustEndAbs = mustEndAbs || (result.host && srcPath.length); if (mustEndAbs && !isAbsolute) { srcPath.unshift(''); } - source.pathname = srcPath.join('/'); - //to support request.http - if (source.pathname !== undefined || source.search !== undefined) { - source.path = (source.pathname ? source.pathname : '') + - (source.search ? source.search : ''); + if (!srcPath.length) { + result.pathname = null; + result.path = null; + } else { + result.pathname = srcPath.join('/'); } - source.auth = relative.auth || source.auth; - source.slashes = source.slashes || relative.slashes; - source.href = urlFormat(source); - return source; -} -function parseHost(host) { - var out = {}; + //to support request.http + if (result.pathname !== null || result.search !== null) { + result.path = (result.pathname ? result.pathname : '') + + (result.search ? result.search : ''); + } + result.auth = relative.auth || result.auth; + result.slashes = result.slashes || relative.slashes; + result.href = result.format(); + return result; +}; + +Url.prototype.parseHost = function() { + var host = this.host; var port = portPattern.exec(host); if (port) { port = port[0]; if (port !== ':') { - out.port = port.substr(1); + this.port = port.substr(1); } host = host.substr(0, host.length - port.length); } - if (host) out.hostname = host; - return out; -} + if (host) this.hostname = host; +}; diff --git a/src/node_version.h b/src/node_version.h index 42dbea45e8..ef5f0b23e5 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -25,7 +25,7 @@ #define NODE_MAJOR_VERSION 0 #define NODE_MINOR_VERSION 9 #define NODE_PATCH_VERSION 2 -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) diff --git a/test/simple/test-fs-watch.js b/test/simple/test-fs-watch.js index 2ad05f2e11..88c3359124 100644 --- a/test/simple/test-fs-watch.js +++ b/test/simple/test-fs-watch.js @@ -24,7 +24,9 @@ var assert = require('assert'); var path = require('path'); var fs = require('fs'); -var expectFilePath = process.platform == 'win32' || process.platform == 'linux'; +var expectFilePath = process.platform === 'win32' || + process.platform === 'linux' || + process.platform === 'darwin'; var watchSeenOne = 0; var watchSeenTwo = 0; @@ -63,7 +65,10 @@ assert.doesNotThrow( var watcher = fs.watch(filepathOne) watcher.on('change', function(event, filename) { assert.equal('change', event); - if (expectFilePath) { + + // darwin only shows the file path for subdir watching, + // not for individual file watching. + if (expectFilePath && process.platform !== 'darwin') { assert.equal('watch.txt', filename); } else { assert.equal(null, filename); @@ -87,7 +92,10 @@ assert.doesNotThrow( function() { var watcher = fs.watch(filepathTwo, function(event, filename) { assert.equal('change', event); - if (expectFilePath) { + + // darwin only shows the file path for subdir watching, + // not for individual file watching. + if (expectFilePath && process.platform !== 'darwin') { assert.equal('hasOwnProperty', filename); } else { assert.equal(null, filename); diff --git a/test/simple/test-url.js b/test/simple/test-url.js index ff4a8449eb..e8229e0591 100644 --- a/test/simple/test-url.js +++ b/test/simple/test-url.js @@ -659,6 +659,12 @@ for (var u in parseTests) { spaced = url.parse(' \t ' + u + '\n\t'); expected = parseTests[u]; + Object.keys(actual).forEach(function (i) { + if (expected[i] === undefined && actual[i] === null) { + expected[i] = null; + } + }); + assert.deepEqual(actual, expected); assert.deepEqual(spaced, expected); @@ -695,6 +701,11 @@ var parseTestsWithQueryString = { for (var u in parseTestsWithQueryString) { var actual = url.parse(u, true); var expected = parseTestsWithQueryString[u]; + for (var i in actual) { + if (actual[i] === null && expected[i] === undefined) { + expected[i] = null; + } + } assert.deepEqual(actual, expected); } @@ -1227,15 +1238,6 @@ relativeTests.forEach(function(relativeTest) { var actual = url.resolveObject(url.parse(relativeTest[0]), relativeTest[1]), expected = url.parse(relativeTest[2]); - //because of evaluation order - //resolveObject(parse(x), y) == parse(resolve(x, y)) will differ by - //false-ish values. remove all except host and hostname - for (var i in actual) { - if (actual[i] === undefined || - (!emptyIsImportant.hasOwnProperty(i) && !actual[i])) { - delete actual[i]; - } - } assert.deepEqual(actual, expected); @@ -1264,16 +1266,6 @@ relativeTests2.forEach(function(relativeTest) { var actual = url.resolveObject(url.parse(relativeTest[1]), relativeTest[0]), expected = url.parse(relativeTest[2]); - //because of evaluation order - //resolveObject(parse(x), y) == parse(resolve(x, y)) will differ by - //false-ish values. remove all except host and hostname - for (var i in actual) { - if (actual[i] === undefined || - (!emptyIsImportant.hasOwnProperty(i) && !actual[i])) { - delete actual[i]; - } - } - assert.deepEqual(actual, expected); var expected = relativeTest[2],