gitlab-sidekiq-fetcher 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []