In article <17383D97-991D-4947-AB88-85758AB964FB / reverberate.org>,
  Joshua Haberman <joshua / reverberate.org> writes:

> - 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.

Right.

> - 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).

Right.

> - 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.

No.

IO#write doesn't raise Errno::EAGAIN but retry until all data is written.

IO#read also retry since Ruby 1.9.

So IO#write and IO#read may block calling thread.

>  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]).

Right.

> 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.

fwrite(3) may lost data.

So Ruby 1.8 may lost data.

% ruby-1.8.3 -v
ruby 1.8.3 (2005-09-21) [i686-linux]
% ruby-1.8.3 -rfcntl -e '
w = STDOUT
w.fcntl(Fcntl::F_SETFL, w.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
w << "a" * 4096
w.flush
w << "b"
w.flush
' | ruby -e 'sleep 1; p STDIN.read.length'
4096

However no data is lost if IO#sync = true since Ruby 1.8.2.
It's because stdio is bypassed.

% ruby-1.8.3 -rfcntl -e '
w = STDOUT
w.sync = true
w.fcntl(Fcntl::F_SETFL, w.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
w << "a" * 4096
w.flush
w << "b"                                  
w.flush
' | ruby -e 'sleep 1; p STDIN.read.length'
4097

Ruby 1.9 doesn't have the problem because it has its own
buffering mechanism.

> # 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

If you want to test some data available, use IO.select.
-- 
Tanaka Akira