gitlab-sidekiq-fetcher 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -1
- data/{RELEASE-GITLAB.md → README-GITLAB.md} +0 -0
- data/README.md +4 -15
- data/gitlab-sidekiq-fetcher.gemspec +4 -2
- data/lib/sidekiq-reliable-fetch.rb +2 -3
- data/lib/sidekiq-reliable-fetch/api.rb +56 -0
- data/lib/sidekiq-reliable-fetch/web.rb +24 -0
- data/lib/sidekiq/reliable_fetcher.rb +143 -0
- data/web/views/working_queue.erb +25 -0
- data/web/views/working_queues.erb +17 -0
- metadata +10 -21
- data/.gitlab-ci.yml +0 -53
- data/.rspec +0 -1
- data/Gemfile +0 -12
- data/Gemfile.lock +0 -50
- data/lib/sidekiq/base_reliable_fetch.rb +0 -185
- data/lib/sidekiq/reliable_fetch.rb +0 -40
- data/lib/sidekiq/semi_reliable_fetch.rb +0 -44
- data/spec/base_reliable_fetch_spec.rb +0 -73
- data/spec/fetch_shared_examples.rb +0 -118
- data/spec/reliable_fetch_spec.rb +0 -7
- data/spec/semi_reliable_fetch_spec.rb +0 -7
- data/spec/spec_helper.rb +0 -115
- data/test/README.md +0 -34
- data/test/config.rb +0 -31
- data/test/reliability_test.rb +0 -116
- data/test/worker.rb +0 -26
data/.rspec
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
--require spec_helper
|
data/Gemfile
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
6
|
-
|
7
|
-
group :test do
|
8
|
-
gem "rspec", '~> 3'
|
9
|
-
gem "pry"
|
10
|
-
gem "sidekiq", '~> 5.0'
|
11
|
-
gem 'simplecov', require: false
|
12
|
-
end
|
data/Gemfile.lock
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
GEM
|
2
|
-
remote: https://rubygems.org/
|
3
|
-
specs:
|
4
|
-
coderay (1.1.2)
|
5
|
-
connection_pool (2.2.2)
|
6
|
-
diff-lcs (1.3)
|
7
|
-
docile (1.3.1)
|
8
|
-
json (2.1.0)
|
9
|
-
method_source (0.9.0)
|
10
|
-
pry (0.11.3)
|
11
|
-
coderay (~> 1.1.0)
|
12
|
-
method_source (~> 0.9.0)
|
13
|
-
rack (2.0.5)
|
14
|
-
rack-protection (2.0.4)
|
15
|
-
rack
|
16
|
-
redis (4.0.2)
|
17
|
-
rspec (3.8.0)
|
18
|
-
rspec-core (~> 3.8.0)
|
19
|
-
rspec-expectations (~> 3.8.0)
|
20
|
-
rspec-mocks (~> 3.8.0)
|
21
|
-
rspec-core (3.8.0)
|
22
|
-
rspec-support (~> 3.8.0)
|
23
|
-
rspec-expectations (3.8.1)
|
24
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
25
|
-
rspec-support (~> 3.8.0)
|
26
|
-
rspec-mocks (3.8.0)
|
27
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
28
|
-
rspec-support (~> 3.8.0)
|
29
|
-
rspec-support (3.8.0)
|
30
|
-
sidekiq (5.2.2)
|
31
|
-
connection_pool (~> 2.2, >= 2.2.2)
|
32
|
-
rack-protection (>= 1.5.0)
|
33
|
-
redis (>= 3.3.5, < 5)
|
34
|
-
simplecov (0.16.1)
|
35
|
-
docile (~> 1.1)
|
36
|
-
json (>= 1.8, < 3)
|
37
|
-
simplecov-html (~> 0.10.0)
|
38
|
-
simplecov-html (0.10.2)
|
39
|
-
|
40
|
-
PLATFORMS
|
41
|
-
ruby
|
42
|
-
|
43
|
-
DEPENDENCIES
|
44
|
-
pry
|
45
|
-
rspec (~> 3)
|
46
|
-
sidekiq (~> 5.0)
|
47
|
-
simplecov
|
48
|
-
|
49
|
-
BUNDLED WITH
|
50
|
-
1.17.1
|
@@ -1,185 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sidekiq
|
4
|
-
class BaseReliableFetch
|
5
|
-
DEFAULT_CLEANUP_INTERVAL = 60 * 60 # 1 hour
|
6
|
-
HEARTBEAT_INTERVAL = 20 # seconds
|
7
|
-
HEARTBEAT_LIFESPAN = 60 # seconds
|
8
|
-
HEARTBEAT_RETRY_DELAY = 1 # seconds
|
9
|
-
WORKING_QUEUE_PREFIX = 'working'
|
10
|
-
|
11
|
-
# Defines how often we try to take a lease to not flood our
|
12
|
-
# Redis server with SET requests
|
13
|
-
DEFAULT_LEASE_INTERVAL = 2 * 60 # seconds
|
14
|
-
LEASE_KEY = 'reliable-fetcher-cleanup-lock'
|
15
|
-
|
16
|
-
# Defines the COUNT parameter that will be passed to Redis SCAN command
|
17
|
-
SCAN_COUNT = 1000
|
18
|
-
|
19
|
-
UnitOfWork = Struct.new(:queue, :job) do
|
20
|
-
def acknowledge
|
21
|
-
Sidekiq.redis { |conn| conn.lrem(Sidekiq::BaseReliableFetch.working_queue_name(queue), 1, job) }
|
22
|
-
end
|
23
|
-
|
24
|
-
def queue_name
|
25
|
-
queue.sub(/.*queue:/, '')
|
26
|
-
end
|
27
|
-
|
28
|
-
def requeue
|
29
|
-
Sidekiq.redis do |conn|
|
30
|
-
conn.multi do |multi|
|
31
|
-
multi.lpush(queue, job)
|
32
|
-
multi.lrem(Sidekiq::BaseReliableFetch.working_queue_name(queue), 1, job)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def self.setup_reliable_fetch!(config)
|
39
|
-
config.options[:fetch] = if config.options[:semi_reliable_fetch]
|
40
|
-
Sidekiq::SemiReliableFetch
|
41
|
-
else
|
42
|
-
Sidekiq::ReliableFetch
|
43
|
-
end
|
44
|
-
|
45
|
-
Sidekiq.logger.info('GitLab reliable fetch activated!')
|
46
|
-
|
47
|
-
start_heartbeat_thread
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.start_heartbeat_thread
|
51
|
-
Thread.new do
|
52
|
-
loop do
|
53
|
-
begin
|
54
|
-
heartbeat
|
55
|
-
|
56
|
-
sleep HEARTBEAT_INTERVAL
|
57
|
-
rescue => e
|
58
|
-
Sidekiq.logger.error("Heartbeat thread error: #{e.message}")
|
59
|
-
|
60
|
-
sleep HEARTBEAT_RETRY_DELAY
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.pid
|
67
|
-
@pid ||= ::Process.pid
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.hostname
|
71
|
-
@hostname ||= Socket.gethostname
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.heartbeat
|
75
|
-
Sidekiq.redis do |conn|
|
76
|
-
conn.set(heartbeat_key(hostname, pid), 1, ex: HEARTBEAT_LIFESPAN)
|
77
|
-
end
|
78
|
-
|
79
|
-
Sidekiq.logger.debug("Heartbeat for hostname: #{hostname} and pid: #{pid}")
|
80
|
-
end
|
81
|
-
|
82
|
-
def self.bulk_requeue(inprogress, _options)
|
83
|
-
return if inprogress.empty?
|
84
|
-
|
85
|
-
Sidekiq.logger.debug('Re-queueing terminated jobs')
|
86
|
-
|
87
|
-
Sidekiq.redis do |conn|
|
88
|
-
inprogress.each do |unit_of_work|
|
89
|
-
conn.multi do |multi|
|
90
|
-
multi.lpush(unit_of_work.queue, unit_of_work.job)
|
91
|
-
multi.lrem(working_queue_name(unit_of_work.queue), 1, unit_of_work.job)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
Sidekiq.logger.info("Pushed #{inprogress.size} jobs back to Redis")
|
97
|
-
rescue => e
|
98
|
-
Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{e.message}")
|
99
|
-
end
|
100
|
-
|
101
|
-
def self.heartbeat_key(hostname, pid)
|
102
|
-
"reliable-fetcher-heartbeat-#{hostname}-#{pid}"
|
103
|
-
end
|
104
|
-
|
105
|
-
def self.working_queue_name(queue)
|
106
|
-
"#{WORKING_QUEUE_PREFIX}:#{queue}:#{hostname}:#{pid}"
|
107
|
-
end
|
108
|
-
|
109
|
-
attr_reader :cleanup_interval, :last_try_to_take_lease_at, :lease_interval,
|
110
|
-
:queues, :use_semi_reliable_fetch,
|
111
|
-
:strictly_ordered_queues
|
112
|
-
|
113
|
-
def initialize(options)
|
114
|
-
@cleanup_interval = options.fetch(:cleanup_interval, DEFAULT_CLEANUP_INTERVAL)
|
115
|
-
@lease_interval = options.fetch(:lease_interval, DEFAULT_LEASE_INTERVAL)
|
116
|
-
@last_try_to_take_lease_at = 0
|
117
|
-
@strictly_ordered_queues = !!options[:strict]
|
118
|
-
@queues = options[:queues].map { |q| "queue:#{q}" }
|
119
|
-
end
|
120
|
-
|
121
|
-
def retrieve_work
|
122
|
-
clean_working_queues! if take_lease
|
123
|
-
|
124
|
-
retrieve_unit_of_work
|
125
|
-
end
|
126
|
-
|
127
|
-
def retrieve_unit_of_work
|
128
|
-
raise NotImplementedError,
|
129
|
-
"#{self.class} does not implement #{__method__}"
|
130
|
-
end
|
131
|
-
|
132
|
-
private
|
133
|
-
|
134
|
-
def clean_working_queue!(working_queue)
|
135
|
-
original_queue = working_queue.gsub(/#{WORKING_QUEUE_PREFIX}:|:[^:]*:[0-9]*\z/, '')
|
136
|
-
|
137
|
-
Sidekiq.redis do |conn|
|
138
|
-
count = 0
|
139
|
-
|
140
|
-
while conn.rpoplpush(working_queue, original_queue) do
|
141
|
-
count += 1
|
142
|
-
end
|
143
|
-
|
144
|
-
Sidekiq.logger.info("Requeued #{count} dead jobs to #{original_queue}")
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
# Detect "old" jobs and requeue them because the worker they were assigned
|
149
|
-
# to probably failed miserably.
|
150
|
-
def clean_working_queues!
|
151
|
-
Sidekiq.logger.info("Cleaning working queues")
|
152
|
-
|
153
|
-
Sidekiq.redis do |conn|
|
154
|
-
conn.scan_each(match: "#{WORKING_QUEUE_PREFIX}:queue:*", count: SCAN_COUNT) do |key|
|
155
|
-
# Example: "working:name_of_the_job:queue:{hostname}:{PID}"
|
156
|
-
hostname, pid = key.scan(/:([^:]*):([0-9]*)\z/).flatten
|
157
|
-
|
158
|
-
continue if hostname.nil? || pid.nil?
|
159
|
-
|
160
|
-
clean_working_queue!(key) if worker_dead?(hostname, pid)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def worker_dead?(hostname, pid)
|
166
|
-
Sidekiq.redis do |conn|
|
167
|
-
!conn.get(self.class.heartbeat_key(hostname, pid))
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def take_lease
|
172
|
-
return unless allowed_to_take_a_lease?
|
173
|
-
|
174
|
-
@last_try_to_take_lease_at = Time.now.to_f
|
175
|
-
|
176
|
-
Sidekiq.redis do |conn|
|
177
|
-
conn.set(LEASE_KEY, 1, nx: true, ex: cleanup_interval)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
def allowed_to_take_a_lease?
|
182
|
-
Time.now.to_f - last_try_to_take_lease_at > lease_interval
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sidekiq
|
4
|
-
class ReliableFetch < BaseReliableFetch
|
5
|
-
# For reliable fetch we don't use Redis' blocking operations so
|
6
|
-
# we inject a regular sleep into the loop.
|
7
|
-
RELIABLE_FETCH_IDLE_TIMEOUT = 5 # seconds
|
8
|
-
|
9
|
-
attr_reader :queues_iterator, :queues_size
|
10
|
-
|
11
|
-
def initialize(options)
|
12
|
-
super
|
13
|
-
|
14
|
-
@queues_size = queues.size
|
15
|
-
@queues_iterator = queues.cycle
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def retrieve_unit_of_work
|
21
|
-
@queues_iterator.rewind if strictly_ordered_queues
|
22
|
-
|
23
|
-
queues_size.times do
|
24
|
-
queue = queues_iterator.next
|
25
|
-
|
26
|
-
work = Sidekiq.redis do |conn|
|
27
|
-
conn.rpoplpush(queue, self.class.working_queue_name(queue))
|
28
|
-
end
|
29
|
-
|
30
|
-
return UnitOfWork.new(queue, work) if work
|
31
|
-
end
|
32
|
-
|
33
|
-
# We didn't find a job in any of the configured queues. Let's sleep a bit
|
34
|
-
# to avoid uselessly burning too much CPU
|
35
|
-
sleep(RELIABLE_FETCH_IDLE_TIMEOUT)
|
36
|
-
|
37
|
-
nil
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Sidekiq
|
4
|
-
class SemiReliableFetch < BaseReliableFetch
|
5
|
-
# We want the fetch operation to timeout every few seconds so the thread
|
6
|
-
# can check if the process is shutting down. This constant is only used
|
7
|
-
# for semi-reliable fetch.
|
8
|
-
SEMI_RELIABLE_FETCH_TIMEOUT = 2 # seconds
|
9
|
-
|
10
|
-
def initialize(options)
|
11
|
-
super
|
12
|
-
|
13
|
-
if strictly_ordered_queues
|
14
|
-
@queues = @queues.uniq
|
15
|
-
@queues << SEMI_RELIABLE_FETCH_TIMEOUT
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def retrieve_unit_of_work
|
22
|
-
work = Sidekiq.redis { |conn| conn.brpop(*queues_cmd) }
|
23
|
-
return unless work
|
24
|
-
|
25
|
-
unit_of_work = UnitOfWork.new(*work)
|
26
|
-
|
27
|
-
Sidekiq.redis do |conn|
|
28
|
-
conn.lpush(self.class.working_queue_name(unit_of_work.queue), unit_of_work.job)
|
29
|
-
end
|
30
|
-
|
31
|
-
unit_of_work
|
32
|
-
end
|
33
|
-
|
34
|
-
def queues_cmd
|
35
|
-
if strictly_ordered_queues
|
36
|
-
@queues
|
37
|
-
else
|
38
|
-
queues = @queues.shuffle.uniq
|
39
|
-
queues << SEMI_RELIABLE_FETCH_TIMEOUT
|
40
|
-
queues
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'fetch_shared_examples'
|
3
|
-
require 'sidekiq/base_reliable_fetch'
|
4
|
-
require 'sidekiq/reliable_fetch'
|
5
|
-
require 'sidekiq/semi_reliable_fetch'
|
6
|
-
|
7
|
-
describe Sidekiq::BaseReliableFetch do
|
8
|
-
before { Sidekiq.redis(&:flushdb) }
|
9
|
-
|
10
|
-
describe 'UnitOfWork' do
|
11
|
-
let(:fetcher) { Sidekiq::ReliableFetch.new(queues: ['foo']) }
|
12
|
-
|
13
|
-
describe '#requeue' do
|
14
|
-
it 'requeues job' do
|
15
|
-
Sidekiq.redis { |conn| conn.rpush('queue:foo', 'msg') }
|
16
|
-
|
17
|
-
uow = fetcher.retrieve_work
|
18
|
-
|
19
|
-
uow.requeue
|
20
|
-
|
21
|
-
expect(Sidekiq::Queue.new('foo').size).to eq 1
|
22
|
-
expect(working_queue_size('foo')).to eq 0
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
describe '#acknowledge' do
|
27
|
-
it 'acknowledges job' do
|
28
|
-
Sidekiq.redis { |conn| conn.rpush('queue:foo', 'msg') }
|
29
|
-
|
30
|
-
uow = fetcher.retrieve_work
|
31
|
-
|
32
|
-
expect { uow.acknowledge }
|
33
|
-
.to change { working_queue_size('foo') }.by(-1)
|
34
|
-
|
35
|
-
expect(Sidekiq::Queue.new('foo').size).to eq 0
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
describe '.bulk_requeue' do
|
41
|
-
it 'requeues the bulk' do
|
42
|
-
queue1 = Sidekiq::Queue.new('foo')
|
43
|
-
queue2 = Sidekiq::Queue.new('bar')
|
44
|
-
|
45
|
-
expect(queue1.size).to eq 0
|
46
|
-
expect(queue2.size).to eq 0
|
47
|
-
|
48
|
-
uow = described_class::UnitOfWork
|
49
|
-
jobs = [ uow.new('queue:foo', 'bob'), uow.new('queue:foo', 'bar'), uow.new('queue:bar', 'widget') ]
|
50
|
-
described_class.bulk_requeue(jobs, queues: [])
|
51
|
-
|
52
|
-
expect(queue1.size).to eq 2
|
53
|
-
expect(queue2.size).to eq 1
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'sets heartbeat' do
|
58
|
-
config = double(:sidekiq_config, options: {})
|
59
|
-
|
60
|
-
heartbeat_thread = described_class.setup_reliable_fetch!(config)
|
61
|
-
|
62
|
-
Sidekiq.redis do |conn|
|
63
|
-
sleep 0.2 # Give the time to heartbeat thread to make a loop
|
64
|
-
|
65
|
-
heartbeat_key = described_class.heartbeat_key(Socket.gethostname, ::Process.pid)
|
66
|
-
heartbeat = conn.get(heartbeat_key)
|
67
|
-
|
68
|
-
expect(heartbeat).not_to be_nil
|
69
|
-
end
|
70
|
-
|
71
|
-
heartbeat_thread.kill
|
72
|
-
end
|
73
|
-
end
|
@@ -1,118 +0,0 @@
|
|
1
|
-
shared_examples 'a Sidekiq fetcher' do
|
2
|
-
let(:queues) { ['assigned'] }
|
3
|
-
|
4
|
-
before { Sidekiq.redis(&:flushdb) }
|
5
|
-
|
6
|
-
describe '#retrieve_work' do
|
7
|
-
let(:fetcher) { described_class.new(queues: ['assigned']) }
|
8
|
-
|
9
|
-
it 'retrieves the job and puts it to working queue' do
|
10
|
-
Sidekiq.redis { |conn| conn.rpush('queue:assigned', 'msg') }
|
11
|
-
|
12
|
-
uow = fetcher.retrieve_work
|
13
|
-
|
14
|
-
expect(working_queue_size('assigned')).to eq 1
|
15
|
-
expect(uow.queue_name).to eq 'assigned'
|
16
|
-
expect(uow.job).to eq 'msg'
|
17
|
-
expect(Sidekiq::Queue.new('assigned').size).to eq 0
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'does not retrieve a job from foreign queue' do
|
21
|
-
Sidekiq.redis { |conn| conn.rpush('queue:not_assigned', 'msg') }
|
22
|
-
|
23
|
-
expect(fetcher.retrieve_work).to be_nil
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'requeues jobs from dead working queue' do
|
27
|
-
Sidekiq.redis do |conn|
|
28
|
-
conn.rpush(other_process_working_queue_name('assigned'), 'msg')
|
29
|
-
end
|
30
|
-
|
31
|
-
uow = fetcher.retrieve_work
|
32
|
-
|
33
|
-
expect(uow.job).to eq 'msg'
|
34
|
-
|
35
|
-
Sidekiq.redis do |conn|
|
36
|
-
expect(conn.llen(other_process_working_queue_name('assigned'))).to eq 0
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'does not requeue jobs from live working queue' do
|
41
|
-
working_queue = live_other_process_working_queue_name('assigned')
|
42
|
-
|
43
|
-
Sidekiq.redis do |conn|
|
44
|
-
conn.rpush(working_queue, 'msg')
|
45
|
-
end
|
46
|
-
|
47
|
-
uow = fetcher.retrieve_work
|
48
|
-
|
49
|
-
expect(uow).to be_nil
|
50
|
-
|
51
|
-
Sidekiq.redis do |conn|
|
52
|
-
expect(conn.llen(working_queue)).to eq 1
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'does not clean up orphaned jobs more than once per cleanup interval' do
|
57
|
-
Sidekiq.redis = Sidekiq::RedisConnection.create(url: REDIS_URL, size: 10)
|
58
|
-
|
59
|
-
expect_any_instance_of(described_class)
|
60
|
-
.to receive(:clean_working_queues!).once
|
61
|
-
|
62
|
-
threads = 10.times.map do
|
63
|
-
Thread.new do
|
64
|
-
described_class.new(queues: ['assigned']).retrieve_work
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
threads.map(&:join)
|
69
|
-
end
|
70
|
-
|
71
|
-
it 'retrieves by order when strictly order is enabled' do
|
72
|
-
fetcher = described_class.new(strict: true, queues: ['first', 'second'])
|
73
|
-
|
74
|
-
Sidekiq.redis do |conn|
|
75
|
-
conn.rpush('queue:first', ['msg3', 'msg2', 'msg1'])
|
76
|
-
conn.rpush('queue:second', 'msg4')
|
77
|
-
end
|
78
|
-
|
79
|
-
jobs = (1..4).map { fetcher.retrieve_work.job }
|
80
|
-
|
81
|
-
expect(jobs).to eq ['msg1', 'msg2', 'msg3', 'msg4']
|
82
|
-
end
|
83
|
-
|
84
|
-
it 'does not starve any queue when queues are not strictly ordered' do
|
85
|
-
fetcher = described_class.new(queues: ['first', 'second'])
|
86
|
-
|
87
|
-
Sidekiq.redis do |conn|
|
88
|
-
conn.rpush('queue:first', (1..200).map { |i| "msg#{i}" })
|
89
|
-
conn.rpush('queue:second', 'this_job_should_not_stuck')
|
90
|
-
end
|
91
|
-
|
92
|
-
jobs = (1..100).map { fetcher.retrieve_work.job }
|
93
|
-
|
94
|
-
expect(jobs).to include 'this_job_should_not_stuck'
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def working_queue_size(queue_name)
|
100
|
-
Sidekiq.redis do |c|
|
101
|
-
c.llen(Sidekiq::BaseReliableFetch.working_queue_name("queue:#{queue_name}"))
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def other_process_working_queue_name(queue)
|
106
|
-
"#{Sidekiq::BaseReliableFetch::WORKING_QUEUE_PREFIX}:queue:#{queue}:#{Socket.gethostname}:#{::Process.pid + 1}"
|
107
|
-
end
|
108
|
-
|
109
|
-
def live_other_process_working_queue_name(queue)
|
110
|
-
pid = ::Process.pid + 1
|
111
|
-
hostname = Socket.gethostname
|
112
|
-
|
113
|
-
Sidekiq.redis do |conn|
|
114
|
-
conn.set(Sidekiq::BaseReliableFetch.heartbeat_key(hostname, pid), 1)
|
115
|
-
end
|
116
|
-
|
117
|
-
"#{Sidekiq::BaseReliableFetch::WORKING_QUEUE_PREFIX}:queue:#{queue}:#{hostname}:#{pid}"
|
118
|
-
end
|