2018-12-13 13:39:08 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
module Gitlab
|
|
|
|
# The SidekiqStatus module and its child classes can be used for checking if a
|
|
|
|
# Sidekiq job has been processed or not.
|
|
|
|
#
|
|
|
|
# To check if a job has been completed, simply pass the job ID to the
|
|
|
|
# `completed?` method:
|
|
|
|
#
|
2021-12-11 22:18:48 +05:30
|
|
|
# job_id = SomeWorker.with_status.perform_async(...)
|
2017-08-17 22:00:37 +05:30
|
|
|
#
|
|
|
|
# if Gitlab::SidekiqStatus.completed?(job_id)
|
|
|
|
# ...
|
|
|
|
# end
|
|
|
|
#
|
2021-12-11 22:18:48 +05:30
|
|
|
# If you do not use `with_status`, and the worker class does not declare
|
|
|
|
# `status_expiration` in its `sidekiq_options`, then this status will not be
|
|
|
|
# stored.
|
|
|
|
#
|
2017-08-17 22:00:37 +05:30
|
|
|
# For each job ID registered a separate key is stored in Redis, making lookups
|
|
|
|
# much faster than using Sidekiq's built-in job finding/status API. These keys
|
|
|
|
# expire after a certain period of time to prevent storing too many keys in
|
|
|
|
# Redis.
|
|
|
|
module SidekiqStatus
|
2019-12-04 20:38:33 +05:30
|
|
|
STATUS_KEY = 'gitlab-sidekiq-status:%s'
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
# The default time (in seconds) after which a status key is expired
|
|
|
|
# automatically. The default of 30 minutes should be more than sufficient
|
|
|
|
# for most jobs.
|
|
|
|
DEFAULT_EXPIRATION = 30.minutes.to_i
|
|
|
|
|
|
|
|
# Starts tracking of the given job.
|
|
|
|
#
|
|
|
|
# jid - The Sidekiq job ID
|
|
|
|
# expire - The expiration time of the Redis key.
|
2022-03-02 08:16:31 +05:30
|
|
|
def self.set(jid, expire = DEFAULT_EXPIRATION)
|
|
|
|
return unless expire
|
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
with_redis do |redis|
|
2022-03-02 08:16:31 +05:30
|
|
|
redis.set(key_for(jid), 1, ex: expire)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Stops the tracking of the given job.
|
|
|
|
#
|
|
|
|
# jid - The Sidekiq job ID to remove.
|
|
|
|
def self.unset(jid)
|
2022-07-23 23:45:48 +05:30
|
|
|
with_redis do |redis|
|
2017-08-17 22:00:37 +05:30
|
|
|
redis.del(key_for(jid))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns true if all the given job have been completed.
|
|
|
|
#
|
|
|
|
# job_ids - The Sidekiq job IDs to check.
|
|
|
|
#
|
|
|
|
# Returns true or false.
|
|
|
|
def self.all_completed?(job_ids)
|
2020-10-24 23:57:45 +05:30
|
|
|
self.num_running(job_ids) == 0
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2019-09-30 21:07:59 +05:30
|
|
|
# Returns true if the given job is running or enqueued.
|
2018-03-17 18:26:18 +05:30
|
|
|
#
|
|
|
|
# job_id - The Sidekiq job ID to check.
|
|
|
|
def self.running?(job_id)
|
|
|
|
num_running([job_id]) > 0
|
|
|
|
end
|
|
|
|
|
2019-09-30 21:07:59 +05:30
|
|
|
# Returns the number of jobs that are running or enqueued.
|
2017-08-17 22:00:37 +05:30
|
|
|
#
|
|
|
|
# job_ids - The Sidekiq job IDs to check.
|
|
|
|
def self.num_running(job_ids)
|
|
|
|
responses = self.job_status(job_ids)
|
|
|
|
|
2021-06-08 01:23:25 +05:30
|
|
|
responses.count(&:present?)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the number of jobs that have completed.
|
|
|
|
#
|
|
|
|
# job_ids - The Sidekiq job IDs to check.
|
|
|
|
def self.num_completed(job_ids)
|
|
|
|
job_ids.size - self.num_running(job_ids)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the job status for each of the given job IDs.
|
|
|
|
#
|
|
|
|
# job_ids - The Sidekiq job IDs to check.
|
|
|
|
#
|
|
|
|
# Returns an array of true or false indicating job completion.
|
2019-09-30 21:07:59 +05:30
|
|
|
# true = job is still running or enqueued
|
2017-08-17 22:00:37 +05:30
|
|
|
# false = job completed
|
|
|
|
def self.job_status(job_ids)
|
2022-01-26 12:08:38 +05:30
|
|
|
return [] if job_ids.empty?
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
keys = job_ids.map { |jid| key_for(jid) }
|
2022-01-26 12:08:38 +05:30
|
|
|
|
2022-07-23 23:45:48 +05:30
|
|
|
with_redis { |redis| redis.mget(*keys) }
|
2022-03-02 08:16:31 +05:30
|
|
|
.map { |result| !result.nil? }
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
# Returns the JIDs that are completed
|
|
|
|
#
|
|
|
|
# job_ids - The Sidekiq job IDs to check.
|
|
|
|
#
|
|
|
|
# Returns an array of completed JIDs
|
|
|
|
def self.completed_jids(job_ids)
|
2018-03-17 18:26:18 +05:30
|
|
|
statuses = job_status(job_ids)
|
|
|
|
|
|
|
|
completed = []
|
|
|
|
job_ids.zip(statuses).each do |job_id, status|
|
|
|
|
completed << job_id unless status
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2018-03-17 18:26:18 +05:30
|
|
|
|
|
|
|
completed
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def self.key_for(jid)
|
|
|
|
STATUS_KEY % jid
|
|
|
|
end
|
2022-07-23 23:45:48 +05:30
|
|
|
|
|
|
|
def self.with_redis
|
|
|
|
if Feature.enabled?(:use_primary_and_secondary_stores_for_sidekiq_status) ||
|
|
|
|
Feature.enabled?(:use_primary_store_as_default_for_sidekiq_status)
|
|
|
|
# TODO: Swap for Gitlab::Redis::SharedState after store transition
|
|
|
|
# https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/923
|
|
|
|
Gitlab::Redis::SidekiqStatus.with { |redis| yield redis }
|
|
|
|
else
|
|
|
|
# Keep the old behavior intact if neither feature flag is turned on
|
2022-11-25 23:54:43 +05:30
|
|
|
Sidekiq.redis { |redis| yield redis } # rubocop:disable Cop/SidekiqRedisCall
|
2022-07-23 23:45:48 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
private_class_method :with_redis
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|