switchman-inst-jobs 1.4.3 → 1.5.0

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: 6ea858af15b508930d58fd21c5c097c4cedc0f244ef12f7d529120d79483322d
4
- data.tar.gz: e431ab3d85859d0dc3acc5e95fbb820bc272c51bc4124615f0bc314c7d6f311a
3
+ metadata.gz: d279a1bceff0465d419fb835766fda41b6f682ba91ee8b76241238c632d08813
4
+ data.tar.gz: fb7c5eb1b6a9ab934e951021d489cb1292e4f944abce9163b48f2b2ef16bf976
5
5
  SHA512:
6
- metadata.gz: 263dc8ef6c816010d058a8033487a3ed89d6978accf1f16f73ebbc311ece6266f57333c80cf9a9f27d738359638e317e3179a2181d6940efbf8c522a02ba6a67
7
- data.tar.gz: a7078f7f69c97e18cabbaa0ab3c4e1ef9df08472b29f2fe188cc83353b0759333e806a72e72bccdd6a635e642f75c5008092595be0ad3b6bbda114e27ce8723a
6
+ metadata.gz: 20c649d58b0975d99e41b13c5fd39eeea73b59763f8075d36301cbdb88b1dd866e4fdaec8c8fa4a656ba9a0ceb166cd099c6b7a80727ce4ed709fbd0da3c0431
7
+ data.tar.gz: 39a3d57f786c8e02e922e5eaead0a415e3f1337fa4907490f9bc16ab00f153a363db599d5422a52a048b5621ee18e07e130912cbb5a4d07643712f15a3fabac1
@@ -0,0 +1,5 @@
1
+ class AddOnHoldToSwitchmanShards < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :switchman_shards, :jobs_held, :bool, default: false
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddBlockStrandedToSwitchmanShards < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :switchman_shards, :block_stranded, :bool, default: false
4
+ end
5
+ end
@@ -44,9 +44,11 @@ require 'switchman_inst_jobs/delayed/pool'
44
44
  require 'switchman_inst_jobs/delayed/worker'
45
45
  require 'switchman_inst_jobs/delayed/worker/health_check'
46
46
  require 'switchman_inst_jobs/engine'
47
+ require 'switchman_inst_jobs/jobs_migrator'
47
48
  require 'switchman_inst_jobs/new_relic'
48
49
  require 'switchman_inst_jobs/shackles'
49
50
  require 'switchman_inst_jobs/switchman/database_server'
50
51
  require 'switchman_inst_jobs/switchman/default_shard'
51
52
  require 'switchman_inst_jobs/switchman/shard'
53
+ require 'switchman_inst_jobs/timed_cache'
52
54
  require 'switchman_inst_jobs/yaml_extensions'
@@ -13,16 +13,32 @@ module SwitchmanInstJobs
13
13
  module Base
14
14
  module ClassMethods
15
15
  def enqueue(object, options = {})
16
+ ::Switchman::Shard.periodic_clear_shard_cache
17
+ current_shard = ::Switchman::Shard.current
16
18
  enqueue_options = options.merge(
17
- current_shard: ::Switchman::Shard.current
19
+ current_shard: current_shard
18
20
  )
21
+ enqueue_job = -> { ::Shackles.activate(:master) { super(object, enqueue_options) } }
19
22
 
20
- if ::ActiveRecord::Migration.open_migrations.positive?
21
- ::Switchman::Shard.current.delayed_jobs_shard.activate(:delayed_jobs) do
22
- super(object, enqueue_options)
23
- end
23
+ # Another dj shard must be currently manually activated, so just use that
24
+ # In general this will only happen in unusual circumstances like tests
25
+ # also if migrations are running, always use the current shard's job shard
26
+ if ::ActiveRecord::Migration.open_migrations.zero? &&
27
+ current_shard.delayed_jobs_shard != ::Switchman::Shard.current(:delayed_jobs)
28
+ enqueue_job.call
24
29
  else
