diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-21 00:17:26 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-21 00:17:26 +0300 |
commit | 7263a0140abe6e08a26ff6939be4add3ba0af064 (patch) | |
tree | f250b839e52e13176483867fb1a96bdbca737a77 | |
parent | b420826c09cb69fd3db101b11e0482c896de73f7 (diff) |
Add latest changes from gitlab-org/gitlab@master
-rw-r--r-- | app/assets/javascripts/issues_list/components/issues_list_app.vue | 4 | ||||
-rw-r--r-- | app/assets/javascripts/issues_list/constants.js | 9 | ||||
-rw-r--r-- | app/assets/javascripts/issues_list/queries/get_issues.query.graphql | 6 | ||||
-rw-r--r-- | app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql | 14 | ||||
-rw-r--r-- | app/assets/javascripts/issues_list/utils.js | 14 | ||||
-rw-r--r-- | config/feature_flags/development/atomic_sidekiq_scheduler.yml | 8 | ||||
-rw-r--r-- | doc/user/admin_area/monitoring/health_check.md | 5 | ||||
-rw-r--r-- | lib/gitlab/runtime.rb | 7 | ||||
-rw-r--r-- | lib/gitlab/sidekiq_enq.rb | 73 | ||||
-rw-r--r-- | spec/frontend/issues_list/mock_data.js | 34 | ||||
-rw-r--r-- | spec/lib/gitlab/runtime_spec.rb | 2 | ||||
-rw-r--r-- | spec/lib/gitlab/sidekiq_enq_spec.rb | 93 |
12 files changed, 234 insertions, 35 deletions
diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues_list/components/issues_list_app.vue index 7b51f6ee46a..2e5b912f4de 100644 --- a/app/assets/javascripts/issues_list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues_list/components/issues_list_app.vue @@ -288,6 +288,7 @@ export default { avatar_url: gon.current_user_avatar_url, }); } + const tokens = [ { type: TOKEN_TYPE_AUTHOR, @@ -297,7 +298,6 @@ export default { dataType: 'user', unique: true, defaultAuthors: [], - operators: OPERATOR_IS_ONLY, fetchAuthors: this.fetchUsers, preloadedAuthors, }, @@ -333,7 +333,6 @@ export default { title: TOKEN_TITLE_TYPE, icon: 'issues', token: GlFilteredSearchToken, - operators: OPERATOR_IS_ONLY, options: [ { icon: 'issue-type-issue', title: 'issue', value: 'issue' }, { icon: 'issue-type-incident', title: 'incident', value: 'incident' }, @@ -349,7 +348,6 @@ export default { icon: 'thumb-up', token: EmojiToken, unique: true, - operators: OPERATOR_IS_ONLY, fetchEmojis: this.fetchEmojis, }); diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues_list/constants.js index 5bdc1bd9f90..c1949c40373 100644 --- a/app/assets/javascripts/issues_list/constants.js +++ b/app/assets/javascripts/issues_list/constants.js @@ -271,6 +271,7 @@ export const filters = { [OPERATOR_IS]: { [NORMAL_FILTER]: 'label_name[]', [SPECIAL_FILTER]: 'label_name[]', + [ALTERNATIVE_FILTER]: 'label_name', }, [OPERATOR_IS_NOT]: { [NORMAL_FILTER]: 'not[label_name][]', @@ -280,12 +281,13 @@ export const filters = { [TOKEN_TYPE_TYPE]: { [API_PARAM]: { [NORMAL_FILTER]: 'types', - [SPECIAL_FILTER]: 'types', }, [URL_PARAM]: { [OPERATOR_IS]: { [NORMAL_FILTER]: 'type[]', - [SPECIAL_FILTER]: 'type[]', + }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[type][]', }, }, }, @@ -299,6 +301,9 @@ export const filters = { [NORMAL_FILTER]: 'my_reaction_emoji', [SPECIAL_FILTER]: 'my_reaction_emoji', }, + [OPERATOR_IS_NOT]: { + [NORMAL_FILTER]: 'not[my_reaction_emoji]', + }, }, }, [TOKEN_TYPE_CONFIDENTIAL]: { diff --git a/app/assets/javascripts/issues_list/queries/get_issues.query.graphql b/app/assets/javascripts/issues_list/queries/get_issues.query.graphql index 6df72cf6596..a7517b88c5a 100644 --- a/app/assets/javascripts/issues_list/queries/get_issues.query.graphql +++ b/app/assets/javascripts/issues_list/queries/get_issues.query.graphql @@ -11,9 +11,11 @@ query getIssues( $assigneeId: String $assigneeUsernames: [String!] $authorUsername: String + $confidential: Boolean $labelName: [String] $milestoneTitle: [String] $milestoneWildcardId: MilestoneWildcardId + $myReactionEmoji: String $types: [IssueType!] $not: NegatedIssueFilterInput $beforeCursor: String @@ -30,9 +32,11 @@ query getIssues( assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + confidential: $confidential labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji types: $types not: $not before: $beforeCursor @@ -57,9 +61,11 @@ query getIssues( assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + confidential: $confidential labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji types: $types not: $not before: $beforeCursor diff --git a/app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql b/app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql index 7bcdbbb28fc..28e62f0859e 100644 --- a/app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql +++ b/app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql @@ -5,9 +5,11 @@ query getIssuesCount( $assigneeId: String $assigneeUsernames: [String!] $authorUsername: String + $confidential: Boolean $labelName: [String] $milestoneTitle: [String] $milestoneWildcardId: MilestoneWildcardId + $myReactionEmoji: String $types: [IssueType!] $not: NegatedIssueFilterInput ) { @@ -19,9 +21,11 @@ query getIssuesCount( assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + confidential: $confidential labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji types: $types not: $not ) { @@ -34,9 +38,11 @@ query getIssuesCount( assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + confidential: $confidential labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji types: $types not: $not ) { @@ -49,9 +55,11 @@ query getIssuesCount( assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + confidential: $confidential labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji types: $types not: $not ) { @@ -65,9 +73,11 @@ query getIssuesCount( assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + confidential: $confidential labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji types: $types not: $not ) { @@ -79,9 +89,11 @@ query getIssuesCount( assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + confidential: $confidential labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji types: $types not: $not ) { @@ -93,9 +105,11 @@ query getIssuesCount( assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames authorUsername: $authorUsername + confidential: $confidential labelName: $labelName milestoneTitle: $milestoneTitle milestoneWildcardId: $milestoneWildcardId + myReactionEmoji: $myReactionEmoji types: $types not: $not ) { diff --git a/app/assets/javascripts/issues_list/utils.js b/app/assets/javascripts/issues_list/utils.js index 1d3d07475af..edb9eeba095 100644 --- a/app/assets/javascripts/issues_list/utils.js +++ b/app/assets/javascripts/issues_list/utils.js @@ -22,6 +22,7 @@ import { SPECIAL_FILTER, SPECIAL_FILTER_VALUES, TOKEN_TYPE_ASSIGNEE, + TOKEN_TYPE_CONFIDENTIAL, TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_TYPE, @@ -200,10 +201,15 @@ const isWildcardValue = (tokenType, value) => const requiresUpperCaseValue = (tokenType, value) => tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value); -const formatData = (token) => - requiresUpperCaseValue(token.type, token.value.data) - ? token.value.data.toUpperCase() - : token.value.data; +const formatData = (token) => { + if (requiresUpperCaseValue(token.type, token.value.data)) { + return token.value.data.toUpperCase(); + } + if (token.type === TOKEN_TYPE_CONFIDENTIAL) { + return token.value.data === 'yes'; + } + return token.value.data; +}; export const convertToApiParams = (filterTokens) => { const params = {}; diff --git a/config/feature_flags/development/atomic_sidekiq_scheduler.yml b/config/feature_flags/development/atomic_sidekiq_scheduler.yml new file mode 100644 index 00000000000..ab516f61144 --- /dev/null +++ b/config/feature_flags/development/atomic_sidekiq_scheduler.yml @@ -0,0 +1,8 @@ +--- +name: atomic_sidekiq_scheduler +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72380 +rollout_issue_url: +milestone: '14.5' +type: development +group: group::project management +default_enabled: false diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md index c5ffb032afd..1d2d7be146c 100644 --- a/doc/user/admin_area/monitoring/health_check.md +++ b/doc/user/admin_area/monitoring/health_check.md @@ -1,8 +1,7 @@ --- -stage: none -group: unassigned +stage: Monitor +group: Monitor info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments -type: concepts, howto --- # Health Check **(FREE SELF)** diff --git a/lib/gitlab/runtime.rb b/lib/gitlab/runtime.rb index f60cac0aff0..56b64b748d4 100644 --- a/lib/gitlab/runtime.rb +++ b/lib/gitlab/runtime.rb @@ -84,9 +84,12 @@ module Gitlab if puma? && Puma.respond_to?(:cli_config) threads += Puma.cli_config.options[:max_threads] elsif sidekiq? - # An extra thread for the poller in Sidekiq Cron: + # 2 extra threads for the pollers in Sidekiq and Sidekiq Cron: # https://github.com/ondrejbartas/sidekiq-cron#under-the-hood - threads += Sidekiq.options[:concurrency] + 1 + # + # These threads execute Sidekiq client middleware when jobs + # are enqueued and those can access DB / Redis. + threads += Sidekiq.options[:concurrency] + 2 end if action_cable? diff --git a/lib/gitlab/sidekiq_enq.rb b/lib/gitlab/sidekiq_enq.rb index d8a01ac8ef4..e498ff3ab25 100644 --- a/lib/gitlab/sidekiq_enq.rb +++ b/lib/gitlab/sidekiq_enq.rb @@ -1,16 +1,37 @@ # frozen_string_literal: true -# This is a copy of https://github.com/mperham/sidekiq/blob/32c55e31659a1e6bd42f98334cca5eef2863de8d/lib/sidekiq/scheduled.rb#L11-L34 -# -# It effectively reverts -# https://github.com/mperham/sidekiq/commit/9b75467b33759888753191413eddbc15c37a219e -# because we observe that the extra ZREMs caused by this change can lead to high -# CPU usage on Redis at peak times: -# https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1179 -# module Gitlab class SidekiqEnq + LUA_ZPOPBYSCORE = <<~EOS + local key, now = KEYS[1], ARGV[1] + local jobs = redis.call("zrangebyscore", key, "-inf", now, "limit", 0, 1) + if jobs[1] then + redis.call("zrem", key, jobs[1]) + return jobs[1] + end + EOS + + LUA_ZPOPBYSCORE_SHA = Digest::SHA1.hexdigest(LUA_ZPOPBYSCORE) + def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = Sidekiq::Scheduled::SETS) + if Feature.enabled?(:atomic_sidekiq_scheduler, default_enabled: :yaml) + atomic_find_jobs_and_enqueue(now, sorted_sets) + else + find_jobs_and_enqueue(now, sorted_sets) + end + end + + private + + # This is a copy of https://github.com/mperham/sidekiq/blob/32c55e31659a1e6bd42f98334cca5eef2863de8d/lib/sidekiq/scheduled.rb#L11-L34 + # + # It effectively reverts + # https://github.com/mperham/sidekiq/commit/9b75467b33759888753191413eddbc15c37a219e + # because we observe that the extra ZREMs caused by this change can lead to high + # CPU usage on Redis at peak times: + # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1179 + # + def find_jobs_and_enqueue(now, sorted_sets) # A job's "score" in Redis is the time at which it should be processed. # Just check Redis for the set of jobs with a timestamp before now. Sidekiq.redis do |conn| @@ -24,8 +45,7 @@ module Gitlab # We need to go through the list one at a time to reduce the risk of something # going wrong between the time jobs are popped from the scheduled queue and when # they are pushed onto a work queue and losing the jobs. - while (job = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 1]).first) - + while job = conn.zrangebyscore(sorted_set, "-inf", now, limit: [0, 1]).first # Pop item off the queue and add it to the work queue. If the job can't be popped from # the queue, it's because another process already popped it so we can move on to the # next one. @@ -47,5 +67,38 @@ module Gitlab end end end + + def atomic_find_jobs_and_enqueue(now, sorted_sets) + Sidekiq.redis do |conn| + sorted_sets.each do |sorted_set| + start_time = ::Gitlab::Metrics::System.monotonic_time + jobs = 0 + + Sidekiq.logger.info(message: 'Atomically enqueuing scheduled jobs', status: 'start', sorted_set: sorted_set) + + while job = redis_eval_lua(conn, LUA_ZPOPBYSCORE, LUA_ZPOPBYSCORE_SHA, keys: [sorted_set], argv: [now]) + jobs += 1 + Sidekiq::Client.push(Sidekiq.load_json(job)) + end + + end_time = ::Gitlab::Metrics::System.monotonic_time + Sidekiq.logger.info(message: 'Atomically enqueuing scheduled jobs', + status: 'done', + sorted_set: sorted_set, + jobs_count: jobs, + duration_s: end_time - start_time) + end + end + end + + def redis_eval_lua(conn, script, sha, keys: nil, argv: nil) + conn.evalsha(sha, keys: keys, argv: argv) + rescue ::Redis::CommandError => e + if e.message.start_with?('NOSCRIPT') + conn.eval(script, keys: keys, argv: argv) + else + raise + end + end end end diff --git a/spec/frontend/issues_list/mock_data.js b/spec/frontend/issues_list/mock_data.js index 3be256d8094..c62050c3563 100644 --- a/spec/frontend/issues_list/mock_data.js +++ b/spec/frontend/issues_list/mock_data.js @@ -101,8 +101,13 @@ export const locationSearch = [ 'label_name[]=tv', 'not[label_name][]=live action', 'not[label_name][]=drama', + 'type[]=issue', + 'type[]=feature', + 'not[type][]=bug', + 'not[type][]=incident', 'my_reaction_emoji=thumbsup', - 'confidential=no', + 'not[my_reaction_emoji]=thumbsdown', + 'confidential=yes', 'iteration_id=4', 'not[iteration_id]=20', 'epic_id=12', @@ -114,10 +119,9 @@ export const locationSearch = [ export const locationSearchWithSpecialValues = [ 'assignee_id=123', 'assignee_username=bart', - 'type[]=issue', - 'type[]=incident', 'my_reaction_emoji=None', 'iteration_id=Current', + 'label_name[]=None', 'milestone_title=Upcoming', 'epic_id=None', 'weight=None', @@ -136,8 +140,13 @@ export const filteredTokens = [ { type: 'labels', value: { data: 'tv', operator: OPERATOR_IS } }, { type: 'labels', value: { data: 'live action', operator: OPERATOR_IS_NOT } }, { type: 'labels', value: { data: 'drama', operator: OPERATOR_IS_NOT } }, + { type: 'type', value: { data: 'issue', operator: OPERATOR_IS } }, + { type: 'type', value: { data: 'feature', operator: OPERATOR_IS } }, + { type: 'type', value: { data: 'bug', operator: OPERATOR_IS_NOT } }, + { type: 'type', value: { data: 'incident', operator: OPERATOR_IS_NOT } }, { type: 'my_reaction_emoji', value: { data: 'thumbsup', operator: OPERATOR_IS } }, - { type: 'confidential', value: { data: 'no', operator: OPERATOR_IS } }, + { type: 'my_reaction_emoji', value: { data: 'thumbsdown', operator: OPERATOR_IS_NOT } }, + { type: 'confidential', value: { data: 'yes', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: '4', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: '20', operator: OPERATOR_IS_NOT } }, { type: 'epic_id', value: { data: '12', operator: OPERATOR_IS } }, @@ -151,10 +160,9 @@ export const filteredTokens = [ export const filteredTokensWithSpecialValues = [ { type: 'assignee_username', value: { data: '123', operator: OPERATOR_IS } }, { type: 'assignee_username', value: { data: 'bart', operator: OPERATOR_IS } }, - { type: 'type', value: { data: 'issue', operator: OPERATOR_IS } }, - { type: 'type', value: { data: 'incident', operator: OPERATOR_IS } }, { type: 'my_reaction_emoji', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'iteration', value: { data: 'Current', operator: OPERATOR_IS } }, + { type: 'labels', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'milestone', value: { data: 'Upcoming', operator: OPERATOR_IS } }, { type: 'epic_id', value: { data: 'None', operator: OPERATOR_IS } }, { type: 'weight', value: { data: 'None', operator: OPERATOR_IS } }, @@ -165,8 +173,9 @@ export const apiParams = { assigneeUsernames: ['bart', 'lisa'], milestoneTitle: 'season 4', labelName: ['cartoon', 'tv'], + types: ['ISSUE', 'FEATURE'], myReactionEmoji: 'thumbsup', - confidential: 'no', + confidential: true, iterationId: '4', epicId: '12', weight: '1', @@ -175,6 +184,8 @@ export const apiParams = { assigneeUsernames: ['patty', 'selma'], milestoneTitle: 'season 20', labelName: ['live action', 'drama'], + types: ['BUG', 'INCIDENT'], + myReactionEmoji: 'thumbsdown', iterationId: '20', epicId: '34', weight: '3', @@ -184,7 +195,7 @@ export const apiParams = { export const apiParamsWithSpecialValues = { assigneeId: '123', assigneeUsernames: 'bart', - types: ['ISSUE', 'INCIDENT'], + labelName: 'None', myReactionEmoji: 'None', iterationWildcardId: 'CURRENT', milestoneWildcardId: 'UPCOMING', @@ -201,8 +212,11 @@ export const urlParams = { 'not[milestone_title]': 'season 20', 'label_name[]': ['cartoon', 'tv'], 'not[label_name][]': ['live action', 'drama'], + 'type[]': ['issue', 'feature'], + 'not[type][]': ['bug', 'incident'], my_reaction_emoji: 'thumbsup', - confidential: 'no', + 'not[my_reaction_emoji]': 'thumbsdown', + confidential: 'yes', iteration_id: '4', 'not[iteration_id]': '20', epic_id: '12', @@ -214,7 +228,7 @@ export const urlParams = { export const urlParamsWithSpecialValues = { assignee_id: '123', 'assignee_username[]': 'bart', - 'type[]': ['issue', 'incident'], + 'label_name[]': 'None', my_reaction_emoji: 'None', iteration_id: 'Current', milestone_title: 'Upcoming', diff --git a/spec/lib/gitlab/runtime_spec.rb b/spec/lib/gitlab/runtime_spec.rb index f51c5dd3d20..96bc5448790 100644 --- a/spec/lib/gitlab/runtime_spec.rb +++ b/spec/lib/gitlab/runtime_spec.rb @@ -108,7 +108,7 @@ RSpec.describe Gitlab::Runtime do allow(sidekiq_type).to receive(:options).and_return(concurrency: 2) end - it_behaves_like "valid runtime", :sidekiq, 4 + it_behaves_like "valid runtime", :sidekiq, 5 end context "console" do diff --git a/spec/lib/gitlab/sidekiq_enq_spec.rb b/spec/lib/gitlab/sidekiq_enq_spec.rb new file mode 100644 index 00000000000..6903f01bf5f --- /dev/null +++ b/spec/lib/gitlab/sidekiq_enq_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::SidekiqEnq, :clean_gitlab_redis_queues do + let(:retry_set) { Sidekiq::Scheduled::SETS.first } + let(:schedule_set) { Sidekiq::Scheduled::SETS.last } + + around do |example| + freeze_time { example.run } + end + + shared_examples 'finds jobs that are due and enqueues them' do + before do + Sidekiq.redis do |redis| + redis.zadd(retry_set, (Time.current - 1.day).to_f.to_s, '{"jid": 1}') + redis.zadd(retry_set, Time.current.to_f.to_s, '{"jid": 2}') + redis.zadd(retry_set, (Time.current + 1.day).to_f.to_s, '{"jid": 3}') + + redis.zadd(schedule_set, (Time.current - 1.day).to_f.to_s, '{"jid": 4}') + redis.zadd(schedule_set, Time.current.to_f.to_s, '{"jid": 5}') + redis.zadd(schedule_set, (Time.current + 1.day).to_f.to_s, '{"jid": 6}') + end + end + + it 'enqueues jobs that are due' do + expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 1 }) + expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 2 }) + expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 4 }) + expect(Sidekiq::Client).to receive(:push).with({ 'jid' => 5 }) + + Gitlab::SidekiqEnq.new.enqueue_jobs + + Sidekiq.redis do |redis| + expect(redis.zscan_each(retry_set).map(&:first)).to contain_exactly('{"jid": 3}') + expect(redis.zscan_each(schedule_set).map(&:first)).to contain_exactly('{"jid": 6}') + end + end + end + + context 'when atomic_sidekiq_scheduler is disabled' do + before do + stub_feature_flags(atomic_sidekiq_scheduler: false) + end + + it_behaves_like 'finds jobs that are due and enqueues them' + + context 'when ZRANGEBYSCORE returns a job that is already removed by another process' do + before do + Sidekiq.redis do |redis| + redis.zadd(schedule_set, Time.current.to_f.to_s, '{"jid": 1}') + + allow(redis).to receive(:zrangebyscore).and_wrap_original do |m, *args, **kwargs| + m.call(*args, **kwargs).tap do |jobs| + redis.zrem(schedule_set, jobs.first) if args[0] == schedule_set && jobs.first + end + end + end + end + + it 'calls ZREM but does not enqueue the job' do + Sidekiq.redis do |redis| + expect(redis).to receive(:zrem).with(schedule_set, '{"jid": 1}').twice.and_call_original + end + expect(Sidekiq::Client).not_to receive(:push) + + Gitlab::SidekiqEnq.new.enqueue_jobs + end + end + end + + context 'when atomic_sidekiq_scheduler is enabled' do + before do + stub_feature_flags(atomic_sidekiq_scheduler: true) + end + + context 'when Lua script is not yet loaded' do + before do + Gitlab::Redis::Queues.with { |redis| redis.script(:flush) } + end + + it_behaves_like 'finds jobs that are due and enqueues them' + end + + context 'when Lua script is already loaded' do + before do + Gitlab::SidekiqEnq.new.enqueue_jobs + end + + it_behaves_like 'finds jobs that are due and enqueues them' + end + end +end |