What is the best way to spawn a subprocess without blocking the actor ?
My goal is to run multiple commands, and when they are done, get the output and the exit code. I tried this, but obviously, the popen call is blocking:
#!/usr/bin/env ruby
require 'celluloid/current'
require 'celluloid/io'
class MyProcessLauncher
include Celluloid::IO
def run
every(1) { puts "tick" }
every(5) {
puts "Starting subprocess"
::IO.popen("sleep 10 && echo 'subprocess done'", :err=>[:child, :out]) { |io|
puts io.read
}
puts $?.exitstatus
}
end
end
MyProcessLauncher.new.run
sleep
The output is :
tick
tick
tick
tick
Starting subprocess
subprocess done
0
tick
Starting subprocess
but I expect five 'tick' between each 'Starting subprocess'...
Thanks !
You need to defer
or use a future
if you want the return value.
defer
( easy if you need no return )In your every(5)
block, to use defer
, wrap your popen
call (including the exit status display call) with defer {}
future
( a bit more complicated )There are several ways to implement this approach. My suggestion would be to move the contents of every(5)
to its own method, and initialize an array @futures
or similar when starting MyProcessLauncher
... then use this inside the every(5)
call:
@futures = future.new_method
Also, add a new loop to process your futures. After every(5)
add a new handler ( either another every
loop, or an async
call to a processor method which is recursive ) to process your values, essentially doing this:
value = @futures.pop.value if @futures.any?
I'd ask a follow up question about futures if you have trouble and can't find examples of how to process futures. Otherwise, there you have two ways to handle popen
calls without blocking.