25
- ::Shackles.activate(:master) { super(object, enqueue_options) }
30
+ ::Switchman::Shard.default.activate do
31
+ current_shard = ::Switchman::Shard.lookup(current_shard.id)
32
+ current_job_shard = current_shard.delayed_jobs_shard
33
+
34
+ if (options[:singleton] || options[:strand]) && current_shard.block_stranded
35
+ enqueue_options[:next_in_strand] = false
36
+ end
37
+
38
+ current_job_shard.activate(:delayed_jobs) do
39
+ enqueue_job.call
40
+ end
41
+ end
26
42
  end
27
43
  end
28
44
 
@@ -54,8 +70,15 @@ module SwitchmanInstJobs
54
70
  end
55
71
 
56
72
  def current_shard=(shard)
73
+ @current_shard = nil
57
74
  self.shard_id = shard.id
58
75
  self.shard_id = nil if shard.is_a?(::Switchman::DefaultShard)
76
+ # If jobs are held for a shard, enqueue new ones as held as well
77
+ return unless shard.jobs_held
78
+
79
+ self.locked_by = ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY
80
+ self.locked_at = ::Delayed::Job.db_time_now
81
+ self.attempts = ::Delayed::Backend::Base::ON_HOLD_COUNT
59
82
  end
60
83
 
61
84
  def invoke_job
@@ -16,6 +16,22 @@ module SwitchmanInstJobs
16
16
  block.call(worker, config)
17
17
  end
18
18
  end
19
+
20
+ # Ensure jobs get unblocked on the new shard if they exist
21
+ ::Delayed::Worker.lifecycle.after(:perform) do |_worker, job|
22
+ if job.strand
23
+ ::Switchman::Shard.clear_cache
24
+ ::Switchman::Shard.default.activate do
25
+ current_job_shard = ::Switchman::Shard.lookup(job.shard_id).delayed_jobs_shard
26
+ if current_job_shard != ::Switchman::Shard.current(:delayed_jobs)
27
+ current_job_shard.activate(:delayed_jobs) do
28
+ j = ::Delayed::Job.where(strand: job.strand).next_in_strand_order.first
29
+ j.update_column(:next_in_strand, true) if j && !j.next_in_strand
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
19
35
  end
20
36
 
21
37
  initializer 'sharding.shackles', after: 'switchman.extend_shackles' do
@@ -0,0 +1,271 @@
1
+ # Just disabling all the rubocop metrics for this file for now,
2
+ # as it is a direct port-in of existing code
3
+
4
+ # rubocop:disable Metrics/BlockLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/ClassLength
5
+ require 'set'
6
+ require 'parallel'
7
+
8
+ module SwitchmanInstJobs
9
+ class JobsMigrator
10
+ class << self
11
+ def add_before_move_callback(proc)
12
+ @before_move_callbacks ||= []
13
+ @before_move_callbacks << proc
14
+ end
15
+
16
+ def transaction_on(shards, &block)
17
+ return yield if shards.empty?
18
+
19
+ shard = shards.pop
20
+ current_shard = ::Switchman::Shard.current(:delayed_jobs)
21
+ shard.activate(:delayed_jobs) do
22
+ ::Delayed::Job.transaction do
23
+ current_shard.activate(:delayed_jobs) do
24
+ transaction_on(shards, &block)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ def migrate_shards(shard_map)
31
+ source_shards = Set[]
32
+ shard_map.each do |(shard, target_shard)|
33
+ shard = ::Switchman::Shard.find(shard) unless shard.is_a?(::Switchman::Shard)
34
+ source_shards << shard.delayed_jobs_shard.id
35
+ # If target_shard is an int, it won't have an id, but we can just use it as is
36
+ shard.update(delayed_jobs_shard_id: target_shard.try(:id) || target_shard, block_stranded: true)
37
+ end
38
+
39
+ # Wait a little over the 60 second in-process shard cache clearing
40
+ # threshold to ensure that all new stranded jobs are now being
41
+ # enqueued with next_in_strand: false
42
+ Rails.logger.debug("Waiting for caches to clear (#{source_shard.id} -> #{target_shard.id})")
43
+ sleep(65) unless @skip_cache_wait
44
+
45
+ # TODO: 4 has been picked completely out of a hat. We should make it configurable or something
46
+ Parallel.each(source_shards, in_processes: 4) do |s|
47
+ # Ensure the child processes don't share connections with the parent
48
+ Delayed::Pool.on_fork.call
49
+ ActiveRecord::Base.clear_all_connections!
50
+ s.activate(:delayed_jobs) { run }
51
+ end
52
+ end
53
+
54
+ # This method expects that all relevant shards already have block_stranded: true
55
+ # but otherwise jobs can be running normally
56
+ def run
57
+ # Ensure this is never run with a dirty in-memory shard cache
58
+ ::Switchman::Shard.clear_cache
59
+ migrate_strands
60
+ migrate_everything
61
+ end
62
+
63
+ def migrate_strands
64
+ # there are 4 scenarios to deal with here
65
+ # 1) no running job, no jobs moved: do nothing
66
+ # 2) running job, no jobs moved; create blocker with next_in_strand=false
67
+ # to prevent new jobs from immediately executing
68
+ # 3) running job, jobs moved; set next_in_strand=false on the first of
69
+ # those (= do nothing since it should already be false)
70
+ # 4) no running job, jobs moved: set next_in_strand=true on the first of
71
+ # those (= do nothing since it should already be true)
72
+
73
+ source_shard = ::Switchman::Shard.current(:delayed_jobs)
74
+ strand_scope = ::Delayed::Job.shard(source_shard).where('strand IS NOT NULL')
75
+ shard_map = build_shard_map(strand_scope, source_shard)
76
+ shard_map.each do |(target_shard, source_shard_ids)|
77
+ shard_scope = strand_scope.where(shard_id: source_shard_ids)
78
+
79
+ # 1) is taken care of because it should not show up here in strands
80
+ strands = shard_scope.distinct.order(:strand).pluck(:strand)
81
+
82
+ target_shard.activate(:delayed_jobs) do
83
+ strands.each do |strand|
84
+ transaction_on([source_shard, target_shard]) do
85
+ this_strand_scope = shard_scope.where(strand: strand)
86
+ # we want to copy all the jobs except the one that is still running.
87
+ jobs_scope = this_strand_scope.where(locked_by: nil)
88
+
89
+ # 2) and part of 3) are taken care of here by creating a blocker
90
+ # job with next_in_strand = false. as soon as the current
91
+ # running job is finished it should set next_in_strand
92
+ # We lock it to ensure that the jobs worker can't delete it until we are done moving the strand
93
+ # Since we only unlock it on the new jobs queue *after* deleting from the original
94
+ # the lock ensures the blocker always gets unlocked
95
+ first = this_strand_scope.where('locked_by IS NOT NULL').next_in_strand_order.lock.first
96
+ if first
97
+ first_job = ::Delayed::Job.create!(strand: strand, next_in_strand: false)
98
+ first_job.payload_object = ::Delayed::PerformableMethod.new(Kernel, :sleep, [0])
99
+ first_job.queue = first.queue
100
+ first_job.tag = 'Kernel.sleep'
101
+ first_job.source = 'JobsMigrator::StrandBlocker'
102
+ first_job.max_attempts = 1
103
+ # If we ever have jobs left over from 9999 jobs moves of a single shard,
104
+ # something has gone terribly wrong
105
+ first_job.strand_order_override = -9999
106
+ first_job.save!
107
+ # the rest of 3) is taken care of here
108
+ # make sure that all the jobs moved over are NOT next in strand
109
+ ::Delayed::Job.where(next_in_strand: true, strand: strand, locked_by: nil)
110
+ .update_all(next_in_strand: false)
111
+ end
112
+
113
+ # 4) is taken care of here, by leaveing next_in_strand alone and
114
+ # it should execute on the new shard
115
+ batch_move_jobs(
116
+ target_shard: target_shard,
117
+ source_shard: source_shard,
118
+ scope: jobs_scope
119
+ ) do |job, new_job|
120
+ # This ensures jobs enqueued on the old jobs shard run before jobs on the new jobs queue
121
+ new_job.strand_order_override = job.strand_order_override - 1
122
+ end
123
+ end
124
+ end
125
+
126
+ ::Switchman::Shard.find(source_shard_ids).each do |shard|
127
+ shard.update(block_stranded: false)
128
+ end
129
+ # Wait a little over the 60 second in-process shard cache clearing
130
+ # threshold to ensure that all new stranded jobs are now being
131
+ # enqueued with next_in_strand: false
132
+ Rails.logger.debug("Waiting for caches to clear (#{source_shard.id} -> #{target_shard.id})")
133
+ # for spec usage only
134
+ sleep(65) unless @skip_cache_wait
135
+ # At this time, let's unblock all the strands on the target shard that aren't being held by a blocker
136
+ # but actually could have run and we just didn't know it because we didn't know if they had jobs
137
+ # on the source shard
138
+ # rubocop:disable Layout/LineLength
139
+ strands_to_unblock = shard_scope.where.not(source: 'JobsMigrator::StrandBlocker')
140
+ .distinct
141
+ .where("NOT EXISTS (SELECT 1 FROM #{::Delayed::Job.quoted_table_name} dj2 WHERE delayed_jobs.strand=dj2.strand AND next_in_strand)")
142
+ .pluck(:strand)
143
+ # rubocop:enable Layout/LineLength
144
+ strands_to_unblock.each do |strand|
145
+ Delayed::Job.where(strand: strand).next_in_strand_order.first.update_attribute(:next_in_strand, true)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ def migrate_everything
152
+ source_shard = ::Switchman::Shard.current(:delayed_jobs)
153
+ scope = ::Delayed::Job.shard(source_shard).where('strand IS NULL')
154
+
155
+ shard_map = build_shard_map(scope, source_shard)
156
+ shard_map.each do |(target_shard, source_shard_ids)|
157
+ batch_move_jobs(
158
+ target_shard: target_shard,
159
+ source_shard: source_shard,
160
+ scope: scope.where(shard_id: source_shard_ids).where(locked_by: nil)
161
+ )
162
+ end
163
+ end
164
+
165
+ private
166
+
167
+ def build_shard_map(scope, source_shard)
168
+ shard_ids = scope.distinct.pluck(:shard_id)
169
+
170
+ shard_map = {}
171
+ ::Switchman::Shard.find(shard_ids).each do |shard|
172
+ next if shard.delayed_jobs_shard == source_shard
173
+
174
+ shard_map[shard.delayed_jobs_shard] ||= []
175
+ shard_map[shard.delayed_jobs_shard] << shard.id
176
+ end
177
+
178
+ shard_map
179
+ end
180
+
181
+ def batch_move_jobs(target_shard:, source_shard:, scope:)
182
+ while scope.exists?
183
+ # Adapted from get_and_lock_next_available in delayed/backend/active_record.rb
184
+ target_jobs = scope.limit(1000).lock('FOR UPDATE SKIP LOCKED')
185
+
186
+ query = "WITH limited_jobs AS (#{target_jobs.to_sql}) " \
187
+ "UPDATE #{::Delayed::Job.quoted_table_name} " \
188
+ "SET locked_by = #{::Delayed::Job.connection.quote(::Delayed::Backend::Base::ON_HOLD_LOCKED_BY)}, " \
189
+ "locked_at = #{::Delayed::Job.connection.quote(::Delayed::Job.db_time_now)} "\
190
+ "FROM limited_jobs WHERE limited_jobs.id=#{::Delayed::Job.quoted_table_name}.id " \
191
+ "RETURNING #{::Delayed::Job.quoted_table_name}.*"
192
+
193
+ jobs = source_shard.activate(:delayed_jobs) { ::Delayed::Job.find_by_sql(query) }
194
+ new_jobs = jobs.map do |job|
195
+ new_job = job.dup
196
+ new_job.shard = target_shard
197
+ new_job.created_at = job.created_at
198
+ new_job.updated_at = job.updated_at
199
+ new_job.locked_at = nil
200
+ new_job.locked_by = nil
201
+ yield(job, new_job) if block_given?
202
+ @before_move_callbacks&.each do |proc|
203
+ proc.call(
204
+ old_job: job,
205
+ new_job: new_job
206
+ )
207
+ end
208
+ new_job
209
+ end
210
+ transaction_on([source_shard, target_shard]) do
211
+ target_shard.activate(:delayed_jobs) do
212
+ bulk_insert_jobs(new_jobs)
213
+ end
214
+ source_shard.activate(:delayed_jobs) do
215
+ ::Delayed::Job.delete(jobs)
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ # This is adapted from the postgreql adapter in canvas-lms
222
+ # Once we stop supporting rails 5.2 we can just use insert_all from activerecord
223
+ def bulk_insert_jobs(objects)
224
+ records = objects.map do |object|
225
+ object.attributes.map do |(name, value)|
226
+ next if name == ::Delayed::Job.primary_key
227
+
228
+ if (type = ::Delayed::Job.attribute_types[name]).is_a?(::ActiveRecord::Type::Serialized)
229
+ value = type.serialize(value)
230
+ end
231
+ [name, value]
232
+ end.compact.to_h
233
+ end
234
+ return if records.length.zero?
235
+
236
+ keys = records.first.keys
237
+
238
+ connection = ::Delayed::Job.connection
239
+ quoted_keys = keys.map { |k| connection.quote_column_name(k) }.join(', ')
240
+
241
+ connection.execute "COPY #{::Delayed::Job.quoted_table_name} (#{quoted_keys}) FROM STDIN"
242
+ records.map do |record|
243
+ connection.raw_connection.put_copy_data(keys.map { |k| quote_text(record[k]) }.join("\t") + "\n")
244
+ end
245
+ connection.clear_query_cache
246
+ connection.raw_connection.put_copy_end
247
+ result = connection.raw_connection.get_result
248
+ begin
249
+ result.check
250
+ rescue StandardError => e
251
+ raise connection.send(:translate_exception, e, 'COPY FROM STDIN')
252
+ end
253
+ result.cmd_tuples
254
+ end
255
+
256
+ # See above comment...
257
+ def quote_text(value)
258
+ if value.nil?
259
+ '\\N'
260
+ elsif value.is_a?(::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array::Data)
261
+ quote_text(encode_array(value))
262
+ else
263
+ hash = { "\n" => '\\n', "\r" => '\\r', "\t" => '\\t', '\\' => '\\\\' }
264
+ value.to_s.gsub(/[\n\r\t\\]/) { |c| hash[c] }
265
+ end
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ # rubocop:enable Metrics/BlockLength, Metrics/MethodLength, Metrics/AbcSize, Metrics/ClassLength
@@ -4,6 +4,10 @@ module SwitchmanInstJobs
4
4
  def delayed_jobs_shard
