canvas_sync 0.22.0.beta7 → 0.22.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|