summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authordanielsdeleo <dan@opscode.com>2013-12-06 12:41:06 -0800
committerdanielsdeleo <dan@opscode.com>2013-12-09 09:58:20 -0800
commitb148f45428299159697344ee63713650c9083c06 (patch)
treeb4d5bb6811dff35fac4eb5177475c84a85aceff8 /spec
parentc195a3f69d9182e52b57eed5eb6ffea24c974ef8 (diff)
downloadmixlib-shellout-b148f45428299159697344ee63713650c9083c06.tar.gz
Run commands with unique pgid to improve timeout cleanup
To ensure that all child processes are properly signaled to exit before forcibly killing them, use `setsid()` in the child process to give the child (and any grandchildren it might create) a new and unique process group. If the command times out, kill and reap the entire process group.
Diffstat (limited to 'spec')
-rw-r--r--spec/mixlib/shellout_spec.rb53
1 files changed, 52 insertions, 1 deletions
diff --git a/spec/mixlib/shellout_spec.rb b/spec/mixlib/shellout_spec.rb
index 5e92a54..582aef4 100644
--- a/spec/mixlib/shellout_spec.rb
+++ b/spec/mixlib/shellout_spec.rb
@@ -825,7 +825,7 @@ describe Mixlib::ShellOut do
end
end
- context 'with subprocess that takes longer than timeout', :focus do
+ context 'with subprocess that takes longer than timeout' do
def ruby_wo_shell(code)
parts = %w[ruby]
parts << "--disable-gems" if ruby_19?
@@ -889,6 +889,57 @@ describe Mixlib::ShellOut do
end
end
+
+ context "and the child process forks grandchildren" do
+ let(:cmd) do
+ ruby_wo_shell(<<-CODE)
+ STDOUT.sync = true
+ trap(:TERM) { print "got term in child\n"; exit!(123) }
+ fork do
+ trap(:TERM) { print "got term in grandchild\n"; exit!(142) }
+ sleep 10
+ end
+ sleep 10
+ CODE
+ end
+
+ it "should TERM the wayward child and grandchild" do
+ # note: let blocks don't correctly memoize if an exception is raised,
+ # so can't use executed_cmd
+ lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
+ shell_cmd.stdout.should include("got term in child")
+ shell_cmd.stdout.should include("got term in grandchild")
+ end
+
+ end
+ context "and the child process forks grandchildren that don't respond to TERM" do
+ let(:cmd) do
+ ruby_wo_shell(<<-CODE)
+ STDOUT.sync = true
+ trap(:TERM) { print "got term in child\n" }
+ fork do
+ trap(:TERM) { print "got term in grandchild\n" }
+ sleep 10
+ end
+ sleep 10
+ CODE
+ end
+
+ it "should TERM the wayward child and grandchild, then KILL whoever is left" do
+ # note: let blocks don't correctly memoize if an exception is raised,
+ # so can't use executed_cmd
+ lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
+ shell_cmd.stdout.should include("got term in child")
+ shell_cmd.stdout.should include("got term in grandchild")
+
+ # A little janky. We get the process group id out of the command
+ # object, then try to kill a process in it to make sure none
+ # exists. Trusting the system under test like this isn't great but
+ # it's difficult to test otherwise.
+ lambda { Process.kill(:INT, shell_cmd.send(:child_pgid)) }.should raise_error(Errno::ESRCH)
+ end
+
+ end
end
context 'with subprocess that exceeds buffersize' do