gitlab-sidekiq-fetcher 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a179330459058ca56cd918066793895684d63bbfcfed73eccd8687d5ed22ea15
4
- data.tar.gz: fb0115218a6c35a6349f66c79a0a9eacbf1434a7fc6015100ff2dffb0bf8cef7
3
+ metadata.gz: 477650a08755f00beb453c4867a270fa8469a83a372b5b09ba1c030885899699
4
+ data.tar.gz: da3fcf3f0b67dd3f71c73ea80d09666e82fa9360cdf5cbf6c8b8e5668b85bfb5
5
5
  SHA512:
6
- metadata.gz: c4ef091732aa1cd0b69b889d7649c6f4ade001989360cdb717296cbfec6db7551e379b278b64fcb823b0cf293245b72293fbef8ad8b009158060983ecf421328
7
- data.tar.gz: 932751a38d73e1f1d0d147fb654ce802bc3a0f9b20319ab76ef9525fd29661ac70b505aa47e71932f9e02d6651785f8779dfdbd3723226b1f53d55d7e1e3880d
6
+ metadata.gz: b4d03a71a6c00e2fa55affd4af5633c2c1ef58093c8bd82b95d4a1603523d940e35b8db4643becf20236224ef065fc879b70585933569efa893e0e05481bffb9
7
+ data.tar.gz: 4fac10a26e3507c70927ad8689c2d8903d452cc9ed25a6522dc5c2c7e5e7fcb13d8412013eadb435a9a2feecab51130ad516553c4bbb49c72b2b36827f75c3cf
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'gitlab-sidekiq-fetcher'
3
- s.version = '0.5.3'
3
+ s.version = '0.5.4'
4
4
  s.authors = ['TEA', 'GitLab']
5
5
  s.email = 'valery@gitlab.com'
6
6
  s.license = 'LGPL-3.0'
@@ -68,20 +68,24 @@ module Sidekiq
68
68
  end
69
69
  end
70
70
 
71
- def self.pid
72
- @pid ||= ::Process.pid
71
+ def self.hostname
72
+ Socket.gethostname
73
73
  end
74
74
 
75
- def self.hostname
76
- @hostname ||= Socket.gethostname
75
+ def self.process_nonce
76
+ @@process_nonce ||= SecureRandom.hex(6)
77
+ end
78
+
79
+ def self.identity
80
+ @@identity ||= "#{hostname}:#{$$}:#{process_nonce}"
77
81
  end
78
82
 
79
83
  def self.heartbeat
80
84
  Sidekiq.redis do |conn|
81
- conn.set(heartbeat_key(hostname, pid), 1, ex: HEARTBEAT_LIFESPAN)
85
+ conn.set(heartbeat_key(identity), 1, ex: HEARTBEAT_LIFESPAN)
82
86
  end
83
87
 
84
- Sidekiq.logger.debug("Heartbeat for hostname: #{hostname} and pid: #{pid}")
88
+ Sidekiq.logger.debug("Heartbeat for #{identity}")
85
89
  end
86
90
 
87
91
  def self.bulk_requeue(inprogress, _options)
@@ -100,9 +104,7 @@ module Sidekiq
100
104
  Sidekiq.logger.warn("Failed to requeue #{inprogress.size} jobs: #{e.message}")
101
105
  end
102
106
 
