[#3228] Core support for Gems, and namespace — "Luke A. Kanies" <luke@...>

Hi all,

21 messages 2004/07/27
[#3230] Re: Core support for Gems, and namespace — Austin Ziegler <halostatue@...> 2004/07/27

On Tue, 27 Jul 2004 11:39:08 +0900, Luke A. Kanies <luke@madstop.com> wrote:

[#3234] Re: Core support for Gems, and namespace — "Luke A. Kanies" <luke@...> 2004/07/27

On Tue, 27 Jul 2004, Austin Ziegler wrote:

[#3238] Re: Core support for Gems, and namespace — Austin Ziegler <halostatue@...> 2004/07/27

On Wed, 28 Jul 2004 00:14:29 +0900, Luke A. Kanies <luke@madstop.com> wrote:

[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

In This Thread

Prev Next