Kill process and sub-processes in Ruby on Windows
I eventually solved this in the following manner
First I installed the sys-proctable gem
gem install 'sys-proctable'
then used the originally posted code to spawn
the process, and the following to kill it (error handling omitted for brevity)
require 'win32/process'
require 'sys/proctable'
include Win32
include Sys
to_kill = .. // PID of spawned process
ProcTable.ps do |proc|
to_kill << proc.pid if to_kill.include?(proc.ppid)
end
Process.kill(9, *to_kill)
to_kill.each do |pid|
Process.waitpid(pid) rescue nil
end
You could change the kill 9
to something a little less offensive of course, but this is the gist of the solution.
What Process.kill signals are available on Windows?
I think I found a solution. To find out what signals your current platform supports, run this:
ruby -e "puts Signal.list"
On Windows:
{"EXIT"=>0, "INT"=>2, "ILL"=>4, "ABRT"=>22, "FPE"=>8, "KILL"=>9, "SEGV"=>11, "TERM"=>15}
Cross-platform way to Check if a (non-child) Process is Running
The easiest way is to test which operation system your ruby is running, and then through the command line use a simple test on linux or windows to check if your program is running.
The easiest way often is not the best way. Yet, be prepared to face some strange code, for example, how to simple kill a process on windows. It is your call.
I would love someone say I'm wrong and that there is a great way to manage processes in ruby being cross-platform. Please do.
How to stop a process from within the tests, when testing a never-ending process?
With the answer from CodeGnome on how to use Timeout::timeout
and the answer from andyconhin on how to redirect Process::spawn
IO, I came up with two Minitest helpers that can be used as follows:
it "runs a deamon" do
wait_for(timeout: 2) do
wait_for_spawned_io(regexp: /Hello World/, command: ["bin/runner"])
end
end
The helpers are:
def wait_for(timeout: 1, &block)
Timeout::timeout(timeout) do
yield block
end
rescue Timeout::Error
flunk "Test did not pass within #{timeout} seconds"
end
def wait_for_spawned_io(regexp: //, command: [])
buffer = ""
begin
read_pipe, write_pipe = IO.pipe
pid = Process.spawn(command.shelljoin, out: write_pipe, err: write_pipe)
loop do
buffer << read_pipe.readpartial(1000)
break if regexp =~ buffer
end
ensure
read_pipe.close
write_pipe.close
Process.kill("INT", pid)
end
buffer
end
These can be used in a test which allows me to start a subprocess, capture the STDOUT and as soon as it matches the test Regular Expression, it passes, else it will wait 'till timeout and flunk (fail the test).
The loop
will capture output and pass the test once it sees matching output. It uses a IO.pipe
because that is most transparant for subprocesses (and their children) to write to.
I doubt this will work on Windows. And it needs some cleaning up of the wait_for_spawned_io
which is doing slightly too much IMO. Antoher problem is that the Process.kill('INT')
might not reach the children which are orphaned but still running after this test has ran. I need to find a way to ensure the entire subtree of processes is killed.
Having a IO.popen command be killed when the caller process is killed
Yes, you just need to install a signal handler in your main process to trap Ctrl-C (SIGINT) and then send that signal to the child process.
This example should explain things:
# Start child and save its pid
io = IO.popen("sleep 600")
pid = io.pid
# Print the output of the ps command (just for demonstration)
puts "Checking #{pid} ..."
system("ps #{pid}")
puts "Installing signal handler..."
Signal.trap("INT") {
# Ctrl-C was pressed...
puts "Caught interrupt - killing child..."
# Kill child process...
Process.kill("INT", pid)
# This prevents the process from becoming defunct
io.close
# Just for demonstration purposes - check that it died
puts "Checking #{pid} ..."
system("ps #{pid}")
# Forward the INT signal back to the parent
# ...or you could just call "exit" here too.
puts "Forwarding signal to myself..."
Signal.trap("INT", "DEFAULT")
Process.kill("INT", 0)
}
# Make the parent "do some stuff"...
puts "Sleeping parent..."
sleep 600
Output:
> ruby popen_test.rb
Checking 2474 ...
PID TTY STAT TIME COMMAND
2474 pts/0 S+ 0:00 sleep 600
Installing signal handler...
Sleeping parent...
# Press Ctrl-C ...
Caught interrupt - killing child...
Checking 2474 ...
PID TTY STAT TIME COMMAND
Forwarding signal to myself...
kill.rb:20: Interrupt
from kill.rb:24:in `call'
from kill.rb:24:in `sleep'
from kill.rb:24
How do I start a Rails server programatically from a Ruby script
I eventually decided to avoid any ickiness and start the rails server in its own process, as detailed in this post. (Being able to kill it plus its child processes consistently was the main blocker and the original reason I'd considered starting it in a thread instead.)
Related Topics
How to Group Numbers into Different Buckets in Ruby
How to Specify a Read Timeout for a Net::Http::Post.New Request in Ruby 2
Is It Necessary to Close Stringio in Ruby
Split a String into Chunks of Specified Size Without Breaking Words
Parsing Large Xml Files W/ Ruby & Nokogiri
How to Remove Xcode 4.2 and Install 4.1 to Develop Ruby/Rails on Osx Lion
How to Remove the Zone from a Datetime Value
Is There an Equivalent of Array#Find_Index for the Last Index in Ruby
How to Run Rake with --Trace Within Capistrano
Setting Mime Type for .Ogv Files in Rails Development Environment
Rails 3.2.8 - How to Get the Week Number from Rails
The Program 'Rails' Can Be Found in the Following Packages - Issue with Some App and Rvm
Ruby Refuses to Divide Correctly
Why Do Ruby Procs/Blocks with Splat Arguments Behave Differently Than Methods and Lambdas