103
- def self.clean_working_queue!(working_queue)
104
- original_queue = working_queue.gsub(/#{WORKING_QUEUE_PREFIX}:|:[^:]*:[0-9]*\z/, '')
105
-
107
+ def self.clean_working_queue!(original_queue, working_queue)
106
108
  Sidekiq.redis do |conn|
107
109
  while job = conn.rpop(working_queue)
108
110
  preprocess_interrupted_job(job, original_queue)
@@ -121,6 +123,14 @@ module Sidekiq
121
123
  end
122
124
  end
123
125
 
126
+ def self.valid_identity_format?(identity)
127
+ # New format is "{hostname}:{pid}:{randomhex}
128
+ # Old format is "{hostname}:{pid}"
129
+
130
+ # Test the newer format first, only checking the older if necessary
131
+ identity.match(/[^:]*:[0-9]*:[0-9a-f]*\z/) || identity.match(/([^:]*):([0-9]*)\z/)
132
+ end
133
+
124
134
  # Detect "old" jobs and requeue them because the worker they were assigned
125
135
  # to probably failed miserably.
126
136
  def self.clean_working_queues!
@@ -128,26 +138,25 @@ module Sidekiq
128
138
 
129
139
  Sidekiq.redis do |conn|
130
140
  conn.scan_each(match: "#{WORKING_QUEUE_PREFIX}:queue:*", count: SCAN_COUNT) do |key|
131
- # Example: "working:name_of_the_job:queue:{hostname}:{PID}"
132
- hostname, pid = key.scan(/:([^:]*):([0-9]*)\z/).flatten
141
+ original_queue, identity = key.scan(/#{WORKING_QUEUE_PREFIX}:(queue:[^:]*):(.*)\z/).flatten
133
142
 
134
- next if hostname.nil? || pid.nil?
143
+ next unless valid_identity_format?(identity)
135
144
 
136
- clean_working_queue!(key) if worker_dead?(hostname, pid, conn)
145
+ clean_working_queue!(original_queue, key) if worker_dead?(identity, conn)
137
146
  end
138
147
  end
139
148
  end
140
149
 
141
- def self.worker_dead?(hostname, pid, conn)
142
- !conn.get(heartbeat_key(hostname, pid))
150
+ def self.worker_dead?(identity, conn)
151
+ !conn.get(heartbeat_key(identity))
143
152
  end
144
153
 
145
- def self.heartbeat_key(hostname, pid)
146
- "reliable-fetcher-heartbeat-#{hostname}-#{pid}"
154
+ def self.heartbeat_key(identity)
155
+ "reliable-fetcher-heartbeat-#{identity.gsub(':', '-')}"
147
156
  end
148
157
 
149
158
  def self.working_queue_name(queue)
150
- "#{WORKING_QUEUE_PREFIX}:#{queue}:#{hostname}:#{pid}"
159
+ "#{WORKING_QUEUE_PREFIX}:#{queue}:#{identity}"
151
160
  end
152
161
 
153
162
  def self.interruption_exhausted?(msg)
@@ -87,7 +87,7 @@ describe Sidekiq::BaseReliableFetch do
87
87
  Sidekiq.redis do |conn|
88
88
  sleep 0.2 # Give the time to heartbeat thread to make a loop
89
89
 
90
- heartbeat_key = described_class.heartbeat_key(Socket.gethostname, ::Process.pid)
90
+ heartbeat_key = described_class.heartbeat_key(described_class.identity)
91
91
  heartbeat = conn.get(heartbeat_key)
92
92
 
93
93
  expect(heartbeat).not_to be_nil
@@ -35,6 +35,7 @@ shared_examples 'a Sidekiq fetcher' do
35
35
 
36
36
  uow = fetcher.retrieve_work
37
37
 
38
+ expect(uow).to_not be_nil
38
39
  expect(uow.job).to eq expected_job
39
40
 
40
41
  Sidekiq.redis do |conn|
@@ -57,6 +58,24 @@ shared_examples 'a Sidekiq fetcher' do
57
58
  end
58
59
  end
59
60
 
61
+ it 'requeues jobs from legacy dead working queue with incremented interrupted_count' do
62
+ Sidekiq.redis do |conn|
63
+ conn.rpush(legacy_other_process_working_queue_name('assigned'), job)
64
+ end
65
+
66
+ expected_job = Sidekiq.load_json(job)
67
+ expected_job['interrupted_count'] = 1
68
+ expected_job = Sidekiq.dump_json(expected_job)
69
+
70
+ uow = fetcher.retrieve_work
71
+
72
+ expect(uow).to_not be_nil
73
+ expect(uow.job).to eq expected_job
74
+
75
+ Sidekiq.redis do |conn|
76
+ expect(conn.llen(legacy_other_process_working_queue_name('assigned'))).to eq 0
77
+ end
78
+ end
60
79
 
61
80
  it 'does not requeue jobs from live working queue' do
62
81
  working_queue = live_other_process_working_queue_name('assigned')
@@ -113,6 +132,24 @@ shared_examples 'a Sidekiq fetcher' do
113
132
 
114
133
  expect(jobs).to include 'this_job_should_not_stuck'
115
134
  end
135
+
136
+ context 'with short cleanup interval' do
137
+ let(:short_interval) { 1 }
138
+ let(:fetcher) { described_class.new(queues: queues, lease_interval: short_interval, cleanup_interval: short_interval) }
139
+
140
+ it 'requeues when there is no heartbeat' do
141
+ Sidekiq.redis { |conn| conn.rpush('queue:assigned', job) }
142
+ # Use of retrieve_work twice with a sleep ensures we have exercised the
143
+ # `identity` method to create the working queue key name and that it
144
+ # matches the patterns used in the cleanup
145
+ uow = fetcher.retrieve_work
146
+ sleep(short_interval + 1)
147
+ uow = fetcher.retrieve_work
148
+
149
+ # Will only receive a UnitOfWork if the job was detected as failed and requeued
150
+ expect(uow).to_not be_nil
151
+ end
152
+ end
116
153
  end
117
154
  end
118
155
 
@@ -122,17 +159,23 @@ def working_queue_size(queue_name)
122
159
  end
123
160
  end
124
161
 
125
- def other_process_working_queue_name(queue)
162
+ def legacy_other_process_working_queue_name(queue)
126
163
  "#{Sidekiq::BaseReliableFetch::WORKING_QUEUE_PREFIX}:queue:#{queue}:#{Socket.gethostname}:#{::Process.pid + 1}"
127
164
  end
128
165
 
166
+
167
+ def other_process_working_queue_name(queue)
168
+ "#{Sidekiq::BaseReliableFetch::WORKING_QUEUE_PREFIX}:queue:#{queue}:#{Socket.gethostname}:#{::Process.pid + 1}:#{::SecureRandom.hex(6)}"
169
+ end
170
+
129
171
  def live_other_process_working_queue_name(queue)
130
172
  pid = ::Process.pid + 1
131
173
  hostname = Socket.gethostname
174
+ nonce = SecureRandom.hex(6)
132
175
 
133
176
  Sidekiq.redis do |conn|
134
- conn.set(Sidekiq::BaseReliableFetch.heartbeat_key(hostname, pid), 1)
177
+ conn.set(Sidekiq::BaseReliableFetch.heartbeat_key("#{hostname}-#{pid}-#{nonce}"), 1)
135
178
  end
136
179
 
137
- "#{Sidekiq::BaseReliableFetch::WORKING_QUEUE_PREFIX}:queue:#{queue}:#{hostname}:#{pid}"
180
+ "#{Sidekiq::BaseReliableFetch::WORKING_QUEUE_PREFIX}:queue:#{queue}:#{hostname}:#{pid}:#{nonce}"
138
181
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-sidekiq-fetcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - TEA
8
8
  - GitLab
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-02-18 00:00:00.000000000 Z
12
+ date: 2021-02-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
@@ -63,7 +63,7 @@ homepage: https://gitlab.com/gitlab-org/sidekiq-reliable-fetch/
63
63
  licenses:
64
64
  - LGPL-3.0
65
65
  metadata: {}
66
- post_install_message:
66
+ post_install_message:
67
67
  rdoc_options: []
68
68
  require_paths:
69
69
  - lib
@@ -79,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
79
  version: '0'
80
80
  requirements: []
81
81
  rubygems_version: 3.1.4
82
- signing_key:
82
+ signing_key:
83
83
  specification_version: 4
84
84
  summary: Reliable fetch extension for Sidekiq
85
85
  test_files: []