background_services.rb 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class BackgroundServices
  3. def self.available_services
  4. BackgroundServices::Service.descendants
  5. end
  6. attr_reader :config
  7. def initialize(config)
  8. @config = Array(config)
  9. end
  10. def run
  11. Rails.logger.debug 'Starting BackgroundServices...'
  12. # Fork before starting the threads in the main process to ensure a consistent state
  13. # and minimal memory overhead (see also #5420).
  14. config
  15. .in_order_of(:start_as, %i[fork thread])
  16. .each do |service_config|
  17. run_service service_config
  18. end
  19. Process.waitall
  20. loop do
  21. sleep 1
  22. end
  23. rescue Interrupt
  24. nil
  25. ensure
  26. Rails.logger.debug('Stopping BackgroundServices.')
  27. end
  28. private
  29. def run_service(service_config)
  30. if !service_config.enabled?
  31. Rails.logger.debug { "Skipping disabled service #{service_config.service.service_name}." }
  32. return
  33. end
  34. service_config.service.pre_run
  35. case service_config.start_as
  36. when :fork
  37. start_as_forks(service_config.service, service_config.workers)
  38. when :thread
  39. start_as_thread(service_config.service)
  40. end
  41. end
  42. def start_as_forks(service, forks)
  43. (1..forks).map do
  44. Process.fork do
  45. Rails.logger.debug { "Starting process ##{Process.pid} for service #{service.service_name}." }
  46. service.new.run
  47. rescue Interrupt
  48. nil
  49. end
  50. end
  51. end
  52. def start_as_thread(service)
  53. Thread.new do
  54. Thread.current.abort_on_exception = true
  55. Rails.logger.debug { "Starting thread for service #{service.service_name} in the main process." }
  56. service.new.run
  57. # BackgroundServices rspec test is using Timeout.timeout to stop background services.
  58. # It was fine for a long time, but started throwing following error in Rails 7.2.
  59. # This seems to affect that test case only.
  60. # Unfortunately, since it's running on a separate thread, that error has to be rescued here.
  61. # That said, this should be handled by improving services loops to support graceful exiting.
  62. rescue ActiveRecord::ActiveRecordError => e
  63. raise e if Rails.env.test? && e.message != 'Cannot expire connection, it is not currently leased.' # rubocop:disable Zammad/DetectTranslatableString
  64. end
  65. end
  66. end