2019-05-03 06:17:27 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-08-19 12:04:58 +08:00
|
|
|
require 'thread'
|
|
|
|
|
|
|
|
class SidekiqPauser
|
2019-02-14 09:22:40 +08:00
|
|
|
TTL = 60
|
|
|
|
PAUSED_KEY = "sidekiq_is_paused_v2"
|
|
|
|
|
2014-08-19 12:04:58 +08:00
|
|
|
def initialize
|
|
|
|
@mutex = Mutex.new
|
2019-02-14 09:22:40 +08:00
|
|
|
@dbs ||= Set.new
|
2014-08-19 12:04:58 +08:00
|
|
|
end
|
|
|
|
|
2019-02-19 10:55:53 +08:00
|
|
|
def pause!(value = "paused")
|
|
|
|
$redis.setex PAUSED_KEY, TTL, value
|
2019-02-20 09:46:08 +08:00
|
|
|
extend_lease_thread
|
2014-02-13 12:27:04 +08:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2014-08-19 12:04:58 +08:00
|
|
|
def paused?
|
2019-02-14 09:22:40 +08:00
|
|
|
!!$redis.get(PAUSED_KEY)
|
2014-02-13 12:27:04 +08:00
|
|
|
end
|
|
|
|
|
2019-02-14 10:33:58 +08:00
|
|
|
def unpause_all!
|
|
|
|
@mutex.synchronize do
|
|
|
|
@dbs = Set.new
|
|
|
|
stop_extend_lease_thread
|
|
|
|
end
|
|
|
|
|
|
|
|
RailsMultisite::ConnectionManagement.each_connection do
|
|
|
|
unpause! if paused?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def paused_dbs
|
|
|
|
dbs = []
|
2019-02-19 10:55:53 +08:00
|
|
|
|
2019-02-14 10:33:58 +08:00
|
|
|
RailsMultisite::ConnectionManagement.each_connection do
|
|
|
|
dbs << RailsMultisite::ConnectionManagement.current_db if paused?
|
|
|
|
end
|
2019-02-19 10:55:53 +08:00
|
|
|
|
2019-02-14 10:33:58 +08:00
|
|
|
dbs
|
|
|
|
end
|
|
|
|
|
2014-08-19 12:04:58 +08:00
|
|
|
def unpause!
|
|
|
|
@mutex.synchronize do
|
2019-02-14 09:22:40 +08:00
|
|
|
@dbs.delete(RailsMultisite::ConnectionManagement.current_db)
|
2019-02-14 10:33:58 +08:00
|
|
|
stop_extend_lease_thread if @dbs.size == 0
|
2014-08-19 12:04:58 +08:00
|
|
|
end
|
|
|
|
|
2019-02-14 09:22:40 +08:00
|
|
|
$redis.del(PAUSED_KEY)
|
2014-02-13 12:27:04 +08:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2019-02-14 10:33:58 +08:00
|
|
|
def stop_extend_lease_thread
|
|
|
|
# should always be called from a mutex
|
|
|
|
if t = @extend_lease_thread
|
|
|
|
@extend_lease_thread = nil
|
|
|
|
while t.alive?
|
2019-02-19 13:33:34 +08:00
|
|
|
begin
|
|
|
|
t.wakeup
|
|
|
|
rescue ThreadError => e
|
|
|
|
unless e.message =~ /killed thread/
|
|
|
|
raise e
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-02-14 10:33:58 +08:00
|
|
|
sleep 0
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-08-19 13:50:17 +08:00
|
|
|
def extend_lease_thread
|
2019-02-20 09:46:08 +08:00
|
|
|
@mutex.synchronize do
|
|
|
|
@dbs << RailsMultisite::ConnectionManagement.current_db
|
|
|
|
|
|
|
|
@extend_lease_thread ||= Thread.new do
|
|
|
|
while true do
|
2019-02-20 10:25:43 +08:00
|
|
|
break if !@extend_lease_thread
|
2019-02-20 09:46:08 +08:00
|
|
|
|
2019-02-20 10:25:43 +08:00
|
|
|
@mutex.synchronize do
|
2019-02-20 09:46:08 +08:00
|
|
|
@dbs.each do |db|
|
|
|
|
RailsMultisite::ConnectionManagement.with_connection(db) do
|
|
|
|
if !$redis.expire(PAUSED_KEY, TTL)
|
|
|
|
# if it was unpaused in another process we got to remove the
|
|
|
|
# bad key
|
|
|
|
@dbs.delete(db)
|
|
|
|
end
|
|
|
|
end
|
2019-02-14 10:33:58 +08:00
|
|
|
end
|
2019-02-14 09:22:40 +08:00
|
|
|
end
|
2014-08-19 13:50:17 +08:00
|
|
|
|
2019-02-20 09:46:08 +08:00
|
|
|
sleep(Rails.env.test? ? 0.01 : TTL / 2)
|
|
|
|
end
|
2019-02-14 09:22:40 +08:00
|
|
|
end
|
|
|
|
end
|
2014-02-13 12:27:04 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-08-19 12:04:58 +08:00
|
|
|
module Sidekiq
|
|
|
|
@pauser = SidekiqPauser.new
|
2019-02-19 10:55:53 +08:00
|
|
|
|
|
|
|
def self.pause!(key = nil)
|
|
|
|
key ? @pauser.pause!(key) : @pauser.pause!
|
2014-08-19 12:04:58 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.paused?
|
|
|
|
@pauser.paused?
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.unpause!
|
|
|
|
@pauser.unpause!
|
|
|
|
end
|
2019-02-14 10:33:58 +08:00
|
|
|
|
|
|
|
def self.unpause_all!
|
|
|
|
@pauser.unpause_all!
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.paused_dbs
|
|
|
|
@pauser.paused_dbs
|
|
|
|
end
|
2014-08-19 12:04:58 +08:00
|
|
|
end
|
|
|
|
|
2014-02-13 12:27:04 +08:00
|
|
|
# server middleware that will reschedule work whenever Sidekiq is paused
|
|
|
|
class Sidekiq::Pausable
|
|
|
|
|
|
|
|
def initialize(delay = 5.seconds)
|
|
|
|
@delay = delay
|
|
|
|
end
|
|
|
|
|
|
|
|
def call(worker, msg, queue)
|
2019-02-14 09:22:40 +08:00
|
|
|
if sidekiq_paused?(msg) && !(Jobs::RunHeartbeat === worker)
|
2016-01-12 01:31:28 +08:00
|
|
|
worker.class.perform_in(@delay, *msg['args'])
|
2014-02-13 12:27:04 +08:00
|
|
|
else
|
2017-10-23 14:30:17 +08:00
|
|
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
|
|
result = yield
|
|
|
|
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
|
|
DiscourseEvent.trigger(:sidekiq_job_ran, worker, msg, queue, duration)
|
|
|
|
result
|
2014-02-13 12:27:04 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-02-14 09:22:40 +08:00
|
|
|
private
|
|
|
|
|
|
|
|
def sidekiq_paused?(msg)
|
|
|
|
if site_id = msg["args"]&.first&.dig("current_site_id")
|
|
|
|
RailsMultisite::ConnectionManagement.with_connection(site_id) do
|
|
|
|
Sidekiq.paused?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-02-13 12:27:04 +08:00
|
|
|
end
|