[PATCH] Nonblocking socket connect - Win32 - 181
From:
"Jean-Francois Nadeau" <jean-francois.nadeau@...>
Date:
2004-07-08 00:35:20 UTC
List:
ruby-core #3154
Hi, In reference to my post in ruby-talk "Windows - Socket - Connect - Nonblocking", I have included in this message the patch I have made for ruby 1.8.1. In Win32, the socket was not put in non-blocking mode in socket.c, ruby_connect. The reason is that fcntl does not exist on Win32. So, to enable non-blocking sockets on Win32, I did two things: - For Win32 only, I have added the method "fcntl" to BasicSocket class that overrides the one in IO. That method only supports F_SETFL, O_NONBLOCK. Internally, it calls ioclsocket with FIONBIO (WinSocket specific). I have added to Win32.h two #define F_SETFL and O_NONBLOCK. That enables the module fcntl to be used like on Unix to activate the non-blocking mode to a socket in Win32. - In socket.c, ruby_connect. I have added a call to ioctlsocket at the same level as the existing fcntl call. I have done the same thing at the end of the function to revert back to blocking. - In Win32, a non-blocking connect does not return EINPROGRESS but WSAEWOULDBLOCK. So, I trap that error code in rb_w32_connect in win32.c. I substitute WSAEWOULDBLOCK by EINPROGRESS. - Another subtle difference in Win32 is that when the connect fails in non-blocking mode, the FD group that is set in select() is "except" and not "write". So, I had to implement a new function "thread_w32_connect_select" in socket.c that calls rb_thread_select on both set. Now, when the "except" set is on, I have to trap it and return ETIMEDOUT. In Win32, you cannot call connect() the second time after a non-blocking connection failure. That's it for the patch. I can assure you that it makes a big difference in multi-threading network code. I am building a very nice RSS aggregator which is partly built in Ruby. That patch is a must for my code. I would appreciate a lot if that fix goes in the next ruby release, if possible. I have run all unit tests and it's ok. I have also included to this message a short ruby test script for that patch. Thanks, Jean-Francois Nadeau http://www.jfnadeau.com
Attachments (2)
nonblocking_connect_w23_test.rb
(966 Bytes, text/x-ruby)
require 'socket'
require 'net/http'
require 'fcntl'
module Net
class HTTP
attr_reader :socket
end
end
# This thread should always run
t = Thread.new do
while 1
puts "in thread"
sleep(0.1)
end
end
BOGUS_IP = "192.168.0.80"
BOGUS_PORT = 80
connect_exception = false
begin
tcpSocket = TCPSocket.new(BOGUS_IP, BOGUS_PORT)
tcpSocket.write("bogus data") # 20 seconds timeout
rescue Exception => e
# We should get a connect failed exception
connect_exception = true
puts "SUCCESS: We received a timeout exception"
end
if !connect_exception
raise "Failed: Did not receive a connect exception"
end
connect_exception = false
begin
h = Net::HTTP.new('news.com.com', 80)
h.start
h.socket.socket.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
resp, data = h.get('/', nil )
if data.size > 0
puts "SUCCESS: Received data!"
end
rescue Exception => e
connect_exception = true
end
if connect_exception
raise "Failed: Received a connect exception"
end
nonblocking_connect_w32_181.patch.txt
(3.63 KB, text/x-diff)
Index: ruby/ext/socket/socket.c
===================================================================
RCS file: /src/ruby/ext/socket/socket.c,v
retrieving revision 1.108
diff -u -r1.108 socket.c
--- ruby/ext/socket/socket.c 14 Dec 2003 10:04:34 -0000 1.108
+++ ruby/ext/socket/socket.c 7 Jul 2004 22:22:31 -0000
@@ -738,6 +738,31 @@
rb_thread_select(fd+1, 0, &fds, 0, 0);
}
+#ifdef _WIN32
+
+static int
+thread_w32_connect_select(fd)
+ int fd;
+{
+ fd_set fds_w;
+ fd_set fds_e;
+
+ FD_ZERO(&fds_w);
+ FD_ZERO(&fds_e);
+
+ FD_SET(fd, &fds_w);
+ FD_SET(fd, &fds_e);
+
+ rb_thread_select(fd+1, 0, &fds_w, &fds_e, 0);
+ if (FD_ISSET(fd, &fds_e)) {
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
+
#ifdef __CYGWIN__
#define WAIT_IN_PROGRESS 10
#endif
@@ -767,6 +792,11 @@
int sockerr, sockerrlen;
#endif
+#ifdef _WIN32
+ u_long ioctlArg;
+ int connecterr;
+#endif
+
#if defined(HAVE_FCNTL)
mode = fcntl(fd, F_GETFL, 0);
@@ -785,6 +815,11 @@
fcntl(fd, F_SETFL, mode|NONBLOCKING);
#endif /* HAVE_FCNTL */
+#ifdef _WIN32
+ ioctlArg = 1;
+ ioctlsocket(fd, FIONBIO, &ioctlArg);
+#endif
+
for (;;) {
#if defined(SOCKS) && !defined(SOCKS5)
if (socks) {
@@ -817,7 +852,17 @@
#if WAIT_IN_PROGRESS > 0
wait_in_progress = WAIT_IN_PROGRESS;
#endif
+#ifndef _WIN32
thread_write_select(fd);
+#endif
+#ifdef _WIN32
+ connecterr = thread_w32_connect_select(fd);
+ if (connecterr) {
+ status = -1;
+ errno = ETIMEDOUT;
+ break;
+ }
+#endif
continue;
#if WAIT_IN_PROGRESS > 0
@@ -854,6 +899,12 @@
#ifdef HAVE_FCNTL
fcntl(fd, F_SETFL, mode);
#endif
+
+#ifdef _WIN32
+ ioctlArg = 0;
+ ioctlsocket(fd, FIONBIO, &ioctlArg);
+#endif
+
return status;
}
}
@@ -2362,6 +2413,43 @@
rb_define_const(mConst, name, INT2FIX(value));
}
+#ifdef _WIN32
+
+static VALUE
+rb_w32_socket_fcntl(self, vcmd, varg)
+ VALUE vcmd;
+ VALUE varg;
+{
+ int cmd, arg;
+ OpenFile *fptr;
+ u_long ioctlArg;
+
+ cmd = NUM2INT(vcmd);
+ arg = NUM2INT(varg);
+
+ if (cmd == F_SETFL) {
+
+ if (arg & O_NONBLOCK) {
+ ioctlArg = 1;
+ } else {
+ ioctlArg = 0;
+ }
+
+ GetOpenFile(self, fptr);
+ if(ioctlsocket(fileno(fptr->f), FIONBIO, &ioctlArg) < 0)
+ rb_sys_fail("ioctlsocket");
+
+ return Qnil;
+
+ } else {
+
+ rb_notimplement();
+ return Qnil;
+ }
+}
+
+#endif
+
void
Init_socket()
{
@@ -2405,6 +2493,10 @@
#ifdef SOCKS5
rb_define_method(rb_cSOCKSSocket, "close", socks_s_close, 0);
#endif
+#endif
+
+#ifdef _WIN32
+ rb_define_method(rb_cIO, "fcntl", rb_w32_socket_fcntl, 2);
#endif
rb_cTCPServer = rb_define_class("TCPServer", rb_cTCPSocket);
Index: ruby/win32/win32.c
===================================================================
RCS file: /src/ruby/win32/win32.c,v
retrieving revision 1.103
diff -u -r1.103 win32.c
--- ruby/win32/win32.c 27 Nov 2003 09:13:50 -0000 1.103
+++ ruby/win32/win32.c 7 Jul 2004 22:23:42 -0000
@@ -1969,8 +1969,15 @@
}
RUBY_CRITICAL({
r = connect(TO_SOCKET(s), addr, addrlen);
- if (r == SOCKET_ERROR)
- errno = map_errno(WSAGetLastError());
+ if (r == SOCKET_ERROR) {
+ r = WSAGetLastError();
+ if (r != WSAEWOULDBLOCK) {
+ errno = map_errno(r);
+ } else {
+ errno = EINPROGRESS;
+ r = -1;
+ }
+ }
});
return r;
}
Index: ruby/win32/win32.h
===================================================================
RCS file: /src/ruby/win32/win32.h,v
retrieving revision 1.46
diff -u -r1.46 win32.h
--- ruby/win32/win32.h 22 Dec 2003 08:23:55 -0000 1.46
+++ ruby/win32/win32.h 7 Jul 2004 21:06:52 -0000
@@ -312,6 +312,9 @@
#define ESTALE WSAESTALE
#define EREMOTE WSAEREMOTE
+#define F_SETFL 1
+#define O_NONBLOCK 1
+
#ifdef accept
#undef accept
#endif