Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better handle interrupts #46

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions lib/turbo_tests/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def initialize(opts)

@messages = Thread::Queue.new
@threads = []
@wait_threads = []
@error = false
end

Expand Down Expand Up @@ -94,9 +95,12 @@ def run

@reporter.seed_notification(@seed, @seed_used)

wait_threads = tests_in_groups.map.with_index do |tests, process_id|
old_signal = Signal.trap(:INT) { handle_interrupt }

@wait_threads = tests_in_groups.map.with_index do |tests, process_id|
start_regular_subprocess(tests, process_id + 1, **subprocess_opts)
end
end.compact
@interrupt_handled = false

handle_messages

Expand All @@ -106,11 +110,28 @@ def run

@threads.each(&:join)

@reporter.failed_examples.empty? && wait_threads.map(&:value).all?(&:success?)
Signal.trap(:INT, old_signal)

@reporter.failed_examples.empty? && @wait_threads.map(&:value).all?(&:success?)
end

private

def handle_interrupt
if @interrupt_handled
Kernel.exit
else
puts "\nShutting down subprocesses..."
@wait_threads.each do |wait_thr|
child_pid = wait_thr.pid
pgid = Process.respond_to?(:getpgid) ? Process.getpgid(child_pid) : 0
Process.kill(:INT, child_pid) if Process.pid != pgid
rescue Errno::ESRCH
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also handle race condition when the PID file was removed.

$ bundle exec turbo_tests
Using recorded test runtime
3 processes for 3 specs, ~ 1 specs per process

Randomized with seed 27875

......

Finished in 3.36 seconds (files took 3.12 seconds to load)
6 examples, 0 failures


Randomized with seed 27875

#<Thread:0x0000000001b5c2b8 $HOME/Workspace/turbo_tests/lib/turbo_tests/runner.rb:224 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
	1: from $HOME/Workspace/turbo_tests/lib/turbo_tests/runner.rb:232:in `block in start_subprocess'
$HOME/Workspace/turbo_tests/lib/turbo_tests/runner.rb:232:in `write': No such file or directory @ rb_sysopen - tmp/test-pipes/subprocess-2 (Errno::ENOENT)
bundler: failed to load command: turbo_tests ($HOME/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/bin/turbo_tests)
Traceback (most recent call last):
	1: from $HOME/Workspace/turbo_tests/lib/turbo_tests/runner.rb:232:in `block in start_subprocess'
$HOME/Workspace/turbo_tests/lib/turbo_tests/runner.rb:232:in `write': No such file or directory @ rb_sysopen - tmp/test-pipes/subprocess-2 (Errno::ENOENT)
Suggested change
rescue Errno::ESRCH
rescue Errno::ESRCH, Errno::ENOENT

end
@interrupt_handled = true
end
end

def setup_tmp_dir
begin
FileUtils.rm_r("tmp/test-pipes")
Expand All @@ -136,6 +157,8 @@ def start_subprocess(env, extra_args, tests, process_id, record_runtime:)
type: "exit",
process_id: process_id
}

nil
else
tmp_filename = "tmp/test-pipes/subprocess-#{process_id}"

Expand Down Expand Up @@ -185,6 +208,7 @@ def start_subprocess(env, extra_args, tests, process_id, record_runtime:)
File.open(tmp_filename) do |fd|
fd.each_line do |line|
message = JSON.parse(line, symbolize_names: true)
break if message[:type] == "quit"

message[:process_id] = process_id
@messages << message
Expand All @@ -201,6 +225,13 @@ def start_subprocess(env, extra_args, tests, process_id, record_runtime:)
unless wait_thr.value.success?
@messages << {type: "error"}
end

# If the rspec quit before sending anything to file, the other thread will be blocking.
# Send a message to awaken it. If the reading side has already closed this step will be skipped (EXNIO).
begin
File.write(tmp_filename, JSON.generate({type: "quit"}), mode: File::WRONLY | File::NONBLOCK)
rescue Errno::ENXIO
end
}

wait_thr
Expand Down