How to kill processes on Windows using Ruby
In order to terminate a process in Ruby you can use the kill method in the of the Process class in the following way:
pid = 1234
Process.kill("TERM", pid)
If you are using Ruby on Windows you have probably already noticed that Process.kill
does not work correctly.
There are two problems with it:
- It does not recognize any signal except
KILL
; - When it kills a process with
Process.kill("KILL", pid)
the process incorrectly returns status 0 (success).
Here is an example:
# On Windows:
irb(main):002:0> Process.kill("TERM", 1234)
Errno::EINVAL: Invalid argument
# On Linux:
irb(main):003:0> Process.kill("TERM", 686868)
Errno::ESRCH: No such process
Windows complains that TERM
is an invalid argument, although Signal.list
includes TERM
:
irb(main):003:0> Signal.list
=> {"EXIT"=>0, "INT"=>2, "ILL"=>4, "ABRT"=>22, "FPE"=>8, "KILL"=>9, "SEGV"=>11, "TERM"=>15}
You might think Process.kill(15, PID)
works but it fails with the same error.
The KILL
signal however works:
irb(main):004:0> Process.kill("KILL", 768)
=> 1
The questions is: does it work correctly?
Let's run some tests. I ran the following script for testing:
Open an irb instance and run:
command = "tracert www.google.com"
exitstatus = nil
Open3::popen3(command) do |stdin, stdout, stderr, wait_thread|
puts "PID: #{wait_thread.pid}" # ===> this will give you the PID.
status = wait_thread.value
puts "============"
puts "termsig=#{status.termsig.inspect}"
puts "success?=#{status.success?.inspect}"
puts "stopsig=#{status.stopsig.inspect}"
puts "stopped?=#{status.stopped?.inspect}"
puts "signaled?=#{status.signaled?.inspect}"
puts "exitstatus=#{status.exitstatus.inspect}"
puts "exited?=#{status.exited?.inspect}"
puts "status.inspect=#{status.inspect}"
exitstatus = status.exitstatus
puts "============"
end
puts exitstatus.inspect
From a different irb session run the following:
pid = 'XXX' # printed in the code above
Process.kill("KILL", pid)
Here are the results I got:
# KILL Force Windows with Process.kill("KILL", pid)
# PID: 2816
# ============
# termsig=nil
# success?=true <======= wrong on Windows, it was a force kill
# stopsig=nil
# stopped?=false
# signaled?=false
# exitstatus=0 <======== wrong on Windows, it was a force kill
# exited?=true
# status.inspect=#<Process::Status: pid 2816 exit 0>
# ============
What is even worse is that if you run the command again and wait until it finishes (thus no Process.kill
), the output is exactly the same.
# Success Windows
# PID: 4860
# ============
# termsig=nil
# success?=true <======== correct
# stopsig=nil
# stopped?=false
# signaled?=false
# exitstatus=0 <======== correct
# exited?=true
# status.inspect=#<Process::Status: pid 4860 exit 0>
# ============
This means we cannot rely on Process.kill("KILL", PID)
on Windows to stop a process. Please correct me if I am wrong but to me the two outputs above look the same.
I performed the same test (only changed the command tracert to traceroute) on OSX and here are the results:
Without Process.kill
, the command finishes successfully:
# Success OSX
# ============
# termsig=nil
# success?=true <========= correct
# stopsig=nil
# stopped?=false
# signaled?=false
# exitstatus=0 <========= correct
# exited?=true
# status.inspect=#<Process::Status: pid 54466 exit 0>
# ============
With Process.kill
, the command exits with a status code different than 0
:
# KILL force on OSX
# ============
# termsig=9 <========= correct
# success?=nil <========= better
# stopsig=nil
# stopped?=false
# signaled?=true <========= looks good
# exitstatus=nil
# exited?=false
# status.inspect=#<Process::Status: pid 54523 SIGKILL (signal 9)>
# ============
Now the question remains: how do I kill a process on Windows?
I found an easy way using the taskkill command. Please check if your version of Windows is included in the link above.
system("taskkill /pid #{pid}") # graceful stop, will return true / false
system("taskkill /f /pid #{pid}") # force stop, will return true / false
In case you are wondering how the output of the test script looks on Windows with taskkill /f
here it is:
# KILL Force Windows with taskkill /f /pid PID
# PID: 4740
# ============
# termsig=nil
# success?=false <======= correct!
# stopsig=nil
# stopped?=false
# signaled?=false
# exitstatus=1 <======== correct!
# exited?=true
# status.inspect=#<Process::Status: pid 4740 exit 1>
# ============
Happy hacking!