Compare commits

...
12 Commits
Author SHA1 Message Date
David Dollar 2ebb33e049 0.62.0 2013-03-08 14:52:50 -05:00
David Dollar 9fe7ddb8bd Merge pull request #334 from ged/reentrant_signal_handlers
Add deferred signal-handling (fixes #332).
2013-03-08 11:41:12 -08:00
David Dollar f954a42ecb Merge pull request #335 from ged/20_encoding_fix
Fix spec encoding problem under Ruby 2.0.0.
2013-03-08 11:40:50 -08:00
Michael Granger 169188376b Try to allow children to shut down gracefully
Since signals will no longer be handled once foreman goes into
`terminate_gracefully`, default signal handlers are restored so as
not to cause it to get stuck in an unTERMable state.

This necessitates not using the process group for signalling
except as a last resort, as foreman itself will receive the signals
it sends. This splits `killall` into two methods, one which
signals only processes foreman itself has started, and one which
signals all processes in the process group to try to clean up
more aggressively, and then reworks `terminate_gracefully` to use
them.
2013-03-04 14:05:53 -08:00
Michael Granger 5ab08c608b Add deferred signal-handling (fixes #332).
This uses a thread-local queue and a self-pipe so the code in the
trap blocks is properly re-entrant.

For details, see:

  * http://cr.yp.to/docs/selfpipe.html
  * http://blog.rubybestpractices.com/posts/ewong/016-Implementing-Signal-Handlers.html
2013-03-04 13:28:37 -08:00
Michael Granger 0b5c1f919e Fix spec encoding problem under Ruby 2.0.0. 2013-03-04 13:03:13 -08:00
David Dollar c94aa13b06 add ruby 2.0 to travis 2013-03-04 13:11:05 -05:00
David Dollar 4e84b92536 Merge pull request #327 from patheticpat/master
Fixed a typo in cli options description
2013-02-14 06:23:57 -08:00
Michael Kaiser 6215f8b3db Fixed a typo in cli options description 2013-02-14 14:45:44 +01:00
David Dollar ba9167152c handled by mingw now 2013-01-14 08:21:40 -05:00
David Dollar f31ff18191 update docs 2013-01-14 08:19:40 -05:00
David Dollar c42110e438 update changelog 2013-01-14 08:19:31 -05:00
9 changed files with 141 additions and 30 deletions
+1
View File
@@ -18,4 +18,5 @@ notifications:
rvm: rvm:
- 1.9.2 - 1.9.2
- 1.9.3 - 1.9.3
- 2.0.0
- jruby - jruby
+17
View File
@@ -1,3 +1,20 @@
## 0.61.0 (2013-01-14)
* 0.61.0 [David Dollar]
* Fix bug in color definitons [nseo]
* Fix for high CPU load when processes close output [Pavel Forkert]
* Ensure foreman is the process group leader [Christos Trochalakis]
* Don't ignore blank lines in the output [Matt Venables]
* Add license to gemspec [petedmarsh]
* Since JRuby 1.9 doesn't require posix/spawn, only follow that path if JRuby is loaded and running in 1.8 mode. [Adam Hutchison]
* Remove explicit requirement on rubygems. [Cyril Rohr]
* Dont use shared_path variable before multistage has a chance at it [Aditya Sanghi]
* Strip Windows Line Endings [Paul Morton]
* Fix man page: --directory is actually --root. [Evan Jones]
* Add timeout switch to CLI [Paulo Luis Franchini Casaretto]
* Remove expectation of double quotes around environment variables from test comparisons [Kevin McAllister]
* Remove explicit wrapping of Shellwords.escape in double quotes [Kevin McAllister]
## 0.60.2 (2012-10-08) ## 0.60.2 (2012-10-08)
* Fix for nil value on io select loop, fixes #260 [Silvio Relli] * Fix for nil value on io select loop, fixes #260 [Silvio Relli]
+1 -1
View File
@@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
foreman (0.61.0) foreman (0.62.0)
thor (>= 0.13.6) thor (>= 0.13.6)
GEM GEM
-16
View File
@@ -1,16 +0,0 @@
file pkg("foreman-#{version}-x86-mswin32.gem") => distribution_files do |t|
Bundler.with_clean_env do
sh "env PLATFORM=mswin32 gem build foreman.gemspec"
end
sh "mv foreman-#{version}-x86-mswin32.gem #{t.name}"
end
task "mswin32:build" => pkg("foreman-#{version}-x86-mswin32.gem")
task "mswin32:clean" do
clean pkg("foreman-#{version}-x86-mswin32.gem")
end
task "mswin32:release" => "mswin32:build" do |t|
sh "gem push #{pkg("foreman-#{version}-x86-mswin32.gem")} || echo 'error'"
end
+1 -1
View File
@@ -23,7 +23,7 @@ class Foreman::CLI < Thor
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env" method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"' method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"'
method_option :port, :type => :numeric, :aliases => "-p" method_option :port, :type => :numeric, :aliases => "-p"
method_option :timeout, :type => :numeric, :aliases => "-t", :desc => "Specify the amount of time (in seconds) processes have to shudown gracefully before receiving a SIGKILL, defaults to 5." method_option :timeout, :type => :numeric, :aliases => "-t", :desc => "Specify the amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL, defaults to 5."
class << self class << self
# Hackery. Take the run method away from Thor so that we can redefine it. # Hackery. Take the run method away from Thor so that we can redefine it.
+118 -9
View File
@@ -9,6 +9,10 @@ require "thread"
class Foreman::Engine class Foreman::Engine
# The signals that the engine cares about.
#
HANDLED_SIGNALS = [ :TERM, :INT, :HUP ]
attr_reader :env attr_reader :env
attr_reader :options attr_reader :options
attr_reader :processes attr_reader :processes
@@ -33,6 +37,16 @@ class Foreman::Engine
@processes = [] @processes = []
@running = {} @running = {}
@readers = {} @readers = {}
# Self-pipe for deferred signal-handling (ala djb: http://cr.yp.to/docs/selfpipe.html)
reader, writer = create_pipe
reader.close_on_exec = true if reader.respond_to?(:close_on_exec)
writer.close_on_exec = true if writer.respond_to?(:close_on_exec)
@selfpipe = { :reader => reader, :writer => writer }
# Set up a global signal queue
# http://blog.rubybestpractices.com/posts/ewong/016-Implementing-Signal-Handlers.html
Thread.main[:signal_queue] = []
end end
# Start the processes registered to this +Engine+ # Start the processes registered to this +Engine+
@@ -41,10 +55,7 @@ class Foreman::Engine
# Make sure foreman is the process group leader. # Make sure foreman is the process group leader.
Process.setpgrp unless Foreman.windows? Process.setpgrp unless Foreman.windows?
trap("TERM") { puts "SIGTERM received"; terminate_gracefully } register_signal_handlers
trap("INT") { puts "SIGINT received"; terminate_gracefully }
trap("HUP") { puts "SIGHUP received"; terminate_gracefully } if ::Signal.list.keys.include? 'HUP'
startup startup
spawn_processes spawn_processes
watch_for_output watch_for_output
@@ -53,6 +64,74 @@ class Foreman::Engine
shutdown shutdown
end end
# Set up deferred signal handlers
#
def register_signal_handlers
HANDLED_SIGNALS.each do |sig|
if ::Signal.list.include? sig.to_s
trap(sig) { Thread.main[:signal_queue] << sig ; notice_signal }
end
end
end
# Unregister deferred signal handlers
#
def restore_default_signal_handlers
HANDLED_SIGNALS.each do |sig|
trap(sig, :DEFAULT) if ::Signal.list.include? sig.to_s
end
end
# Wake the main thread up via the selfpipe when there's a signal
#
def notice_signal
@selfpipe[:writer].write_nonblock( '.' )
rescue Errno::EAGAIN
# Ignore writes that would block
rescue Errno::EINT
# Retry if another signal arrived while writing
retry
end
# Invoke the real handler for signal +sig+. This shouldn't be called directly
# by signal handlers, as it might invoke code which isn't re-entrant.
#
# @param [Symbol] sig the name of the signal to be handled
#
def handle_signal(sig)
case sig
when :TERM
handle_term_signal
when :INT
handle_interrupt
when :HUP
handle_hangup
else
system "unhandled signal #{sig}"
end
end
# Handle a TERM signal
#
def handle_term_signal
puts "SIGTERM received"
terminate_gracefully
end
# Handle an INT signal
#
def handle_interrupt
puts "SIGINT received"
terminate_gracefully
end
# Handle a HUP signal
#
def handle_hangup
puts "SIGHUP received"
terminate_gracefully
end
# Register a process to be run by this +Engine+ # Register a process to be run by this +Engine+
# #
# @param [String] name A name for this process # @param [String] name A name for this process
@@ -98,11 +177,11 @@ class Foreman::Engine
end end
end end
# Send a signal to all processesstarted by this +Engine+ # Send a signal to all processes started by this +Engine+
# #
# @param [String] signal The signal to send to each process # @param [String] signal The signal to send to each process
# #
def killall(signal="SIGTERM") def kill_children(signal="SIGTERM")
if Foreman.windows? if Foreman.windows?
@running.each do |pid, (process, index)| @running.each do |pid, (process, index)|
system "sending #{signal} to #{name_for(pid)} at pid #{pid}" system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
@@ -111,6 +190,21 @@ class Foreman::Engine
rescue Errno::ESRCH, Errno::EPERM rescue Errno::ESRCH, Errno::EPERM
end end
end end
else
begin
Process.kill signal, *@running.keys unless @running.empty?
rescue Errno::ESRCH, Errno::EPERM
end
end
end
# Send a signal to the whole process group.
#
# @param [String] signal The signal to send
#
def killall(signal="SIGTERM")
if Foreman.windows?
kill_children(signal)
else else
begin begin
Process.kill "-#{signal}", Process.getpgrp Process.kill "-#{signal}", Process.getpgrp
@@ -277,8 +371,22 @@ private
Thread.new do Thread.new do
begin begin
loop do loop do
io = IO.select(@readers.values, nil, nil, 30) io = IO.select([@selfpipe[:reader]] + @readers.values, nil, nil, 30)
begin
@selfpipe[:reader].read_nonblock(11)
rescue Errno::EAGAIN, Errno::EINTR => err
# ignore
end
# Look for any signals that arrived and handle them
while sig = Thread.main[:signal_queue].shift
self.handle_signal(sig)
end
(io.nil? ? [] : io.first).each do |reader| (io.nil? ? [] : io.first).each do |reader|
next if reader == @selfpipe[:reader]
if reader.eof? if reader.eof?
@readers.delete_if { |key, value| value == reader } @readers.delete_if { |key, value| value == reader }
else else
@@ -305,13 +413,14 @@ private
def terminate_gracefully def terminate_gracefully
return if @terminating return if @terminating
restore_default_signal_handlers
@terminating = true @terminating = true
if Foreman.windows? if Foreman.windows?
system "sending SIGKILL to all processes" system "sending SIGKILL to all processes"
killall "SIGKILL" kill_children "SIGKILL"
else else
system "sending SIGTERM to all processes" system "sending SIGTERM to all processes"
killall "SIGTERM" kill_children "SIGTERM"
end end
Timeout.timeout(options[:timeout]) do Timeout.timeout(options[:timeout]) do
watch_for_termination while @running.length > 0 watch_for_termination while @running.length > 0
+1 -1
View File
@@ -1,5 +1,5 @@
module Foreman module Foreman
VERSION = "0.61.0" VERSION = "0.62.0"
end end
+1 -1
View File
@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3
. .
.TH "FOREMAN" "1" "October 2012" "" "" .TH "FOREMAN" "1" "January 2013" "Foreman 0.61.0" "Foreman Manual"
. .
.SH "NAME" .SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications \fBforeman\fR \- manage Procfile\-based applications
+1 -1
View File
@@ -41,7 +41,7 @@ describe Foreman::Process do
it "should output utf8 properly" do it "should output utf8 properly" do
process = Foreman::Process.new(resource_path("bin/utf8")) process = Foreman::Process.new(resource_path("bin/utf8"))
run(process).should == "\xFF\x03\n" run(process).should == "\xFF\x03\n".force_encoding('binary')
end end
end end