зеркало из https://github.com/github/ruby.git
socket (rsock_connect): fix and refactor for blocking
* ext/socket/init.c (rsock_connect): refactor for blocking (wait_connectable): clear error before wait [Bug #9356] We no longer use non-blocking sockets to emulate blocking behavior, so eliminate error-prone and confusing platform-dependent code. According to POSIX, connect() only needs to be called once in the face of EINTR, so do not loop on it. Before waiting on connect, drop any pending errors, since rb_wait_for_single_fd may not clear the existing error properly. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@47617 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
Родитель
106075eac8
Коммит
7914a8726e
|
@ -1,3 +1,9 @@
|
|||
Thu Sep 18 05:44:05 2014 Eric Wong <e@80x24.org>
|
||||
|
||||
* ext/socket/init.c (rsock_connect): refactor for blocking
|
||||
(wait_connectable): clear error before wait
|
||||
[Bug #9356]
|
||||
|
||||
Wed Sep 17 23:12:36 2014 NARUSE, Yui <naruse@ruby-lang.org>
|
||||
|
||||
* lib/uri/rfc3986_parser.rb: specify a regexp for :OPAQUE; generic.rb
|
||||
|
|
|
@ -339,68 +339,65 @@ rsock_socket(int domain, int type, int proto)
|
|||
return fd;
|
||||
}
|
||||
|
||||
/* emulate blocking connect behavior on EINTR or non-blocking socket */
|
||||
static int
|
||||
wait_connectable(int fd)
|
||||
{
|
||||
int sockerr;
|
||||
int sockerr, revents;
|
||||
socklen_t sockerrlen;
|
||||
int revents;
|
||||
int ret;
|
||||
|
||||
for (;;) {
|
||||
/*
|
||||
* Stevens book says, successful finish turn on RB_WAITFD_OUT and
|
||||
* failure finish turn on both RB_WAITFD_IN and RB_WAITFD_OUT.
|
||||
*/
|
||||
revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, NULL);
|
||||
/* only to clear pending error */
|
||||
sockerrlen = (socklen_t)sizeof(sockerr);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) < 0)
|
||||
return -1;
|
||||
|
||||
if (revents & (RB_WAITFD_IN|RB_WAITFD_OUT)) {
|
||||
sockerrlen = (socklen_t)sizeof(sockerr);
|
||||
ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen);
|
||||
/*
|
||||
* Stevens book says, successful finish turn on RB_WAITFD_OUT and
|
||||
* failure finish turn on both RB_WAITFD_IN and RB_WAITFD_OUT.
|
||||
* So it's enough to wait only RB_WAITFD_OUT and check the pending error
|
||||
* by getsockopt().
|
||||
*
|
||||
* Note: rb_wait_for_single_fd already retries on EINTR/ERESTART
|
||||
*/
|
||||
revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, NULL);
|
||||
|
||||
/*
|
||||
* Solaris getsockopt(SO_ERROR) return -1 and set errno
|
||||
* in getsockopt(). Let's return immediately.
|
||||
*/
|
||||
if (ret < 0)
|
||||
break;
|
||||
if (sockerr == 0) {
|
||||
if (revents & RB_WAITFD_OUT)
|
||||
break;
|
||||
else
|
||||
continue; /* workaround for winsock */
|
||||
}
|
||||
if (revents < 0)
|
||||
return -1;
|
||||
|
||||
/* BSD and Linux use sockerr. */
|
||||
errno = sockerr;
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
sockerrlen = (socklen_t)sizeof(sockerr);
|
||||
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) < 0)
|
||||
return -1;
|
||||
|
||||
if ((revents & (RB_WAITFD_IN|RB_WAITFD_OUT)) == RB_WAITFD_OUT) {
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
switch (sockerr) {
|
||||
case 0:
|
||||
/*
|
||||
* be defensive in case some platforms set SO_ERROR on the original,
|
||||
* interrupted connect()
|
||||
*/
|
||||
case EINTR:
|
||||
#ifdef ERESTART
|
||||
case ERESTART:
|
||||
#endif
|
||||
case EAGAIN:
|
||||
#ifdef EINPROGRESS
|
||||
case EINPROGRESS:
|
||||
#endif
|
||||
#ifdef EALREADY
|
||||
case EALREADY:
|
||||
#endif
|
||||
#ifdef EISCONN
|
||||
case EISCONN:
|
||||
#endif
|
||||
return 0; /* success */
|
||||
default:
|
||||
/* likely (but not limited to): ECONNREFUSED, ETIMEDOUT, EHOSTUNREACH */
|
||||
errno = sockerr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
#define WAIT_IN_PROGRESS 10
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#define WAIT_IN_PROGRESS 10
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
/* returns correct error */
|
||||
#define WAIT_IN_PROGRESS 0
|
||||
#endif
|
||||
#ifndef WAIT_IN_PROGRESS
|
||||
/* BSD origin code apparently has a problem */
|
||||
#define WAIT_IN_PROGRESS 1
|
||||
#endif
|
||||
|
||||
struct connect_arg {
|
||||
int fd;
|
||||
const struct sockaddr *sockaddr;
|
||||
|
@ -429,11 +426,6 @@ rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks)
|
|||
int status;
|
||||
rb_blocking_function_t *func = connect_blocking;
|
||||
struct connect_arg arg;
|
||||
#if WAIT_IN_PROGRESS > 0
|
||||
int wait_in_progress = -1;
|
||||
int sockerr;
|
||||
socklen_t sockerrlen;
|
||||
#endif
|
||||
|
||||
arg.fd = fd;
|
||||
arg.sockaddr = sockaddr;
|
||||
|
@ -441,76 +433,22 @@ rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks)
|
|||
#if defined(SOCKS) && !defined(SOCKS5)
|
||||
if (socks) func = socks_connect_blocking;
|
||||
#endif
|
||||
for (;;) {
|
||||
status = (int)BLOCKING_REGION_FD(func, &arg);
|
||||
if (status < 0) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
#if defined(ERESTART)
|
||||
case ERESTART:
|
||||
#endif
|
||||
continue;
|
||||
status = (int)BLOCKING_REGION_FD(func, &arg);
|
||||
|
||||
case EAGAIN:
|
||||
if (status < 0) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
#ifdef ERESTART
|
||||
case ERESTART:
|
||||
#endif
|
||||
case EAGAIN:
|
||||
#ifdef EINPROGRESS
|
||||
case EINPROGRESS:
|
||||
case EINPROGRESS:
|
||||
#endif
|
||||
#if WAIT_IN_PROGRESS > 0
|
||||
sockerrlen = (socklen_t)sizeof(sockerr);
|
||||
status = getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen);
|
||||
if (status) break;
|
||||
if (sockerr) {
|
||||
status = -1;
|
||||
errno = sockerr;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef EALREADY
|
||||
case EALREADY:
|
||||
#endif
|
||||
#if WAIT_IN_PROGRESS > 0
|
||||
wait_in_progress = WAIT_IN_PROGRESS;
|
||||
#endif
|
||||
status = wait_connectable(fd);
|
||||
if (status) {
|
||||
break;
|
||||
}
|
||||
errno = 0;
|
||||
continue;
|
||||
|
||||
#if WAIT_IN_PROGRESS > 0
|
||||
case EINVAL:
|
||||
if (wait_in_progress-- > 0) {
|
||||
/*
|
||||
* connect() after EINPROGRESS returns EINVAL on
|
||||
* some platforms, need to check true error
|
||||
* status.
|
||||
*/
|
||||
sockerrlen = (socklen_t)sizeof(sockerr);
|
||||
status = getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen);
|
||||
if (!status && !sockerr) {
|
||||
struct timeval tv = {0, 100000};
|
||||
rb_thread_wait_for(tv);
|
||||
continue;
|
||||
}
|
||||
status = -1;
|
||||
errno = sockerr;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifdef EISCONN
|
||||
case EISCONN:
|
||||
status = 0;
|
||||
errno = 0;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
return wait_connectable(fd);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
Загрузка…
Ссылка в новой задаче