app_version.rb 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class AppVersion
  3. MAINTENANCE_THREAD_SLEEP = 5.seconds
  4. REDIS_RESTART_REQUIRED_KEY = 'zammad_restart_required_timestamp'.freeze
  5. REDIS_RESTART_REQUIRED_TTL = 5.minutes
  6. MSG_APP_VERSION = 'app_version'.freeze
  7. MSG_RESTART_MANUAL = 'restart_manual'.freeze
  8. MSG_RESTART_AUTO = 'restart_auto'.freeze
  9. MSG_CONFIG_CHANGED = 'config_changed'.freeze
  10. class_attribute :_redis
  11. =begin
  12. get current app version
  13. version = AppVersion.get
  14. returns
  15. '20150212131700:0' # 'version:if_browser_reload_is_required'
  16. =end
  17. def self.get
  18. Setting.get('app_version')
  19. end
  20. def self.trigger_browser_reload(type, timestamp: make_timestamp)
  21. return if !Setting.exists?(name: 'app_version')
  22. Setting.set('app_version', timestamp)
  23. Sessions.broadcast(event_data(type), 'public')
  24. Gql::Subscriptions::AppMaintenance.trigger({ type: type })
  25. end
  26. def self.trigger_restart
  27. timestamp = make_timestamp
  28. type = if Setting.get('auto_shutdown')
  29. restart_required!(timestamp)
  30. MSG_RESTART_AUTO
  31. else
  32. MSG_RESTART_MANUAL
  33. end
  34. trigger_browser_reload(type, timestamp:)
  35. end
  36. def self.start_maintenance_thread(process_name:)
  37. return if !Setting.get('auto_shutdown')
  38. initial_app_version = get
  39. Rails.logger.debug { "Starting maintenance thread for #{process_name} (#{Process.pid})" }
  40. Thread.new do
  41. Thread.current.abort_on_exception = true
  42. loop do
  43. if restart_required?(initial_app_version)
  44. Rails.logger.debug { "App version change detected, sending TERM signal to #{process_name} (#{Process.pid})" }
  45. Process.kill('TERM', Process.pid)
  46. break
  47. end
  48. sleep MAINTENANCE_THREAD_SLEEP
  49. end
  50. end
  51. end
  52. def self.event_data(type = 'app_version')
  53. {
  54. event: 'maintenance',
  55. data: {
  56. type: type,
  57. app_version: get,
  58. }
  59. }
  60. end
  61. def self.make_timestamp
  62. Time.current.strftime('%Y%m%d%H%M%S')
  63. end
  64. private_class_method :make_timestamp
  65. def self.restart_required!(timestamp)
  66. redis.set(REDIS_RESTART_REQUIRED_KEY, timestamp, ex: REDIS_RESTART_REQUIRED_TTL)
  67. end
  68. private_class_method :restart_required!
  69. def self.restart_required?(known_app_version)
  70. value = redis.get(REDIS_RESTART_REQUIRED_KEY)
  71. value.present? && (known_app_version != value)
  72. end
  73. private_class_method :restart_required?
  74. def self.redis
  75. self._redis ||= ::Redis.new(driver: :hiredis, url: ENV['REDIS_URL'].presence || 'redis://localhost:6379')
  76. end
  77. private_class_method :redis
  78. end