J
Joshua Haberman
Here is my understanding about the current state of I/O in Ruby.
Please correct me where I am mistaken.
- by default, ruby i/o operations block, but only block the calling
Ruby thread. Ruby does this by scheduling a thread out if the fd is
not read-ready/write-ready. If there is more than one Ruby thread,
Ruby won't do a read(2) or write(2) on an fd unless select() says it
is ready, to prevent blocking the entire process.
- the one flaw with this scheme is that write(2) can block even if an
fd is write-ready, if you try to write too much data. This will
cause such a write to lock the entire process and all Ruby threads
therein ([0] is a simple test program that displays the problem).
- You can try setting O_NONBLOCK on your IO objects with fcntl. That
will help you in the case where you only have one Ruby thread -- now
read and write will raise Errno::EAGAIN if the fd isn't ready. But
in the case where there is more than one Ruby thread, this won't work
because Ruby won't perform the read(2) or write(2) until the fd is
ready. So even though you have O_NONBLOCK set, you block your Ruby
thread. (See [1] for an example]).
Is this right? What is the current state of supporting nonblocking i/
o in Ruby?
One other question: are the buffered fread()/fwrite() functions
guaranteed to work correctly if O_NONBLOCK is set on the underlying
descriptor? I have not been able to find a good answer to this.
Josh
Example [0]:
thread = Thread.new {
while true
puts "Background thread running..."
sleep 1;
end
}
# Give the background thread a few chances to show that it's running
sleep 2;
(read_pipe, write_pipe) = IO:ipe
# this will stall the entire process, including the background thread.
# change the length to 4096 and everything is fine.
write_pipe.write(" " * 4097)
thread.join
Example [1]:
require 'fcntl'
thread = Thread.new {
while true
puts "Background thread running..."
sleep 1;
end
}
(read_pipe, write_pipe) = IO:ipe
read_pipe.fcntl(Fcntl::F_SETFL, read_pipe.fcntl(Fcntl::F_GETFL) |
Fcntl::O_NONBLOCK)
# this will block our thread, even though the fd is set to nonblocking.
# however, if you eliminate the background thread, this call with
give you EAGAIN,
# which is what you want.
read_pipe.read
# we will never get here
puts "Finished read!"
Please correct me where I am mistaken.
- by default, ruby i/o operations block, but only block the calling
Ruby thread. Ruby does this by scheduling a thread out if the fd is
not read-ready/write-ready. If there is more than one Ruby thread,
Ruby won't do a read(2) or write(2) on an fd unless select() says it
is ready, to prevent blocking the entire process.
- the one flaw with this scheme is that write(2) can block even if an
fd is write-ready, if you try to write too much data. This will
cause such a write to lock the entire process and all Ruby threads
therein ([0] is a simple test program that displays the problem).
- You can try setting O_NONBLOCK on your IO objects with fcntl. That
will help you in the case where you only have one Ruby thread -- now
read and write will raise Errno::EAGAIN if the fd isn't ready. But
in the case where there is more than one Ruby thread, this won't work
because Ruby won't perform the read(2) or write(2) until the fd is
ready. So even though you have O_NONBLOCK set, you block your Ruby
thread. (See [1] for an example]).
Is this right? What is the current state of supporting nonblocking i/
o in Ruby?
One other question: are the buffered fread()/fwrite() functions
guaranteed to work correctly if O_NONBLOCK is set on the underlying
descriptor? I have not been able to find a good answer to this.
Josh
Example [0]:
thread = Thread.new {
while true
puts "Background thread running..."
sleep 1;
end
}
# Give the background thread a few chances to show that it's running
sleep 2;
(read_pipe, write_pipe) = IO:ipe
# this will stall the entire process, including the background thread.
# change the length to 4096 and everything is fine.
write_pipe.write(" " * 4097)
thread.join
Example [1]:
require 'fcntl'
thread = Thread.new {
while true
puts "Background thread running..."
sleep 1;
end
}
(read_pipe, write_pipe) = IO:ipe
read_pipe.fcntl(Fcntl::F_SETFL, read_pipe.fcntl(Fcntl::F_GETFL) |
Fcntl::O_NONBLOCK)
# this will block our thread, even though the fd is set to nonblocking.
# however, if you eliminate the background thread, this call with
give you EAGAIN,
# which is what you want.
read_pipe.read
# we will never get here
puts "Finished read!"