- Ruby socket connect code seems to work unstably in case if connection

to remote host was refused. FreeBSD connect(2) call returns EINVAL in
  that case and clears the error code, so there's no way to determine
  what happened. Reimplement ruby_connect via select call instead of
  polling the status by connect(2). This may also reduce overhead (though,
  not verified).

Reported by:	Saku Ytti <saku@ytti.fi>
This commit is contained in:
Stanislav Sedov 2009-02-23 00:41:07 +00:00
parent 1648f40b44
commit d99591ca7b

View file

@ -0,0 +1,105 @@
--- ext/socket/socket.c.orig 2009-02-23 00:54:12.000000000 +0300
+++ ext/socket/socket.c 2009-02-23 01:27:13.000000000 +0300
@@ -1111,81 +1111,33 @@
fcntl(fd, F_SETFL, mode|NONBLOCKING);
#endif /* HAVE_FCNTL */
- for (;;) {
#if defined(SOCKS) && !defined(SOCKS5)
- if (socks) {
- status = Rconnect(fd, sockaddr, len);
- }
- else
-#endif
- {
- status = connect(fd, sockaddr, len);
- }
- if (status < 0) {
- switch (errno) {
- case EAGAIN:
-#ifdef EINPROGRESS
- case EINPROGRESS:
-#endif
-#if WAIT_IN_PROGRESS > 0
- sockerrlen = 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 = 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;
+ if (socks) {
+ status = Rconnect(fd, sockaddr, len);
+ }
+ else
#endif
- default:
- break;
+ {
+ status = connect(fd, sockaddr, len);
+ }
+
+ if (status < 0 && (errno == EINPROGRESS || errno == EWOULDBLOCK)) {
+ status = wait_connectable(fd);
+ if (status == 0) {
+ int buf;
+ char c;
+ int len = sizeof(buf);
+ status = getpeername(fd, (struct sockaddr *)&buf, &len);
+ if (status == -1) {
+ read(fd, &c, 1); /* set errno. */
}
}
+ }
+
#ifdef HAVE_FCNTL
- fcntl(fd, F_SETFL, mode);
+ fcntl(fd, F_SETFL, mode);
#endif
- return status;
- }
+ return status;
}
struct inetsock_arg