Question

I saw an article which suggests the following code for a writer:

output = open("my_pipe", "w+") # the w+ means we don't block
output.puts "hello world"
output.flush # do this when we're done writing data

and a reader:

input = open("my_pipe", "r+") # the r+ means we don't block
puts input.gets # will block if there's nothing in the pipe

But could it happen that open, puts, gets will block the program? Is there some kind of timeout in place? Can one change it? Also, how come w+ means non-blocking call? Which open system call flags is it converted to?

Was it helpful?

Solution

Okay, let me share with you my picture of the world. As rogerdpack said, there are two options: 1) using select in blocking mode, 2) using non-blocking mode (O_NONBLOCK flag, read_nonblock, write_nonblock, select methods). I haven't tried, so these are just speculations.

As to why open, puts and gets may block the thread. open call blocks until there are at least one reader and at least one writer. And that must be the reason why we need to specify r+, w+ for open call. Judging from strace output they both are converted to O_RDWR flag. Then there must be some buffer, where not yet received data are stored. And that must be the reason why write methods may block. Read methods may block because they expect more data to be available, than it really is.

UPD

If a process attempts to read from an empty pipe, then read(2) will block until data is available. If a process attempts to write to a full pipe (see below), then write(2) blocks until sufficient data has been read from the pipe to allow the write to complete.

-- http://linux.die.net/man/7/pipe

The FIFO must be opened on both ends (reading and writing) before data can be passed. Normally, opening the FIFO blocks until the other end is opened also.

Under Linux, opening a FIFO for read and write will succeed both in blocking and nonblocking mode. POSIX leaves this behavior undefined. This can be used to open a FIFO for writing while there are no readers available.

-- http://linux.die.net/man/7/fifo

And here's the implementation I came up with:

#!/home/yuri/.rbenv/shims/ruby
require 'timeout'
data = ((0..15).to_a.map { |v|
    (v < 10 ? '0'.ord + v : 'a'.ord + v - 10).chr
} * 4096 * 2).reduce('', :+)
timeout = 10
start = Time.now
open('1.fifo', File::WRONLY | File::NONBLOCK) { |out|
    out.flock(File::LOCK_EX)
    nwritten = 0
    data_len = data.length
    begin
        delta = out.write_nonblock data
        data = data[delta..-1]
        nwritten += delta
    rescue IO::WaitWritable, Errno::EINTR
        timeout_left = timeout - (Time.now - start)
        if timeout_left < 0
            puts Time.now - start
            raise Timeout::Error
        end
        IO.select nil, [out], nil, timeout_left
        retry
    end while nwritten < data_len
}
puts Time.now - start

But for my problem at hand I decided to ignore this timeout thing. It probably will suffice to handle just situations when there is no reader on the other end of the pipe (Errno::ENXIO):

open('1.fifo', File::WRONLY | File::NONBLOCK) { |out|
    out.flock(File::LOCK_EX)
    nwritten = 0
    data_len = data.length
    begin
        delta = out.write_nonblock data
        data = data[delta..-1]
        nwritten += delta
    rescue IO::WaitWritable, Errno::EINTR
        IO.select nil, [out]
        retry
    end while nwritten < data_len
}

P.S. Your feedback is appreciated.

OTHER TIPS

This page should answer all your questions... http://www.ruby-doc.org/core-2.0.0/IO.html

In general, puts can always block the current thread, since they may have to wait for IO to complete for it to return. gets can also block the current thread because it will read and read forever until it hits the first newline, then it will return everything it read. HTH.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top