Search code examples
rubypipefork

Errno::EPIPE: Broken pipe exception is raised


if @block
  rd, wr = IO.pipe
  @pid = fork do
    $0 = "Forked child from Page #{@path}"
    rd.close
    result = @block.call(@resp.body)
    begin
    wr.write Marshal.dump(result)
  end
  wr.close

This is a pretty standard way to share a pipe with a fork, yet as soon as rd.close is called it breaks the pipe for wr to use. Up until that line the pipe works as it should (I ran it line by line with Pry). As far as I'm aware it is good practice to close the reader inside the fork to stop it interfering with EOF being sent (I don't know why that works I just know that's the practice).

This is part of a library I call into a production app. The library's own specs never encounter this even though they run very similar code (only the @block and @resp will differ to any great degree). Obviously the app's code is more complex but I can't see how it would be interfering with this code. I searched through other libraries required by the app to see if any were trapping signals that might interfere with this but I found nothing.

Can anyone suggest what the problem could be or a work around for it? I've tried catching the Errno::EPIPE exception and retrying but that doesn't fix it, reopening the pipe (I'm not entirely sure how to do that anyway as after the fork occurs it'd be hard to link it to the main process), emptying the block so it doesn't do any work… still no joy.

I have also found (via a comment on this question) that Ruby's Open3 in the standard library silently rescues and drops Errno::EPIPE but no reason was given with the commit message. I don't know if it is related. https://github.com/ruby/ruby/blob/e3c288569833b6777e7ecc0bbc26f8e6ca8f2ba7/lib/open3.rb#L268


Solution

  • Not sure why this has taken so long to get answered. I suspect this is "fixed" in current versions of ruby as I was unable to replicate using simplified versions of your code, but for future reference, here's how I would have structured the code:

    def test_my_pipes
     rd, wr = IO.pipe  
     fork do  
       rd.close
       sleep 5
       wr.write "Hello world"
       wr.close
     end    
     wr.close  # essential
     puts "Waiting for sleepy fork"
     puts rd.read
     rd.close
    end
    

    Note that both inside and outside the fork block, we close both rd and wr. In actual fact, only the parent process wr.close is essential, but definitely better to close all the ends of the pipe when not needed.

    If this code still breaks, would be interested to see what versions of ruby they break for.