Considering Ruby For a Networking Application

When you run nonblock_test.rb in Windows, does it ever catch EAGAIN on
the
read thread? (I’m too lazy to track down a Windows box to run it on.)

From: “Francis C.” [email protected]

end
end

That’s because on Windows, this isn’t the way to set sockets nonblocking.
Microsoft reinvented a lot of wheels in Windows, and this is one of 'em. Try
this C code instead (I’ll leave it to you to turn it into Ruby):

unsigned long one = 1;
ioctlsocket (the_socket_descriptor, FIONBIO, &one);

Right, except that in win32/win32.c, ruby corrects microsoft’s
misfeatures for us, by making fcntl() on win32-ruby shunt to
ioctlsocket behind the scenes:

int
fcntl(int fd, int cmd, …)
{
//[…]

if (arg & O_NONBLOCK) {
    ioctlArg = 1;
}
else {
    ioctlArg = 0;
}
RUBY_CRITICAL({
    ret = ioctlsocket(sock, FIONBIO, &ioctlArg);
    if (ret == -1) {
        errno = map_errno(WSAGetLastError());
    }
});

Regards,

Bill

One more thing, what happens if you run this program on Windows on a
network? You have an intra-process localhost connection and the kernel
may
be behaving differently in this case, particularly as regards the buffer
usage that would interact with nonblocking i/o.

Thanks for correcting me on the wrapper in win32.c. I have to say, it’s
a
strange design choice. To me it would have made more sense to wrap both
behind a “setnonblocking” method. Also, that source file is interesting,
it
looks like the Ruby folks borrowed it.

On May 8, 2006, at 7:36 PM, Francis C. wrote:

Yes, you want your callbacks to return quickly. Spin a Ruby thread
otherwise. If it turns out that many users of Eventmachine face
that issue
(such as doing a database call while processing a callback) then
we’ll look
at incorporating a thread-pool into Eventmachine. That would fit
with the
goals of Eventmachine, which are performance/scalability, ease-of-
use, and
feature-completeness for server implementations.

I personally am fine with the current implementation. Seems like a
perfect fit with Ruby’s threads.

James Edward G. II

If you try it out, I’ll be very interested in hearing how it goes. We’re
taking feature requests, too :-).
(We’re currently re-implementing the Windows version to use async i/o,
but
that probably doesn’t matter to you.)

Your guess about how that Intergraph library happened to get into the
distro
makes a lot of sense. They can also roll it back out it they ever decide
to,
because the core APIs never got changed :-). At the end of the day,
however,
this convinces me all the more that it was right for us to offer a
higher-level library that wraps up network i/o as much as possible. We
wrote
a SIP stack in pure Ruby last year, and it was very painful to get it
right
because Ruby’s implementation of i/o kept violating expectations.

From: “Francis C.” [email protected]

When you run nonblock_test.rb in Windows, does it ever catch EAGAIN on the
read thread? (I’m too lazy to track down a Windows box to run it on.)

No; the only exception I ever saw raised was EOFError from the
readpartial.

I included the EAGAIN handling because the code in
lib/openssl/buffering.rb is written that way. But, I was never
sure it should be necessary, from what I’d seen in the io.c,
win32/win32.c, and ext/socket/socket.c source.

From casually perusing the source, it appears to me as though
EAGAIN/EWOULDBLOCK are being handled internally; so I’m unclear
on why/when one would expect to need to handle them from ruby.

