canvas_sync 0.22.0.beta7 → 0.22.0

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: 460a37fe879ff078150579638df606c33dfd35870b69955c42dc7bee7afb4833
4
- data.tar.gz: e02f9568f6d80f0f0bf4ab6ff21800ed8bcd36d5cd1803f03cf6a186b3fda690
3
+ metadata.gz: b659edf35a8c6f427f847142d4627920cc19effabfe7fb5ad2ccb960884cf47c
4
+ data.tar.gz: 3264f9b298a5fcbf251a6ccd927ce20133598ed1b9fdd8adf3a728ef90a4e080
5
5
  SHA512:
6
- metadata.gz: 1a08031744764b9f14a8da562ffee5ca83a85e4efa9b5a5b9725605cce53853b90d3dfd9b01bb5ec5ac5aeb248bc6be88fe07b1de9b5b2e5b0b6c4d25d8cd942
7
- data.tar.gz: a71f36915f50bbb74dc746ac2f9c7a8f3489995f8dc67648a58ceb20e85cd71c17072b7b967e415c1be4d3f349c378afcc7b1ed132e2682711cca1c98958d7bc
6
+ metadata.gz: a9c7add460f50301b9f404880e9f6ac2fc57996f1e4c8928f2c52b9bfa5f4973f6cc3c531729763a40de6cce127b86aed70b88ea11fc2119e9fe1856f81a4996
7
+ data.tar.gz: 7d5951ebd7f02917344ea94c23d4f7e5d467d7d803be2944ab1d073aa207acca55a065aa8e2b9145f129154c1ed10b9fb2d14b0692c1e1d8ec21a99f042dab8a
@@ -45,7 +45,7 @@ module CanvasSync::JobBatches
45
45
 
46
46
  def call(_worker, msg, _queue, _redis_pool = nil)
47
47
  if (batch = Thread.current[CURRENT_BATCH_THREAD_KEY]) && should_handle_batch?(msg)
48
- batch.increment_job_queue(msg['jid']) if (msg[:bid] = batch.bid)
48
+ batch.increment_job_queue(msg['jid']) if (msg['bid'] = batch.bid)
49
49
  end
50
50
  yield
51
51
  end
@@ -41,7 +41,7 @@ module CanvasSync::JobUniqueness
41
41
 
42
42
  def deserialize(data)
43
43
  super
44
- @uniqueness_cache_data = data['uniqueness_cache_data'].symbolize_keys
44
+ @uniqueness_cache_data = data['uniqueness_cache_data']&.symbolize_keys
45
45
  end
46
46
 
47
47
  def uniqueness_lock_context
@@ -19,7 +19,7 @@ module CanvasSync::JobUniqueness
19
19
 
20
20
  class SidekiqLockContext < LockContext
21
21
  def job_scheduled_at
22
- @job_instance["at"]
22
+ @job_instance&.[]("at")
23
23
  end
24
24
 
25
25
  def reenqueue(schedule_in:)
@@ -38,7 +38,7 @@ module CanvasSync::JobUniqueness
38
38
  queue: msg['queue'],
39
39
  args: msg['args'],
40
40
  # kwargs: msg['kwargs'],
41
- **(msg['uniqueness_cache_data'] || {}),
41
+ **(msg['uniqueness_cache_data']&.symbolize_keys || {}),
42
42
  }, job_instance: msg)
43
43
  end
44
44
 
@@ -58,7 +58,7 @@ module CanvasSync::JobUniqueness
58
58
  def call(_worker, msg, _queue, _redis_pool = nil, &blk)
59
59
  ctx = lock_context(msg)
60
60
  return blk.call unless ctx
61
- msg['uniqueness_cache_data'] = ctx.cache_data
61
+ msg['uniqueness_cache_data'] = ctx.cache_data.stringify_keys
62
62
  ctx.handle_lifecycle!(:enqueue, &blk)
63
63
  end
64
64
  end
@@ -6,18 +6,23 @@ module CanvasSync::JobUniqueness
6
6
  context_class.new(data, **kwargs)
7
7
  end
8
8
 
9
+ attr_reader :lock_id
10
+
9
11
  # { job_clazz, jid, queue, args?, kwargs?, base_key? }
10
12
  def initialize(data, job_instance: nil, config: nil)
11
13
  @base_key = data[:base_key]
12
14
  @context_data = data
13
15
  @job_instance = job_instance
14
16
  @config = config || @context_data[:config]
17
+
18
+ # TODO Consider (somewhere) updating the lock_id to the BID of the wrapping Batch (when applicable)
19
+ @lock_id ||= data[:lid] || Thread.current[:unique_jobs_previous_context]&.lock_id || job_id
15
20
  end
16
21
 
17
22
  # This is primarily for rehydrating in a Batch Callback, so it is unlikely that args and kwargs are needed.
18
- # Honestly, base_key and job_clazz are probably the only needed values
19
23
  def serialize
20
24
  {
25
+ lid: lock_id,
21
26
  clazz: self.class.to_s,
22
27
  job_clazz: @context_data[:job_clazz].to_s,
23
28
  jid: @context_data[:jid],
@@ -29,6 +34,7 @@ module CanvasSync::JobUniqueness
29
34
  # Properties to cache on the serialized Job object to prevent issues arising from code changes between enqueue and perform
30
35
  def cache_data
31
36
  {
37
+ lid: lock_id,
32
38
  base_key: base_key,
33
39
  job_score: job_score,
34
40
  # TODO Should config also be cached on the Job at time of enqueue?
@@ -38,6 +44,7 @@ module CanvasSync::JobUniqueness
38
44
 
39
45
  def debug_data
40
46
  {
47
+ lid: lock_id,
41
48
  context_class: self.class.to_s,
42
49
  job_class: job_class.to_s,
43
50
  queue: job_queue,
@@ -146,7 +153,6 @@ module CanvasSync::JobUniqueness
146
153
  # @yieldreturn [void] the yield is irrelevant, it only provides a mechanism in
147
154
  # one specific situation to yield back to the middleware.
148
155
  def call_conflict_strategy(origin)
149
- new_job_id = nil
150
156
  strategy = conflict_strategy_for(origin)
151
157
 
152
158
  strategy.call
@@ -14,7 +14,7 @@ module CanvasSync::JobUniqueness
14
14
 
15
15
  def initialize(key, lock_context, redis_pool = nil)
16
16
  @lock_context = lock_context
17
- @job_id = lock_context.job_id
17
+ @job_id = lock_context.lock_id # Yes, .lock_id is intentional
18
18
  @item = lock_context
19
19
  @key = SidekiqUniqueJobs::Key.new(key)
20
20
 
@@ -28,7 +28,7 @@ module CanvasSync::JobUniqueness
28
28
  :"limit" => lcfg[:limit],
29
29
  })
30
30
 
31
- @redis_pool = redis_pool
31
+ @redis_pool = redis_pool
32
32
  end
33
33
 
34
34
  def locked_jids
@@ -4,12 +4,12 @@ module CanvasSync::JobUniqueness
4
4
  valid_for :perform
5
5
 
6
6
  def call
7
- Thread.current[:unique_jobs_previous_jid] = lock_context.job_id
7
+ Thread.current[:unique_jobs_previous_context] = lock_context
8
8
  rescheduled = lock_context.reenqueue(
9
9
  schedule_in: schedule_in,
10
10
  )
11
11
  ensure
12
- Thread.current[:unique_jobs_previous_jid] = nil
12
+ Thread.current[:unique_jobs_previous_context] = nil
13
13
  end
14
14
 
15
15
  def schedule_in
@@ -43,7 +43,7 @@ module CanvasSync::JobUniqueness
43
43
  end
44
44
 
45
45
  def wrap_in_batch(&blk)
46
- if Thread.current[:unique_jobs_previous_jid] # Ensure we don't re-wrap in a batch when rescheduling
46
+ if Thread.current[:unique_jobs_previous_context] # Ensure we don't re-wrap in a batch when rescheduling
47
47
  return blk.call
48
48
  end
49
49
 
@@ -62,7 +62,7 @@ module CanvasSync::JobUniqueness
62
62
  })
63
63
  end
64
64
 
65
- CanvasSync::JobUniqueness.logger.debug("Wrapped job in Locking Batch #{batch.bid} for #{key}")
65
+ CanvasSync::JobUniqueness.logger.debug("Wrapped job (#{lock_context.lock_id}) in Locking Batch #{batch.bid} for #{key}")
66
66
 
67
67
  batch.jobs do
68
68
  return blk.call
@@ -74,7 +74,7 @@ module CanvasSync::JobUniqueness
74
74
  CanvasSync::JobUniqueness.logger.debug("Context data: #{opts[:lock_context]}")
75
75
  strategy_class = opts[:lock_strategy].constantize
76
76
  lock_context = LockContext.from_serialized(opts[:lock_context])
77
- CanvasSync::JobUniqueness.logger.debug("Rehydrated LockContext: #{lock_context.job_id} #{lock_context.debug_data}")
77
+ CanvasSync::JobUniqueness.logger.debug("Rehydrated LockContext: #{lock_context.lock_id} #{lock_context.debug_data}")
78
78
  strategy = strategy_class.new(lock_context)
79
79
  # TODO Should this route through LockContext#handle_lifecycle!?
80
80
  strategy.batch_callback(opts[:event].to_sym, batch_status)
@@ -83,13 +83,9 @@ module CanvasSync::JobUniqueness
83
83
  def lock!(purpose, wait: nil)
84
84
  locked = nil
85
85
  if purpose == :enqueue
86
- if Thread.current[:unique_jobs_previous_jid].present?
87
- locked = locksmith.swap_locks(Thread.current[:unique_jobs_previous_jid])
88
- else
89
- locked = locksmith.lock()
90
- end
86
+ locked = locksmith.lock()
91
87
  elsif purpose == :perform
92
- locked = locksmith.execute { lock_context.job_id }
88
+ locked = locksmith.execute { lock_context.lock_id }
93
89
  end
94
90
 
95
91
  CanvasSync::JobUniqueness.logger.debug { "Requested lock of #{key} for #{purpose} - (#{locked || 'Not Obtained!'})" }
@@ -98,7 +94,7 @@ module CanvasSync::JobUniqueness
98
94
  end
99
95
 
100
96
  def unlock()
101
- CanvasSync::JobUniqueness.logger.debug { "Trying to unlock #{key} for JID #{lock_context.job_id}" }
97
+ CanvasSync::JobUniqueness.logger.debug { "Trying to unlock #{key} for LID #{lock_context.lock_id}" }
102
98
  result = locksmith.unlock
103
99
  CanvasSync::JobUniqueness.logger.debug { "Unlocked #{key} - (#{result || 'Not Unlocked!'})" }
104
100
  end
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.22.0.beta7".freeze
2
+ VERSION = "0.22.0".freeze
3
3
  end
@@ -74,7 +74,7 @@ RSpec.describe CanvasSync::JobBatches::Compat::Sidekiq do
74
74
  it 'assigns bid to msg' do
75
75
  msg = { 'jid' => jid }
76
76
  subject.call(nil, msg, nil) {}
77
- expect(msg[:bid]).to eq(bid)
77
+ expect(msg['bid']).to eq(bid)
78
78
  end
79
79
  end
80
80
  end
@@ -6,7 +6,7 @@ RSpec.describe CanvasSync::JobUniqueness::OnConflict::Reschedule do
6
6
 
7
7
  it "calls reenqueue" do
8
8
  expect(lock_context).to receive(:reenqueue) do |*args, **kwargs|
9
- expect(Thread.current[:unique_jobs_previous_jid]).to be_present
9
+ expect(Thread.current[:unique_jobs_previous_context]).to be_present
10
10
  end
11
11
  on_conflict.call
12
12
  end
@@ -21,4 +21,43 @@ RSpec.describe CanvasSync::JobUniqueness::OnConflict::Reschedule do
21
21
  lock_context2.handle_lifecycle!(:enqueue) {}
22
22
  lock_context2.handle_lifecycle!(:perform) {}
23
23
  end
24
+
25
+ it "maintains a consistent lock_id" do
26
+ TestWorker.clear
27
+ TestWorker.unique_job_options.merge!({ strategy: :while_executing, on_conflict: :reschedule })
28
+
29
+ lock_context.handle_lifecycle!(:enqueue) {}
30
+ lock_context.handle_lifecycle!(:perform) {}
31
+
32
+ TestWorker.perform_async
33
+ j1 = TestWorker.jobs[0]
34
+ TestWorker.perform_one
35
+ j2 = TestWorker.jobs[0]
36
+
37
+ expect(j1).not_to eq(j2)
38
+ expect(j1['uniqueness_cache_data']['lid']).to be_present
39
+ expect(j1['uniqueness_cache_data']['lid']).to eq(j2['uniqueness_cache_data']['lid'])
40
+ end
41
+
42
+ it "unlocks after rescheduling" do
43
+ TestWorker.clear
44
+ TestWorker.unique_job_options.merge!({ strategy: :while_executing, on_conflict: :reschedule })
45
+
46
+ lock_context.handle_lifecycle!(:enqueue) {}
47
+ lock_context.handle_lifecycle!(:perform) {}
48
+
49
+ TestWorker.perform_async
50
+ expect(TestWorker.jobs.size).to eq(1)
51
+ TestWorker.perform_one
52
+ expect(TestWorker.jobs.size).to eq(1)
53
+
54
+ lock_context.lock_strategy.send(:unlock)
55
+ Sidekiq::Worker.drain_all
56
+ expect(TestWorker.jobs.size).to eq(0)
57
+
58
+ # Run another worker to validate that the lock was released
59
+ TestWorker.perform_async
60
+ TestWorker.perform_one
61
+ expect(TestWorker.jobs.size).to eq(0)
62
+ end
24
63
  end
@@ -1,10 +1,13 @@
1
1
  require_relative "../spec_helper"
2
2
  require "canvas_sync/job_uniqueness/job_uniqueness"
3
3
 
4
+ require 'redis'
5
+ Redis.silence_deprecations = true
6
+
4
7
  Dir[File.join(File.dirname(__FILE__), "./support/**/*.rb")].sort.each { |f| require f }
5
8
 
6
9
  Sidekiq::Testing.server_middleware do |chain|
7
- chain.insert_before CanvasSync::JobBatches::Compat::Sidekiq::ServerMiddleware, CanvasSync::JobUniqueness::Compat::Sidekiq::ServerMiddleware
10
+ chain.insert_after CanvasSync::JobBatches::Compat::Sidekiq::ServerMiddleware, CanvasSync::JobUniqueness::Compat::Sidekiq::ServerMiddleware
8
11
  end
9
12
 
10
13
  RSpec.configure do |config|
@@ -9,11 +9,11 @@ RSpec.describe CanvasSync::JobUniqueness::Strategy::Base do
9
9
 
10
10
  describe '#wrap_in_batch' do
11
11
  it 'does not wrap if re-enqueuing' do
12
- Thread.current[:unique_jobs_previous_jid] = "j100"
12
+ Thread.current[:unique_jobs_previous_context] = context
13
13
  expect(CanvasSync::JobBatches::Batch).not_to receive(:new)
14
14
  strategy.send(:wrap_in_batch, &->{ })
15
15
  ensure
16
- Thread.current[:unique_jobs_previous_jid] = nil
16
+ Thread.current[:unique_jobs_previous_context] = nil
17
17
  end
18
18
 
19
19
  context 'when the job fails' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canvas_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0.beta7
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Instructure CustomDev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-07-25 00:00:00.000000000 Z
11
+ date: 2024-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -870,9 +870,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
870
870
  version: '0'
871
871
  required_rubygems_version: !ruby/object:Gem::Requirement
872
872
  requirements:
873
- - - ">"
873
+ - - ">="
874
874
  - !ruby/object:Gem::Version
875
- version: 1.3.1
875
+ version: '0'
876
876
  requirements: []
877
877
  rubygems_version: 3.1.6
878
878
  signing_key: