threads.rb 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. module ThreadsHelper
  3. # Ensure that any new threads which might be spawned by the block will be cleaned up
  4. # to not interfere with any subsequent tests.
  5. def ensure_threads_exited()
  6. initial_threads = Thread.list
  7. yield
  8. ensure
  9. superfluous_threads = -> { Thread.list - initial_threads }
  10. # Keep going until no more changes are needed to catch threads spawned in between.
  11. 3.times do
  12. superfluous_threads.call.each do |t|
  13. t.kill
  14. t.join # From `Timeout.timeout`: make sure thread is dead.
  15. end
  16. break if superfluous_threads.call.count.zero?
  17. sleep 1 # Wait a bit for stuff to settle before trying again.
  18. end
  19. if superfluous_threads.call.count.positive?
  20. superfluous_threads.each do |thread|
  21. warn "Error: found a superfluous thread after clean-up: #{thread}"
  22. warn "Backtrace: #{thread.backtrace.join("\n")}"
  23. end
  24. raise 'Superfluous threads found after clean-up.'
  25. end
  26. # Sometimes connections are not checked back in after thread is killed
  27. # This recovers connections from the workers
  28. ActiveRecord::Base.connection_pool.reap
  29. end
  30. # Thread control loops usually run forever. This method can test that they were started.
  31. def ensure_block_keeps_running(timeout: 2.seconds, &block)
  32. # Stop after timeout and return true if everything was ok.
  33. # Suppress the Rails logger as its exception may rescue our TimeoutErrors, causing failures here.
  34. Rails.logger.silence do
  35. Timeout.timeout(timeout, &block)
  36. end
  37. raise 'Process ended unexpectedly.'
  38. rescue SystemExit
  39. # Convert SystemExit to a RuntimeError as otherwise rspec will shut down without an error.
  40. raise 'Process tried to shut down unexpectedly.'
  41. rescue Timeout::Error
  42. # Default case: process started fine and kept running, interrupted by timeout.
  43. true
  44. end
  45. end
  46. RSpec.configure do |config|
  47. config.include ThreadsHelper
  48. config.around(:each, :ensure_threads_exited) do |example|
  49. ensure_threads_exited { example.run }
  50. end
  51. end