zammad/spec/support/threads.rb
2026-01-02 15:41:09 +02:00

59 lines
2 KiB
Ruby

# Copyright (C) 2012-2026 Zammad Foundation, https://zammad-foundation.org/
module ThreadsHelper
# Ensure that any new threads which might be spawned by the block will be cleaned up
# to not interfere with any subsequent tests.
def ensure_threads_exited()
initial_threads = Thread.list
yield
ensure
superfluous_threads = -> { Thread.list - initial_threads }
# Keep going until no more changes are needed to catch threads spawned in between.
3.times do
superfluous_threads.call.each do |t|
t.kill
t.join # From `Timeout.timeout`: make sure thread is dead.
end
break if superfluous_threads.call.none?
sleep 1 # Wait a bit for stuff to settle before trying again.
end
if superfluous_threads.call.any?
superfluous_threads.call.each do |thread|
warn "Error: found a superfluous thread after clean-up: #{thread}"
warn "Backtrace: #{thread.backtrace.join("\n")}"
end
raise 'Superfluous threads found after clean-up.'
end
# Sometimes connections are not checked back in after thread is killed
# This recovers connections from the workers
ActiveRecord::Base.connection_pool.reap
end
# Thread control loops usually run forever. This method can test that they were started.
def ensure_block_keeps_running(timeout: 2.seconds, &)
# Stop after timeout and return true if everything was ok.
# Suppress the Rails logger as its exception may rescue our TimeoutErrors, causing failures here.
Rails.logger.silence do
Timeout.timeout(timeout, &)
end
raise 'Process ended unexpectedly.'
rescue SystemExit
# Convert SystemExit to a RuntimeError as otherwise rspec will shut down without an error.
raise 'Process tried to shut down unexpectedly.'
rescue Timeout::Error
# Default case: process started fine and kept running, interrupted by timeout.
true
end
end
RSpec.configure do |config|
config.include ThreadsHelper
config.around(:each, :ensure_threads_exited) do |example|
ensure_threads_exited { example.run }
end
end