switchman-inst-jobs 4.0.9 → 4.0.12

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: c6453b4fb5882411028c48e458ec49ee8dd14686b6baa889053bdc44c0f43ecc
4
- data.tar.gz: b5883129b65be01ce10acdb373bdd8bcc95c76e8a18eb4326cb2590b71ead518
3
+ metadata.gz: e4081fb390625cd34169a6585ffaddb12143403e3545988a1ada3ca90d34dc2d
4
+ data.tar.gz: 2f60cfa5de1529a206115bd2d68758f41f71763afbdaa980095de67dda1da544
5
5
  SHA512:
6
- metadata.gz: adc1ed9eb64a227ecbe46f98f92e6b357c595249ad681a4dd3b649fade15b5848ebb4cf15feca35b49ca2c312abe31ef3357941f666be11ad554a1f5cc37892c
7
- data.tar.gz: f0d8cd82e01d15b698f57417f2b2d88cb0263bbd47cfba167a812158f89a5110e653590b3e125f65c5555e3f580f346bbf5f1de9b0a58cc8f951bd326d348e9e
6
+ metadata.gz: 86bb9901b00dd38410092f17e15e1ff9b70b4907426ad32b7f2779a65844d7442e01e8df056d2293c90354eb814e880068854713e9169099e7d772294fc0a1d9
7
+ data.tar.gz: 9376187ebecfaab79e07696fd44ed762694e5ed06e9b539183f93f0f49e9706fa9eef59d6e9ef5456bccae2be780360d021da600e854eb8f2afcace71ca25bb5
@@ -8,6 +8,9 @@ module SwitchmanInstJobs
8
8
  end
9
9
  end
10
10
 
11
+ class JobsBlockedError < RuntimeError
12
+ end
13
+
11
14
  module Delayed
12
15
  module Backend
13
16
  module Base
@@ -9,6 +9,16 @@ module SwitchmanInstJobs
9
9
  @before_move_callbacks << proc
10
10
  end
11
11
 
12
+ def add_validation_callback(proc)
13
+ @validation_callbacks ||= []
14
+ @validation_callbacks << proc
15
+ end
16
+
17
+ def clear_callbacks!
18
+ @before_move_callbacks = []
19
+ @validation_callbacks = []
20
+ end
21
+
12
22
  def transaction_on(shards, &block)
13
23
  return yield if shards.empty?
14
24
 
@@ -31,6 +41,10 @@ module SwitchmanInstJobs
31
41
  source_shards << shard.delayed_jobs_shard.id
32
42
  target_shard = target_shard.try(:id) || target_shard
33
43
  target_shards[target_shard] += [shard.id]
44
+
45
+ @validation_callbacks&.each do |proc|
46
+ proc.call(shard: shard, target_shard: ::Switchman::Shard.find(target_shard))
47
+ end
34
48
  end
35
49
 
36
50
  # Do the updates in batches and then just clear redis instead of clearing them one at a time
@@ -39,7 +53,7 @@ module SwitchmanInstJobs
39
53
  updates[:updated_at] = Time.zone.now if ::Switchman::Shard.column_names.include?('updated_at')
40
54
  ::Switchman::Shard.where(id: shards).update_all(updates)
41
55
  end
42
- clear_shard_cache
56
+ clear_shard_cache(default: ::Switchman::Shard.exists?(id: target_shards.values.flatten, default: true))
43
57
 
44
58
  ::Switchman::Shard.clear_cache
45
59
  # rubocop:disable Style/CombinableLoops
@@ -62,7 +76,7 @@ module SwitchmanInstJobs
62
76
  return unless shards.any?
63
77
 
64
78
  ::Switchman::Shard.where(id: shards).update_all(block_stranded: false)
65
- clear_shard_cache
79
+ clear_shard_cache(default: shards.any?(&:default?))
66
80
 
67
81
  # shards is an array of shard objects that is now stale cause block_stranded has been updated.
68
82
  shards.map(&:delayed_jobs_shard).uniq.each do |dj_shard|
@@ -70,8 +84,9 @@ module SwitchmanInstJobs
70
84
  end
71
85
  end
72
86
 
73
- def clear_shard_cache(debug_message = nil)
74
- ::Switchman.cache.clear
87
+ def clear_shard_cache(debug_message = nil, default:)
88
+ ::Switchman.cache.delete_matched('shard/*')
89
+ ::Switchman.cache.delete('default_shard') if default
75
90
  Rails.logger.debug { "Waiting for caches to clear #{debug_message}" }
76
91
  # Wait a little over the 60 second in-process shard cache clearing
77
92
  # threshold to ensure that all new stranded jobs are now being
@@ -80,6 +95,17 @@ module SwitchmanInstJobs
80
95
  sleep(65) unless @skip_cache_wait
81
96
  end
82
97
 
98
+ def acquire_advisory_lock(type, name)
99
+ @quoted_function_name ||= ::Delayed::Job.connection.quote_table_name('half_md5_as_bigint')
100
+
101
+ value = type == :singleton ? "singleton:#{name}" : name
102
+ ::Delayed::Job.connection.execute(
103
+ ::Delayed::Job.sanitize_sql_for_conditions(
104
+ ["SELECT pg_advisory_xact_lock(#{@quoted_function_name}(?))", value]
105
+ )
106
+ )
107
+ end
108
+
83
109
  # This method expects that all relevant shards already have block_stranded: true
84
110
  # but otherwise jobs can be running normally
85
111
  def run
@@ -166,15 +192,12 @@ module SwitchmanInstJobs
166
192
  locked_by: ::Delayed::Backend::Base::ON_HOLD_BLOCKER
167
193
  }
168
194
 
