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 theif..else..end
block, yet they are. For mepipe
is a file handle (like in old Turbo Pascal) which is first definded somewhere, then manipulated elsewhere.
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 secondputs "M'kay"
increase.Try the following code:
It waits until the child closes the pipe (then the
pipe.gets
will returnnil
), which happens then it terminates, and it ensures that it won't try to write there anymore.