runtime/forked_popen.rb
tests/_examples/runtime/forked_popen.rb in the metorex repository.
# `IO.popen("-")` forks. The child answers nil with its standard output and
# input joined to pipes, and the parent answers a stream over the other ends.
io = IO.popen "-"
if io
p io.gets
p io.pid.is_a?(Integer)
io.close
p $?.success?
else
puts "hello from child"
exit!
end
# "r+" joins both ways, and closing the stream waits for the child.
both = IO.popen "-", "r+"
if both
both.puts "to child"
both.close_write
p both.read
both.close
p $?.exitstatus
else
line = STDIN.gets
puts "child read #{line.inspect}"
exit! 3
end
# With a block the parent's block gets the stream and answers the result,
# while the child's gets nil and the child exits after it.
answered = IO.popen "-" do |pipe|
if pipe
pipe.read
else
puts "from the child's block"
end
end
p answered
# A stream opened one way refuses the other.
writing = IO.popen "-", "w"
if writing
p((writing.read rescue $!.class))
writing.close
else
exit!
end
# `fork` goes through `Process._fork`, and both answer `respond_to?`.
p Process.respond_to?(:fork)
p Process.respond_to?(:_fork)
child = Process._fork
Process.exit! 5 if child == 0
p Process.wait2(child)[1].exitstatus
# A shared buffer over a file is the same bytes in both processes.
path = "/tmp/metorex_forked_popen_#{Process.pid}.txt"
File.write path, "I'm private"
file = File.open path, "r+"
buffer = IO::Buffer.map file
IO.popen "-" do |pipe|
if pipe
pipe.read
p buffer.get_string
else
buffer.set_string "I'm shared!"
end
end
p File.read(path)
file.close
File.delete path
# `exit!` without a status leaves with false, which is 1.
bare = fork { exit! }
p Process.wait2(bare)[1].exitstatus
Output:
"hello from child\n"
true
false
"child read \"to child\\n\"\n"
3
"from the child's block\n"
IOError
true
true
5
"I'm shared!"
"I'm shared!"
1