Update trigger abort behavior when running parallel actions

When the provider supports parallel actions and actions are being
run in parallel, do not immediately kill the process on failure.
Instead terminate the action thread and log the exit code. Once
all running actions have completed, the process will then exit
with the stored exit code.
This commit is contained in:
Chris Roberts 2019-06-04 14:37:33 -07:00
parent d0115de80e
commit 88c675694a
5 changed files with 78 additions and 2 deletions

View File

@ -71,6 +71,10 @@ module Vagrant
thread = Thread.new do
Thread.current[:error] = nil
# Note that this thread is being used for running
# a batch action
Thread.current[:batch_parallel_action] = par
# Record our pid when we started in order to figure out if
# we've forked...
start_pid = Process.pid
@ -160,6 +164,16 @@ module Vagrant
if !errors.empty?
raise Errors::BatchMultiError, message: errors.join("\n\n")
end
# Check if any threads set an exit code and exit if found. If
# multiple threads have exit code values set, the first encountered
# will be the value used.
threads.each do |thread|
if thread[:exit_code]
@logger.debug("Found exit code set within batch action thread. Exiting")
Process.exit!(thread[:exit_code])
end
end
end
end
end

View File

@ -300,8 +300,17 @@ module Vagrant
#
# @param [Integer] code Code to exit Vagrant on
def trigger_abort(exit_code)
@ui.warn(I18n.t("vagrant.trigger.abort"))
Process.exit!(exit_code)
if Thread.current[:batch_parallel_action]
@ui.warn(I18n.t("vagrant.trigger.abort_threaded"))
@logger.debug("Trigger abort within parallel batch action. " \
"Setting exit code and terminating.")
Thread.current[:exit_code] = exit_code
Thread.current.terminate
else
@ui.warn(I18n.t("vagrant.trigger.abort"))
@logger.debug("Trigger abort within non-parallel action, exiting directly")
Process.exit!(exit_code)
end
end
# Calls the given ruby block for execution

View File

@ -297,6 +297,9 @@ en:
Trigger configured to continue on error...
abort: |-
Vagrant has been configured to abort. Terminating now...
abort_threaded: |-
Vagrant has been configured to abort. Vagrant will terminate
after remaining running actions have completed...
start: |-
Running %{type} triggers %{stage} %{action} ...
fire_with_name: |-

View File

@ -63,5 +63,24 @@ describe Vagrant::BatchAction do
subject.action(machine, "up")
subject.run
end
context "with provider supporting parallel actions" do
let(:provider_options) { {parallel: true} }
it "should flag threads as being parallel actions" do
parallel = nil
subject.custom(machine) { |m| parallel = Thread.current[:batch_parallel_action] }
subject.custom(machine) { |*_| }
subject.run
expect(parallel).to eq(true)
end
it "should exit the process if exit_code has been set" do
subject.custom(machine) { |m| Thread.current[:exit_code] = 1}
subject.custom(machine) { |*_| }
expect(Process).to receive(:exit!).with(1)
subject.run
end
end
end
end

View File

@ -423,6 +423,37 @@ describe Vagrant::Plugin::V2::Trigger do
expect(Process).to receive(:exit!).with(3)
subject.send(:trigger_abort, 3)
end
context "when running in parallel" do
let(:thread) {
@t ||= Thread.new do
Thread.current[:batch_parallel_action] = true
Thread.stop
subject.send(:trigger_abort, exit_code)
end
}
let(:exit_code) { 22 }
before do
expect(Process).not_to receive(:exit!)
sleep(0.1) until thread.stop?
end
after { @t = nil }
it "should terminate the thread" do
expect(thread).to receive(:terminate).and_call_original
thread.wakeup
thread.join(1) while thread.alive?
end
it "should set the exit code into the thread data" do
expect(thread).to receive(:terminate).and_call_original
thread.wakeup
thread.join(1) while thread.alive?
expect(thread[:exit_code]).to eq(exit_code)
end
end
end
context "#ruby" do