169
- quoted_function_name = ::Delayed::Job.connection.quote_table_name('half_md5_as_bigint')
170
195
  strand_advisory_lock_fn = lambda do |value|
171
- ::Delayed::Job.connection.execute("SELECT pg_advisory_xact_lock(#{quoted_function_name}('#{value}'))")
196
+ acquire_advisory_lock(:strand, value)
172
197
  end
173
198
 
174
199
  singleton_advisory_lock_fn = lambda do |value|
175
- ::Delayed::Job.connection.execute(
176
- "SELECT pg_advisory_xact_lock(#{quoted_function_name}('singleton:#{value}'))"
177
- )
200
+ acquire_advisory_lock(:singleton, value)
178
201
  end
179
202
 
180
203
  handler.call(strand_scope, :strand, {}, strand_advisory_lock_fn)
@@ -186,7 +209,11 @@ module SwitchmanInstJobs
186
209
  updated = ::Switchman::Shard.where(id: source_shard_ids, block_stranded: true).
187
210
  update_all(block_stranded: false)
188
211
  # If this is being manually re-run for some reason to clean something up, don't wait for nothing to happen
189
- clear_shard_cache("(#{source_shard.id} -> #{target_shard.id})") unless updated.zero?
212
+ unless updated.zero?
213
+ clear_shard_cache("(#{source_shard.id} -> #{target_shard.id})",
214
+ default: ::Switchman::Shard.exists?(id: source_shard_ids,
215
+ default: true))
216
+ end
190
217
 
191
218
  ::Switchman::Shard.clear_cache
192
219
  # At this time, let's unblock all the strands on the target shard that aren't being held by a blocker
@@ -198,12 +225,12 @@ module SwitchmanInstJobs
198
225
  end
199
226
 
200
227
  def unblock_strands(target_shard, batch_size: 10_000)
201
- block_stranded_ids = ::Switchman::Shard.where(block_stranded: true).pluck(:id)
228
+ blocked_shard_ids = blocked_shards.pluck(:id)
202
229
  query = lambda { |column, scope|
203
230
  ::Delayed::Job.
204
231
  where(id: ::Delayed::Job.select("DISTINCT ON (#{column}) id").
205
232
  where(scope).
206
- where.not(shard_id: block_stranded_ids).
233
+ where.not(shard_id: blocked_shard_ids).
207
234
  where(
208
235
  ::Delayed::Job.select(1).from("#{::Delayed::Job.quoted_table_name} dj2").
209
236
  where("dj2.next_in_strand = true OR dj2.source = 'JobsMigrator::StrandBlocker'").
@@ -245,6 +272,72 @@ module SwitchmanInstJobs
245
272
  end
246
273
  end
247
274
 
275
+ def blocked_shards
276
+ ::Switchman::Shard.where(block_stranded: true).or(::Switchman::Shard.where(jobs_held: true))
277
+ end
278
+
279
+ def blocked_by_migrator?(job_scope)
280
+ job_scope.exists?(source: 'JobsMigrator::StrandBlocker') ||
281
+ blocked_shards.exists?(id: job_scope.distinct.pluck(:shard_id))
282
+ end
283
+
284
+ def blocked_strands
285
+ ::Delayed::Job.
286
+ where.not(strand: nil).
287
+ group(:strand).
288
+ having('NOT BOOL_OR(next_in_strand)').
289
+ pluck(:strand)
290
+ end
291
+
292
+ def unblock_strand!(strand, new_parallelism: nil)
293
+ job_scope = ::Delayed::Job.where(strand: strand)
294
+ raise JobsBlockedError if blocked_by_migrator?(job_scope)
295
+
296
+ ::Delayed::Job.transaction do
297
+ acquire_advisory_lock(:strand, strand)
298
+
299
+ new_parallelism ||= job_scope.pick('MAX(max_concurrent)')
300
+ if new_parallelism
301
+ needed_jobs = new_parallelism - job_scope.where(next_in_strand: true).count
302
+ if needed_jobs.positive?
303
+ job_scope.where(next_in_strand: false, locked_by: nil,
304
+ singleton: nil).order(:strand_order_override, :id).
305
+ limit(needed_jobs).update_all(next_in_strand: true)
306
+ else
307
+ 0
308
+ end
309
+ end
310
+ end
311
+ end
312
+
313
+ def blocked_singletons
314
+ ::Delayed::Job.
315
+ where(strand: nil).
316
+ where.not(singleton: nil).
317
+ group(:singleton).
318
+ having('NOT BOOL_OR(next_in_strand)').
319
+ pluck(:singleton)
320
+ end
321
+
322
+ def unblock_singleton!(singleton)
323
+ job_scope = ::Delayed::Job.where(strand: nil, singleton: singleton)
324
+ raise JobsBlockedError if blocked_by_migrator?(job_scope)
325
+
326
+ ::Delayed::Job.transaction do
327
+ acquire_advisory_lock(:singleton, singleton)
328
+
329
+ id, next_in_strand = job_scope.
330
+ group(:singleton).
331
+ pick('MIN(id), BOOL_OR(next_in_strand)')
332
+
333
+ if next_in_strand
334
+ 0
335
+ elsif id
336
+ ::Delayed::Job.where(id: id).update_all(next_in_strand: true)
337
+ end
338
+ end
339
+ end
340
+
248
341
  private
249
342
 
250
343
  def create_blocker_job(**kwargs)
@@ -1,3 +1,3 @@
1
1
  module SwitchmanInstJobs
2
- VERSION = '4.0.9'.freeze
2
+ VERSION = '4.0.12'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: switchman-inst-jobs
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.9
4
+ version: 4.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Petty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-29 00:00:00.000000000 Z
11
+ date: 2022-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inst-jobs