5
5
  self
6
6
  end
7
+
8
+ def jobs_held
9
+ false
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -17,6 +17,51 @@ module SwitchmanInstJobs
17
17
  database_server&.delayed_jobs_shard(self)
18
18
  end
19
19
 
20
+ # Adapted from hold/unhold methods in base delayed jobs base
21
+ # Wait is required to be able to safely move jobs
22
+ def hold_jobs!(wait: false)
23
+ self.jobs_held = true
24
+ save! if changed?
25
+ delayed_jobs_shard.activate(:delayed_jobs) do
26
+ lock_jobs_for_hold
27
+ end
28
+ return unless wait
29
+
30
+ delayed_jobs_shard.activate(:delayed_jobs) do
31
+ while ::Delayed::Job.where(shard_id: id)
32
+ .where.not(locked_at: nil)
33
+ .where.not(locked_by: ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY).exists?
34
+ sleep 10
35
+ lock_jobs_for_hold
36
+ end
37
+ end
38
+ end
39
+
40
+ def unhold_jobs!
41
+ self.jobs_held = false
42
+ save! if changed?
43
+ delayed_jobs_shard.activate(:delayed_jobs) do
44
+ ::Delayed::Job.where(locked_by: ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY, shard_id: id)
45
+ .in_batches(of: 10_000)
46
+ .update_all(
47
+ locked_by: nil,
48
+ locked_at: nil,
49
+ attempts: 0,
50
+ failed_at: nil
51
+ )
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def lock_jobs_for_hold
58
+ ::Delayed::Job.where(locked_at: nil, shard_id: id).in_batches(of: 10_000).update_all(
59
+ locked_by: ::Delayed::Backend::Base::ON_HOLD_LOCKED_BY,
60
+ locked_at: ::Delayed::Job.db_time_now,
61
+ attempts: ::Delayed::Backend::Base::ON_HOLD_COUNT
62
+ )
63
+ end
64
+
20
65
  module ClassMethods
21
66
  def clear_cache
22
67
  super
@@ -57,6 +102,14 @@ module SwitchmanInstJobs
57
102
  db.create_new_shard
58
103
  end
59
104
 
105
+ def periodic_clear_shard_cache
106
+ # TODO: make this configurable
107
+ @timed_cache ||= TimedCache.new(-> { 60.to_i.seconds.ago }) do
108
+ ::Switchman::Shard.clear_cache
109
+ end
110
+ @timed_cache.clear
111
+ end
112
+
60
113
  def delayed_jobs_shards
61
114
  unless instance_variable_defined?(:@delayed_jobs_shards)
62
115
  # re-entrancy protection
@@ -0,0 +1,19 @@
1
+ module SwitchmanInstJobs
2
+ class TimedCache
3
+ def initialize(timeout, &block)
4
+ @timeout = timeout
5
+ @block = block
6
+ @cached_at = Time.zone.now
7
+ end
8
+
9
+ def clear(force = false)
10
+ if force || @cached_at < @timeout.call
11
+ @block.call
12
+ @cached_at = Time.zone.now
13
+ true
14
+ else
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,3 @@
1
1
  module SwitchmanInstJobs
2
- VERSION = '1.4.3'.freeze
2
+ VERSION = '1.5.0'.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: 1.4.3
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Petty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-18 00:00:00.000000000 Z
11
+ date: 2020-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: inst-jobs
@@ -16,20 +16,34 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0.15'
19
+ version: '0.16'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '0.16'
22
+ version: '0.17'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '0.15'
29
+ version: '0.16'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '0.16'
32
+ version: '0.17'
33
+ - !ruby/object:Gem::Dependency
34
+ name: parallel
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '1.19'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.19'
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: railties
35
49
  requirement: !ruby/object:Gem::Requirement
@@ -261,6 +275,8 @@ extra_rdoc_files: []
261
275
  files:
262
276
  - db/migrate/20170308045400_add_shard_id_to_delayed_jobs.rb
263
277
  - db/migrate/20180628153808_set_search_paths_on_functions.rb
278
+ - db/migrate/20200818130101_add_on_hold_to_switchman_shards.rb
279
+ - db/migrate/20200822014259_add_block_stranded_to_switchman_shards.rb
264
280
  - lib/switchman-inst-jobs.rb
265
281
  - lib/switchman_inst_jobs.rb
266
282
  - lib/switchman_inst_jobs/active_record/connection_adapters/postgresql_adapter.rb
@@ -271,11 +287,13 @@ files:
271
287
  - lib/switchman_inst_jobs/delayed/worker.rb
272
288
  - lib/switchman_inst_jobs/delayed/worker/health_check.rb
273
289
  - lib/switchman_inst_jobs/engine.rb
290
+ - lib/switchman_inst_jobs/jobs_migrator.rb
274
291
  - lib/switchman_inst_jobs/new_relic.rb
275
292
  - lib/switchman_inst_jobs/shackles.rb
276
293
  - lib/switchman_inst_jobs/switchman/database_server.rb
277
294
  - lib/switchman_inst_jobs/switchman/default_shard.rb
278
295
  - lib/switchman_inst_jobs/switchman/shard.rb
296
+ - lib/switchman_inst_jobs/timed_cache.rb
279
297
  - lib/switchman_inst_jobs/version.rb
280
298
  - lib/switchman_inst_jobs/yaml_extensions.rb
281
299
  homepage: https://github.com/instructure/switchman-inst-jobs
@@ -297,7 +315,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
315
  - !ruby/object:Gem::Version
298
316
  version: '0'
299
317
  requirements: []
300
- rubygems_version: 3.1.4
318
+ rubygems_version: 3.0.3
301
319
  signing_key:
302
320
  specification_version: 4
303
321
  summary: Switchman and Instructure Jobs compatibility gem.