diff options
author | danielsdeleo <dan@opscode.com> | 2013-12-06 12:41:06 -0800 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2013-12-09 09:58:20 -0800 |
commit | b148f45428299159697344ee63713650c9083c06 (patch) | |
tree | b4d5bb6811dff35fac4eb5177475c84a85aceff8 /spec | |
parent | c195a3f69d9182e52b57eed5eb6ffea24c974ef8 (diff) | |
download | mixlib-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.rb | 53 |
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 |