mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-21 09:07:49 +08:00
Pitchfork has been the default web server for some time now. This removes Unicorn entirely to simplify the codebase and unblock future improvements (like Rack 3). Notable changes beyond the straightforward removal: - `Discourse.after_unicorn_worker_fork` → `Discourse.apply_worker_db_variables_overrides`: renamed and wired into pitchfork.conf.rb's `after_worker_fork`. This actually *fixes* per-worker DB variable overrides (`unicorn_worker_db_variables_*`) which were never called under Pitchfork. - `bin/ember-cli`: `--unicorn` flag renamed to `--server` (`-u` kept). - `lib/demon/sidekiq.rb`: removed Unicorn-specific USR1/USR2 signal handlers and `reopen_logs` (called `Unicorn::Util.reopen_logs`), which were already dead code under Pitchfork. Intentionally kept unchanged: - `config/unicorn_launcher` (used by Docker images, separate effort) - `docker_manager` plugin (separate repo) - `UNICORN_*` env vars (renaming deferred) - Rack < 3 constraint (separate PR)
146 lines
4.1 KiB
Ruby
Vendored
146 lines
4.1 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
require "demon/base"
|
|
|
|
class Demon::Sidekiq < ::Demon::Base
|
|
def self.prefix
|
|
"sidekiq"
|
|
end
|
|
|
|
def self.after_fork(&blk)
|
|
blk ? (@blk = blk) : @blk
|
|
end
|
|
|
|
# Number of seconds Sidekiq waits for jobs to finish before forcefully
|
|
# terminating them. See the "-t" CLI option.
|
|
SIDEKIQ_SHUTDOWN_TIMEOUT_SECONDS = 5
|
|
|
|
# By default Sidekiq does a heartbeat check every 5 seconds. If the processes misses 20 heartbeat checks, we consider it
|
|
# dead and kill the process.
|
|
SIDEKIQ_HEARTBEAT_CHECK_MISS_THRESHOLD_SECONDS = 5.seconds * 20
|
|
|
|
def self.heartbeat_check
|
|
sidekiq_processes_for_current_hostname = {}
|
|
|
|
Sidekiq::ProcessSet.new.each do |process|
|
|
if process["hostname"] == HOSTNAME
|
|
sidekiq_processes_for_current_hostname[process["pid"]] = process
|
|
end
|
|
end
|
|
|
|
Demon::Sidekiq.demons.values.each do |daemon|
|
|
next if !daemon.already_running?
|
|
|
|
running_sidekiq_process = sidekiq_processes_for_current_hostname[daemon.pid]
|
|
|
|
if !running_sidekiq_process ||
|
|
(Time.now.to_i - running_sidekiq_process["beat"]) >
|
|
SIDEKIQ_HEARTBEAT_CHECK_MISS_THRESHOLD_SECONDS
|
|
Rails.logger.warn("Sidekiq heartbeat test failed for #{daemon.pid}, restarting")
|
|
daemon.restart
|
|
end
|
|
end
|
|
end
|
|
|
|
SIDEKIQ_RSS_MEMORY_CHECK_INTERVAL_SECONDS = 30.minutes
|
|
|
|
def self.rss_memory_check
|
|
if defined?(@@last_sidekiq_rss_memory_check) && @@last_sidekiq_rss_memory_check &&
|
|
@@last_sidekiq_rss_memory_check > Time.now.to_i - SIDEKIQ_RSS_MEMORY_CHECK_INTERVAL_SECONDS
|
|
return @@last_sidekiq_rss_memory_check
|
|
end
|
|
|
|
Demon::Sidekiq.demons.values.each do |daemon|
|
|
next if !daemon.already_running?
|
|
|
|
daemon_rss_bytes = (`ps -o rss= -p #{daemon.pid}`.chomp.to_i || 0) * 1024
|
|
|
|
if daemon_rss_bytes > max_allowed_sidekiq_rss_bytes
|
|
Rails.logger.warn(
|
|
"Sidekiq is consuming too much memory (using: %0.2fM) for '%s', restarting" %
|
|
[(daemon_rss_bytes.to_f / 1.megabyte), HOSTNAME],
|
|
)
|
|
|
|
daemon.restart
|
|
end
|
|
end
|
|
|
|
@@last_sidekiq_rss_memory_check = Time.now.to_i
|
|
end
|
|
|
|
# Given Discourse AI a steady state during streaming is closer
|
|
# to 700MB given tokenization overhead. Set the default a bit higher
|
|
# so only outliers get restarted.
|
|
DEFAULT_MAX_ALLOWED_SIDEKIQ_RSS_MEGABYTES = 1000
|
|
|
|
def self.max_allowed_sidekiq_rss_bytes
|
|
[ENV["UNICORN_SIDEKIQ_MAX_RSS"].to_i, DEFAULT_MAX_ALLOWED_SIDEKIQ_RSS_MEGABYTES].max.megabytes
|
|
end
|
|
|
|
def stop_signal
|
|
"TERM"
|
|
end
|
|
|
|
def stop_timeout
|
|
# Official documentation says that Sidekiq should be given shutdown timeout
|
|
# plus 5 seconds to shutdown cleanly.
|
|
#
|
|
# See https://github.com/sidekiq/sidekiq/wiki/Deployment.
|
|
|
|
SIDEKIQ_SHUTDOWN_TIMEOUT_SECONDS + 5
|
|
end
|
|
|
|
private
|
|
|
|
def suppress_stdout
|
|
false
|
|
end
|
|
|
|
def suppress_stderr
|
|
false
|
|
end
|
|
|
|
def log_in_trap(message, level: :info)
|
|
SignalTrapLogger.instance.log(@logger, message, level: level)
|
|
end
|
|
|
|
def after_fork
|
|
Demon::Sidekiq.after_fork&.call
|
|
SignalTrapLogger.instance.after_fork
|
|
|
|
log("Loading Sidekiq in process id #{Process.pid}")
|
|
require "sidekiq/cli"
|
|
cli = Sidekiq::CLI.instance
|
|
|
|
options = [
|
|
"-c",
|
|
GlobalSetting.sidekiq_workers.to_s,
|
|
"-t",
|
|
SIDEKIQ_SHUTDOWN_TIMEOUT_SECONDS.to_s,
|
|
]
|
|
|
|
[["critical", 8], ["default", 4], ["low", 2], ["ultra_low", 1]].each do |queue_name, weight|
|
|
custom_queue_hostname = ENV["UNICORN_SIDEKIQ_#{queue_name.upcase}_QUEUE_HOSTNAME"]
|
|
|
|
if !custom_queue_hostname || custom_queue_hostname.split(",").include?(Discourse.os_hostname)
|
|
options << "-q"
|
|
options << "#{queue_name},#{weight}"
|
|
end
|
|
end
|
|
|
|
# Sidekiq not as high priority as web, in this environment it is forked so a web is very
|
|
# likely running
|
|
Discourse::Utils.execute_command("renice", "-n", "5", "-p", Process.pid.to_s)
|
|
|
|
cli.parse(options)
|
|
load Rails.root + "config/initializers/100-sidekiq.rb"
|
|
cli.run
|
|
rescue => error
|
|
log(
|
|
"Error encountered while starting Sidekiq: [#{error.class}] #{error.message}\n#{error.backtrace.join("\n")}",
|
|
level: :error,
|
|
)
|
|
|
|
exit 1
|
|
end
|
|
end
|