Ruby IO.popen with “-” , what happens under the ho

2019-07-21 12:03发布

问题:

I'm trying to understand IO.popen when its command is "-" which starts a new Ruby interpreter.

There is not much material about this subject, and I'm getting slowly through them, mainly because of me as I only code for fun.

As far as I have understood when IO.popen("-", "w+") {|f| ...} is invoked - that's with a block - that block will be run by both the parent and the child process. The difference is that the parent process will get an IO object as a result, but the child gets only a Nil. That's easy, I need to check |f| in the block and when it is Nil, execution is in the child process, when it is not nil, execution is in the parent. So I have to write both code for parent and child separated by if.

This time it helps me to understand the problem, that the block is part of the IO.popen command.

I have this code:

pipe = IO.popen("-","w+")
# puts "This line will break functionality if uncommented"
  if pipe != nil then
    pipe.puts "PID: #{Process.pid}"
    $stderr.puts "Parent from child: #{pipe.gets.chomp}"
  else
    $stderr.puts "Child PID: #{Process.pid} and Parent #{gets.chomp}"
    puts "M'kay"
  end

Questions:

  • What decides which process runs first? If they were to append a file would it be vulnerable to race condition?
  • Why the 2nd line breaks the code? The pipe = IO.popen... command shouldn't be related to the if..else..end block, yet they are. For me pipe is a file handle (like in old Turbo Pascal) which is first definded somewhere, then manipulated elsewhere.

回答1:

No one decides which process runs first. The child process may run first—or the parent process may run first—OS may schedule them either way.

This means that parent process may finish before the child process finishes. When parent process finishes, the pipe to it is closed, and when the child process writes to it, it gets an exception. That's what happens in your code.

Why doesn't it happen without the commented line? When you invoke gets in the parent process, it waits until the child writes a line to the pipe. It means that the parent won't finish until the child writes a line to the pipe, and this neglects the issue. However, when you print two lines, the odds that the parent process terminates before the child executes the second puts "M'kay" increase.

Try the following code:

pipe = IO.popen("-","w+")
puts "This line will not break functionality"
puts "This line will not break functionality"
puts "This line will not break functionality"
  if pipe != nil then
    pipe.puts "PID: #{Process.pid}"
    while line = pipe.gets
      $stderr.puts "Parent from child: #{line.chomp}"
    end
  else
    $stderr.puts "Child PID: #{Process.pid} and Parent #{gets.chomp}"
    puts "M'kay"
  end

It waits until the child closes the pipe (then the pipe.gets will return nil), which happens then it terminates, and it ensures that it won't try to write there anymore.