(And, as yet, I’ve not seen an EAGAIN from readpartial or
syswrite in my tests on either Linux or Windows… not that my
tests have been by any means exhaustive… :slight_smile:

Continuing to your next post:

One more thing, what happens if you run this program on Windows on a
network? You have an intra-process localhost connection and the kernel may
be behaving differently in this case, particularly as regards the buffer
usage that would interact with nonblocking i/o.

Good point; I’ll try that…

[…] the wrapper in win32.c. I have to say, it’s a strange
design choice. To me it would have made more sense to wrap both
behind a “setnonblocking” method.

I’d tend to agree, but I think it’s partly historical. The
nonblocking handling (including the ftctl -> ioctlsocket shunt)
magically appeared in ruby sometime between 1.8.2 and 1.8.4.
Previously, there had been discussions on the mailing lists about
how to address the blocking issues, and suggestions for adding
a set of nonblock_read(), etc. methods, among other suggestions.
As I recall, Matz was supportive of seeing a solution to the
blocking issues, but didn’t like any of the as-yet proposed
method names. My guess is, some enterprising soul(s) between
1.8.2 and 1.8.4 found an acceptable way to make the solution
happen… without changing the existing APIs. :slight_smile:

Regards,

Bill

In article [email protected],
“Bill K.” [email protected] writes:

No; the only exception I ever saw raised was EOFError from the
readpartial.

I included the EAGAIN handling because the code in
lib/openssl/buffering.rb is written that way. But, I was never
sure it should be necessary, from what I’d seen in the io.c,
win32/win32.c, and ext/socket/socket.c source.

I modified the document of readpartial.
I hope it makes the behaviour clear.

Index: io.c

RCS file: /src/ruby/io.c,v
retrieving revision 1.401
diff -u -p -r1.401 io.c
— io.c 28 Mar 2006 01:50:11 -0000 1.401
+++ io.c 9 May 2006 02:10:04 -0000
@@ -1337,7 +1337,7 @@ io_getpartial(int argc, VALUE *argv, VAL

  • r.readpartial(4096)      #=> "ghi\n"     ""              ""
    
  • Note that readpartial is nonblocking-flag insensitive.
    • It blocks even if the nonblocking-flag is set.
    • It blocks on the situation IO#sysread causes Errno::EAGAIN.
    • Also note that readpartial behaves similar to sysread in blocking
      mode.
    • The behavior is identical when the buffer is empty.

From casually perusing the source, it appears to me as though
EAGAIN/EWOULDBLOCK are being handled internally; so I’m unclear
on why/when one would expect to need to handle them from ruby.

(And, as yet, I’ve not seen an EAGAIN from readpartial or
syswrite in my tests on either Linux or Windows… not that my
tests have been by any means exhaustive… :slight_smile:

IO#syswrite causes EAGAIN.

% ./ruby -rfcntl -ve ’
r, w = IO.pipe
w.fcntl(Fcntl::F_SETFL, File::NONBLOCK|w.fcntl(Fcntl::F_GETFL))
p w.syswrite(“a” * 70000)
p w.syswrite(“a” * 70000)

ruby 1.9.0 (2006-05-01) [i686-linux]
65536
-e:5:in `IO#syswrite’: Resource temporarily unavailable (Errno::EAGAIN)
from -e:5

It is also possible on sockets.

% ./ruby -rsocket -rfcntl -ve ’
a = TCPServer.new 20000
r = TCPSocket.new “localhost”, 20000
w = a.accept
w.fcntl(Fcntl::F_SETFL, File::NONBLOCK|w.fcntl(Fcntl::F_GETFL))
p w.syswrite(“a” * 70000)
p w.syswrite(“a” * 70000)
p w.syswrite(“a” * 70000)
p w.syswrite(“a” * 70000)
p w.syswrite(“a” * 70000)

ruby 1.9.0 (2006-05-01) [i686-linux]
65536
16384
-e:8:in `IO#syswrite’: Resource temporarily unavailable (Errno::EAGAIN)
from -e:8

The condition is

  1. IO#syswrite
  2. pipe/socket (kernel) buffer is full
  3. Ruby has only one thread (No I/O multiplexing using select)

In article [email protected],
“Bill K.” [email protected] writes:

Wow. Thanks for the details! I’m going to have to eat my
words on a couple recent posts where I thought ruby behaved
identically regardless of whether just one thread or multiple
threads existed.

As far as I know, matz found the inconsistency in
[ruby-talk:66196]. It is not intended.

Out of curiosity, is there a technical reason why it’s
desirable for #sysread to raise EAGAIN in this one situation?

  1. In general, matz respect programmer’s knowledge on POSIX.
    For example, “read(2) system call cause EAGAIN on
    non-blocking mode” in this case. I don’t think many
    programmers understand the non-blocking issue well,
    though.

  2. Some application, maybe event driven system, needs
    EAGAIN. (If no application needs EAGAIN, why POSIX has
    EAGAIN?)

However checking O_NONBLOCK before read(2) have race
condition if the fd is shared with another process. So I
recommend other solution if possible.

“Tanaka A.” [email protected] wrote:

I modified the document of readpartial.
I hope it makes the behaviour clear.
[…]

    • It blocks even if the nonblocking-flag is set.
    • It blocks on the situation IO#sysread causes Errno::EAGAIN.

Ah - OK, thanks!

IO#syswrite causes EAGAIN.
[…]
It is also possible on sockets.
[…]
The condition is

  1. IO#syswrite
  2. pipe/socket (kernel) buffer is full
  3. Ruby has only one thread (No I/O multiplexing using select)

Wow. Thanks for the details! I’m going to have to eat my
words on a couple recent posts where I thought ruby behaved
identically regardless of whether just one thread or multiple
threads existed.

Out of curiosity, is there a technical reason why it’s
desirable for #sysread to raise EAGAIN in this one situation?

It seems strange to have to clutter the ruby code with EAGAIN
checks, if just saying Thread.new{loop{sleep(1000000)}} means
I’ll never have to check for EAGAIN in ruby.

If that makes sense. I’m just wondering if there’s a technical
reason why we wouldn’t want the C code to hide EAGAIN from
ruby code in all cases, not just most cases.

Thanks,

Regards,

Bill

Francis – just out of curiousity – is the Reactor model in
Eventmachine similar to what happens inside the Twisted networking
framework in Python?

Well, Giles, now I’m really glad you asked the question about Twisted.
I’m
going to open another thread about this because I’m curious to know what
others think.

Well, I don’t actually know anything about the innards of Twisted, but
I’ll
try to find out. The “reactor” pattern has been around for a while. As I
recall it started getting more currency a few years ago when the whole
“C10K” issue was being discussed. To me, it’s one of the ways of
recognizing
that specific issues of blocking vs. nonblocking i/o, threaded vs.
nonthreaded and all the rest of the controversial stuff are really
low-level
issues. And they shouldn’t pollute the application-level domain where
most
programmers work, because they add no value there. What the reactor
pattern
says is: “if you’re willing to think of a network protocol as a state
machine rather than a conversation, then I can enable to you process far
more users in much less time.”

And for what it’s worth, the reactor model may not be the best way to do
Windows servers, because unlike any mainstream Unix flavor, Windows
actually
has excellent async i/o. We’re currently in the middle of rewriting the
Windows side of EventMachine to use async instead of nonblocking i/o.

Quoting [email protected], on Tue, May 09, 2006 at 04:09:20AM
+0900:

has relatively light requirements. You may find this odd but I worry more
about accepts blocking than writes. An accepting socket can select readable
(meaning a new connection is available), but be empty by the time you get
around to reading it (like if there was a network error that caused a
pending connection to reset). This can be a real nasty.

Stevens describes this problem, but I thought he also described the fix,
set the socket to non-blocking so the accept() won’t block.

doesn’t this work? is this a ruby-specific problem?

Sam

Works fine. It’s not a ruby-specific issue. You have to remember to set
the
accepting socket non-blocking, and then be aware that it may return
EAGAIN
(or whatever your platform-specific equivalent is) even if it selected
readable.

(This was two days ago and now I can’t remember the context this came
up
in. Were we talking about apps that prefer to use blocking i/o?)

Quoting [email protected], on Thu, May 11, 2006 at 01:35:58PM
+0900:

Works fine. It’s not a ruby-specific issue. You have to remember to set the
accepting socket non-blocking, and then be aware that it may return EAGAIN
(or whatever your platform-specific equivalent is) even if it selected
readable.

(This was two days ago and now I can’t remember the context this came up
in. Were we talking about apps that prefer to use blocking i/o?)

I think it was in the contex of ruby’s socket apis not behaving as
expected, maybe a few things got mixed up there.

Cheers,
Sam

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs