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 +4 -4
- data/lib/canvas_sync/job_batches/compat/sidekiq.rb +1 -1
- data/lib/canvas_sync/job_uniqueness/compat/active_job.rb +1 -1
- data/lib/canvas_sync/job_uniqueness/compat/sidekiq.rb +3 -3
- data/lib/canvas_sync/job_uniqueness/lock_context.rb +8 -2
- data/lib/canvas_sync/job_uniqueness/locksmith.rb +2 -2
- data/lib/canvas_sync/job_uniqueness/on_conflict/reschedule.rb +2 -2
- data/lib/canvas_sync/job_uniqueness/strategy/base.rb +6 -10
- data/lib/canvas_sync/version.rb +1 -1
- data/spec/job_batching/compat/sidekiq_spec.rb +1 -1
- data/spec/job_uniqueness/on_conflict/reschedule_spec.rb +40 -1
- data/spec/job_uniqueness/spec_helper.rb +4 -1
- data/spec/job_uniqueness/strategy/base_spec.rb +2 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b659edf35a8c6f427f847142d4627920cc19effabfe7fb5ad2ccb960884cf47c
|
4
|
+
data.tar.gz: 3264f9b298a5fcbf251a6ccd927ce20133598ed1b9fdd8adf3a728ef90a4e080
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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[
|
48
|
+
batch.increment_job_queue(msg['jid']) if (msg['bid'] = batch.bid)
|
49
49
|
end
|
50
50
|
yield
|
51
51
|
end
|
@@ -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.
|
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
|
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[:
|
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[:
|
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[:
|
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.
|
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
|
-
|
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.
|
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
|
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
|
data/lib/canvas_sync/version.rb
CHANGED
@@ -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[:
|
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.
|
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[:
|
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[:
|
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
|
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-
|
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:
|
875
|
+
version: '0'
|
876
876
|
requirements: []
|
877
877
|
rubygems_version: 3.1.6
|
878
878
|
signing_key:
|