2018-10-12 01:03:47 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
require "weakref"
|
|
|
|
|
2014-03-16 19:59:34 -05:00
|
|
|
module Scheduler
|
|
|
|
module Deferrable
|
2018-10-12 01:03:47 -05:00
|
|
|
DEFAULT_TIMEOUT ||= 90
|
2023-01-11 19:29:50 -06:00
|
|
|
STATS_CACHE_SIZE ||= 100
|
2018-10-12 01:03:47 -05:00
|
|
|
|
2014-03-16 19:59:34 -05:00
|
|
|
def initialize
|
2014-08-18 01:42:48 -05:00
|
|
|
@async = !Rails.env.test?
|
2023-07-28 06:53:51 -05:00
|
|
|
@queue =
|
|
|
|
WorkQueue::ThreadSafeWrapper.new(
|
|
|
|
WorkQueue::FairQueue.new(500) { WorkQueue::BoundedQueue.new(10) },
|
|
|
|
)
|
|
|
|
|
2014-03-16 23:22:11 -05:00
|
|
|
@mutex = Mutex.new
|
2023-01-11 19:29:50 -06:00
|
|
|
@stats_mutex = Mutex.new
|
2015-02-16 16:56:21 -06:00
|
|
|
@paused = false
|
2014-03-16 23:22:11 -05:00
|
|
|
@thread = nil
|
2018-10-12 01:03:47 -05:00
|
|
|
@reactor = nil
|
|
|
|
@timeout = DEFAULT_TIMEOUT
|
2023-01-11 19:29:50 -06:00
|
|
|
@stats = LruRedux::ThreadSafeCache.new(STATS_CACHE_SIZE)
|
2018-10-12 01:03:47 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def timeout=(t)
|
|
|
|
@mutex.synchronize { @timeout = t }
|
2015-02-16 16:56:21 -06:00
|
|
|
end
|
|
|
|
|
2017-11-22 22:48:47 -06:00
|
|
|
def length
|
2023-07-28 06:53:51 -05:00
|
|
|
@queue.size
|
2017-11-22 22:48:47 -06:00
|
|
|
end
|
|
|
|
|
2023-01-11 19:29:50 -06:00
|
|
|
def stats
|
|
|
|
@stats_mutex.synchronize { @stats.to_a }
|
|
|
|
end
|
|
|
|
|
2015-02-16 16:56:21 -06:00
|
|
|
def pause
|
|
|
|
stop!
|
|
|
|
@paused = true
|
|
|
|
end
|
2014-03-16 23:22:11 -05:00
|
|
|
|
2015-02-16 16:56:21 -06:00
|
|
|
def resume
|
|
|
|
@paused = false
|
2014-03-16 19:59:34 -05:00
|
|
|
end
|
|
|
|
|
2018-05-22 21:05:17 -05:00
|
|
|
# for test and sidekiq
|
2014-03-16 19:59:34 -05:00
|
|
|
def async=(val)
|
|
|
|
@async = val
|
|
|
|
end
|
|
|
|
|
2023-07-28 06:53:51 -05:00
|
|
|
def later(desc = nil, db = RailsMultisite::ConnectionManagement.current_db, force: true, &blk)
|
2023-01-11 19:29:50 -06:00
|
|
|
@stats_mutex.synchronize do
|
|
|
|
stats = (@stats[desc] ||= { queued: 0, finished: 0, duration: 0, errors: 0 })
|
|
|
|
stats[:queued] += 1
|
|
|
|
end
|
|
|
|
|
2014-03-16 19:59:34 -05:00
|
|
|
if @async
|
2018-10-12 01:03:47 -05:00
|
|
|
start_thread if !@thread&.alive? && !@paused
|
2023-07-28 06:53:51 -05:00
|
|
|
@queue.push({ key: db, task: [db, blk, desc] }, force: force)
|
2014-03-16 19:59:34 -05:00
|
|
|
else
|
|
|
|
blk.call
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def stop!
|
2018-05-15 02:51:32 -05:00
|
|
|
@thread.kill if @thread&.alive?
|
2015-02-16 16:56:21 -06:00
|
|
|
@thread = nil
|
2018-10-12 01:03:47 -05:00
|
|
|
@reactor&.stop
|
|
|
|
@reactor = nil
|
2014-03-16 19:59:34 -05:00
|
|
|
end
|
|
|
|
|
2014-03-16 23:22:11 -05:00
|
|
|
# test only
|
|
|
|
def stopped?
|
2018-05-15 02:51:32 -05:00
|
|
|
!@thread&.alive?
|
2015-02-16 16:56:21 -06:00
|
|
|
end
|
|
|
|
|
|
|
|
def do_all_work
|
2023-07-28 06:53:51 -05:00
|
|
|
do_work(non_block = true) while !@queue.empty?
|
2014-03-16 23:22:11 -05:00
|
|
|
end
|
|
|
|
|
2014-03-16 19:59:34 -05:00
|
|
|
private
|
|
|
|
|
2014-03-16 23:22:11 -05:00
|
|
|
def start_thread
|
|
|
|
@mutex.synchronize do
|
2018-10-12 01:03:47 -05:00
|
|
|
@reactor = MessageBus::TimerThread.new if !@reactor
|
2023-01-15 16:08:44 -06:00
|
|
|
@thread =
|
|
|
|
Thread.new do
|
|
|
|
@thread.abort_on_exception = true if Rails.env.test?
|
|
|
|
do_work while true
|
|
|
|
end if !@thread&.alive?
|
2014-03-16 23:22:11 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-02-16 16:56:21 -06:00
|
|
|
# using non_block to match Ruby #deq
|
|
|
|
def do_work(non_block = false)
|
2023-07-28 06:53:51 -05:00
|
|
|
db, job, desc = @queue.shift(block: !non_block)[:task]
|
|
|
|
|
2023-01-11 19:29:50 -06:00
|
|
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
2017-10-11 04:19:26 -05:00
|
|
|
db ||= RailsMultisite::ConnectionManagement::DEFAULT
|
2017-10-11 04:17:03 -05:00
|
|
|
|
2017-10-11 04:45:19 -05:00
|
|
|
RailsMultisite::ConnectionManagement.with_connection(db) do
|
2017-10-11 04:19:26 -05:00
|
|
|
begin
|
2018-10-12 01:03:47 -05:00
|
|
|
warning_job =
|
|
|
|
@reactor.queue(@timeout) do
|
|
|
|
Rails.logger.error "'#{desc}' is still running after #{@timeout} seconds on db #{db}, this process may need to be restarted!"
|
|
|
|
end if !non_block
|
2017-10-11 04:17:03 -05:00
|
|
|
job.call
|
2017-10-11 04:19:26 -05:00
|
|
|
rescue => ex
|
2023-01-11 19:29:50 -06:00
|
|
|
@stats_mutex.synchronize do
|
|
|
|
stats = @stats[desc]
|
|
|
|
stats[:errors] += 1 if stats
|
|
|
|
end
|
2017-10-11 04:19:26 -05:00
|
|
|
Discourse.handle_job_exception(ex, message: "Running deferred code '#{desc}'")
|
2018-10-12 01:03:47 -05:00
|
|
|
ensure
|
|
|
|
warning_job&.cancel
|
2017-10-11 04:17:03 -05:00
|
|
|
end
|
2014-07-17 15:22:46 -05:00
|
|
|
end
|
2014-03-16 19:59:34 -05:00
|
|
|
rescue => ex
|
2015-02-09 14:47:46 -06:00
|
|
|
Discourse.handle_job_exception(ex, message: "Processing deferred code queue")
|
2018-06-19 04:58:21 -05:00
|
|
|
ensure
|
|
|
|
ActiveRecord::Base.connection_handler.clear_active_connections!
|
2023-01-15 16:08:44 -06:00
|
|
|
if start
|
|
|
|
@stats_mutex.synchronize do
|
|
|
|
stats = @stats[desc]
|
|
|
|
if stats
|
|
|
|
stats[:finished] += 1
|
|
|
|
stats[:duration] += Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
|
|
|
end
|
2023-01-11 19:29:50 -06:00
|
|
|
end
|
|
|
|
end
|
2014-03-16 19:59:34 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Defer
|
|
|
|
extend Deferrable
|
|
|
|
initialize
|
|
|
|
end
|
|
|
|
end
|