canvas_sync 0.23.5 → 0.24.0.beta1
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/Rakefile +2 -4
- data/lib/canvas_sync/version.rb +1 -1
- data/lib/canvas_sync.rb +6 -4
- data/spec/canvas_sync/canvas_sync_spec.rb +11 -11
- data/spec/canvas_sync/live_events/process_event_job_spec.rb +1 -0
- data/spec/{dummy → internal}/app/models/application_record.rb +1 -0
- data/spec/{dummy → internal}/app/models/content_migration.rb +0 -0
- data/spec/{dummy → internal}/app/models/course.rb +0 -1
- data/spec/{dummy → internal}/app/models/course_nickname.rb +0 -0
- data/spec/{dummy → internal}/app/models/learning_outcome.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/assignment_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/course_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/course_section_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/enrollment_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/grade_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/module_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/module_item_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/submission_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/syllabus_event.rb +0 -0
- data/spec/{dummy → internal}/app/services/live_events/user_event.rb +0 -0
- data/spec/internal/bin/rails +9 -0
- data/spec/internal/config/database.yml +5 -0
- data/spec/internal/config/routes.rb +5 -0
- data/spec/internal/config/storage.yml +3 -0
- data/spec/{dummy/db/migrate/20220926221926_create_users.rb → internal/db/migrate/20250912205136_create_users.rb} +0 -0
- data/spec/{dummy/db/migrate/20201016181346_create_pseudonyms.rb → internal/db/migrate/20250912205137_create_pseudonyms.rb} +0 -0
- data/spec/{dummy/db/migrate/20200415171620_create_groups.rb → internal/db/migrate/20250912205139_create_groups.rb} +0 -0
- data/spec/{dummy/db/migrate/20200416214248_create_group_memberships.rb → internal/db/migrate/20250912205140_create_group_memberships.rb} +0 -0
- data/spec/{dummy/db/migrate/20190702203622_create_accounts.rb → internal/db/migrate/20250912205141_create_accounts.rb} +0 -0
- data/spec/{dummy/db/migrate/20190702203623_create_terms.rb → internal/db/migrate/20250912205142_create_terms.rb} +0 -0
- data/spec/{dummy/db/migrate/20190702203625_create_sections.rb → internal/db/migrate/20250912205144_create_sections.rb} +0 -0
- data/spec/{dummy/db/migrate/20190702203626_create_assignments.rb → internal/db/migrate/20250912205145_create_assignments.rb} +0 -0
- data/spec/{dummy/db/migrate/20190702203627_create_submissions.rb → internal/db/migrate/20250912205146_create_submissions.rb} +0 -0
- data/spec/{dummy/db/migrate/20190927204545_create_roles.rb → internal/db/migrate/20250912205147_create_roles.rb} +2 -2
- data/spec/{dummy/db/migrate/20190927204546_create_admins.rb → internal/db/migrate/20250912205148_create_admins.rb} +0 -0
- data/spec/{dummy/db/migrate/20190702203630_create_assignment_groups.rb → internal/db/migrate/20250912205149_create_assignment_groups.rb} +0 -0
- data/spec/{dummy/db/migrate/20190702203631_create_context_modules.rb → internal/db/migrate/20250912205150_create_context_modules.rb} +0 -0
- data/spec/{dummy/db/migrate/20190702203632_create_context_module_items.rb → internal/db/migrate/20250912205151_create_context_module_items.rb} +0 -0
- data/spec/{dummy/db/migrate/20210907233329_create_user_observers.rb → internal/db/migrate/20250912205152_create_user_observers.rb} +0 -0
- data/spec/{dummy/db/migrate/20210907233330_create_grading_periods.rb → internal/db/migrate/20250912205153_create_grading_periods.rb} +0 -0
- data/spec/{dummy/db/migrate/20211001184920_create_grading_period_groups.rb → internal/db/migrate/20250912205154_create_grading_period_groups.rb} +0 -0
- data/spec/{dummy/db/migrate/20220308072643_create_content_migrations.rb → internal/db/migrate/20250912205155_create_content_migrations.rb} +0 -0
- data/spec/{dummy/db/migrate/20220712210559_create_learning_outcomes.rb → internal/db/migrate/20250912205156_create_learning_outcomes.rb} +0 -0
- data/spec/{dummy/db/migrate/20240523101010_create_learning_outcome_results.rb → internal/db/migrate/20250912205157_create_learning_outcome_results.rb} +0 -0
- data/spec/{dummy/db/migrate/20240510094100_create_rubric_associations.rb → internal/db/migrate/20250912205160_create_rubric_associations.rb} +0 -0
- data/spec/{dummy/db/migrate/20240510101100_create_rubric_assessments.rb → internal/db/migrate/20250912205161_create_rubric_assessments.rb} +0 -0
- data/spec/{dummy/db/migrate/20240828161300_create_course_progresses.rb → internal/db/migrate/20250912205162_create_course_progresses.rb} +0 -0
- data/spec/internal/db/schema.rb +6 -0
- data/spec/spec_helper.rb +8 -4
- metadata +182 -372
- data/lib/canvas_sync/job_batches/batch.rb +0 -595
- data/lib/canvas_sync/job_batches/callback.rb +0 -135
- data/lib/canvas_sync/job_batches/chain_builder.rb +0 -247
- data/lib/canvas_sync/job_batches/compat/active_job.rb +0 -108
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/batches_assets/css/styles.less +0 -182
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/batches_assets/js/batch_tree.js +0 -108
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/batches_assets/js/util.js +0 -2
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/helpers.rb +0 -41
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_batch_tree.erb +0 -6
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_batches_table.erb +0 -44
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_common.erb +0 -13
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_jobs_table.erb +0 -21
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_pagination.erb +0 -26
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/batch.erb +0 -81
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/batches.erb +0 -23
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/pool.erb +0 -137
- data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/pools.erb +0 -47
- data/lib/canvas_sync/job_batches/compat/sidekiq/web.rb +0 -218
- data/lib/canvas_sync/job_batches/compat/sidekiq.rb +0 -149
- data/lib/canvas_sync/job_batches/compat.rb +0 -20
- data/lib/canvas_sync/job_batches/context_hash.rb +0 -157
- data/lib/canvas_sync/job_batches/hier_batch_ids.lua +0 -25
- data/lib/canvas_sync/job_batches/jobs/base_job.rb +0 -5
- data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +0 -20
- data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +0 -175
- data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +0 -20
- data/lib/canvas_sync/job_batches/pool.rb +0 -254
- data/lib/canvas_sync/job_batches/pool_refill.lua +0 -47
- data/lib/canvas_sync/job_batches/redis_model.rb +0 -67
- data/lib/canvas_sync/job_batches/redis_script.rb +0 -161
- data/lib/canvas_sync/job_batches/schedule_callback.lua +0 -14
- data/lib/canvas_sync/job_batches/status.rb +0 -89
- data/lib/canvas_sync/job_uniqueness/compat/active_job.rb +0 -75
- data/lib/canvas_sync/job_uniqueness/compat/sidekiq.rb +0 -135
- data/lib/canvas_sync/job_uniqueness/compat.rb +0 -20
- data/lib/canvas_sync/job_uniqueness/configuration.rb +0 -25
- data/lib/canvas_sync/job_uniqueness/job_uniqueness.rb +0 -47
- data/lib/canvas_sync/job_uniqueness/lock_context.rb +0 -199
- data/lib/canvas_sync/job_uniqueness/locksmith.rb +0 -92
- data/lib/canvas_sync/job_uniqueness/on_conflict/base.rb +0 -32
- data/lib/canvas_sync/job_uniqueness/on_conflict/log.rb +0 -13
- data/lib/canvas_sync/job_uniqueness/on_conflict/null_strategy.rb +0 -9
- data/lib/canvas_sync/job_uniqueness/on_conflict/raise.rb +0 -11
- data/lib/canvas_sync/job_uniqueness/on_conflict/reject.rb +0 -21
- data/lib/canvas_sync/job_uniqueness/on_conflict/reschedule.rb +0 -20
- data/lib/canvas_sync/job_uniqueness/on_conflict.rb +0 -62
- data/lib/canvas_sync/job_uniqueness/strategy/base.rb +0 -107
- data/lib/canvas_sync/job_uniqueness/strategy/until_and_while_executing.rb +0 -35
- data/lib/canvas_sync/job_uniqueness/strategy/until_executed.rb +0 -20
- data/lib/canvas_sync/job_uniqueness/strategy/until_executing.rb +0 -20
- data/lib/canvas_sync/job_uniqueness/strategy/until_expired.rb +0 -16
- data/lib/canvas_sync/job_uniqueness/strategy/while_executing.rb +0 -26
- data/lib/canvas_sync/job_uniqueness/strategy.rb +0 -27
- data/lib/canvas_sync/job_uniqueness/unique_job_common.rb +0 -79
- data/spec/dummy/README.rdoc +0 -1
- data/spec/dummy/Rakefile +0 -6
- data/spec/dummy/app/services/live_events/assignment_created_event.rb +0 -12
- data/spec/dummy/app/services/live_events/assignment_updated_event.rb +0 -12
- data/spec/dummy/app/services/live_events/base_event.rb +0 -52
- data/spec/dummy/app/services/live_events/course_created_event.rb +0 -12
- data/spec/dummy/app/services/live_events/course_section_created_event.rb +0 -12
- data/spec/dummy/app/services/live_events/course_section_updated_event.rb +0 -12
- data/spec/dummy/app/services/live_events/course_updated_event.rb +0 -12
- data/spec/dummy/app/services/live_events/enrollment_created_event.rb +0 -12
- data/spec/dummy/app/services/live_events/enrollment_updated_event.rb +0 -12
- data/spec/dummy/app/services/live_events/grade_changed_event.rb +0 -12
- data/spec/dummy/app/services/live_events/module_created_event.rb +0 -12
- data/spec/dummy/app/services/live_events/module_item_created_event.rb +0 -12
- data/spec/dummy/app/services/live_events/module_item_updated_event.rb +0 -12
- data/spec/dummy/app/services/live_events/module_updated_event.rb +0 -12
- data/spec/dummy/app/services/live_events/submission_created_event.rb +0 -12
- data/spec/dummy/app/services/live_events/submission_updated_event.rb +0 -12
- data/spec/dummy/app/services/live_events/syllabus_updated_event.rb +0 -12
- data/spec/dummy/app/services/live_events/user_created_event.rb +0 -12
- data/spec/dummy/app/services/live_events/user_updated_event.rb +0 -12
- data/spec/dummy/bin/rails +0 -4
- data/spec/dummy/config/application.rb +0 -39
- data/spec/dummy/config/boot.rb +0 -5
- data/spec/dummy/config/database.yml +0 -25
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/development.rb +0 -41
- data/spec/dummy/config/environments/test.rb +0 -44
- data/spec/dummy/config/initializers/assets.rb +0 -11
- data/spec/dummy/config/initializers/session_store.rb +0 -3
- data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
- data/spec/dummy/config/routes.rb +0 -3
- data/spec/dummy/config/secrets.yml +0 -22
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/db/schema.rb +0 -563
- data/spec/job_batching/batch_spec.rb +0 -531
- data/spec/job_batching/callback_spec.rb +0 -38
- data/spec/job_batching/compat/active_job_spec.rb +0 -107
- data/spec/job_batching/compat/sidekiq_spec.rb +0 -127
- data/spec/job_batching/context_hash_spec.rb +0 -54
- data/spec/job_batching/flow_spec.rb +0 -82
- data/spec/job_batching/integration/fail_then_succeed.rb +0 -42
- data/spec/job_batching/integration/integration.rb +0 -57
- data/spec/job_batching/integration/nested.rb +0 -88
- data/spec/job_batching/integration/simple.rb +0 -47
- data/spec/job_batching/integration/workflow.rb +0 -134
- data/spec/job_batching/integration_helper.rb +0 -50
- data/spec/job_batching/pool_spec.rb +0 -161
- data/spec/job_batching/status_spec.rb +0 -76
- data/spec/job_batching/support/base_job.rb +0 -14
- data/spec/job_batching/support/sample_callback.rb +0 -2
- data/spec/job_uniqueness/compat/active_job_spec.rb +0 -49
- data/spec/job_uniqueness/compat/sidekiq_spec.rb +0 -68
- data/spec/job_uniqueness/lock_context_spec.rb +0 -106
- data/spec/job_uniqueness/on_conflict/log_spec.rb +0 -11
- data/spec/job_uniqueness/on_conflict/raise_spec.rb +0 -10
- data/spec/job_uniqueness/on_conflict/reschedule_spec.rb +0 -63
- data/spec/job_uniqueness/on_conflict_spec.rb +0 -16
- data/spec/job_uniqueness/spec_helper.rb +0 -17
- data/spec/job_uniqueness/strategy/base_spec.rb +0 -100
- data/spec/job_uniqueness/strategy/until_and_while_executing_spec.rb +0 -48
- data/spec/job_uniqueness/strategy/until_executed_spec.rb +0 -23
- data/spec/job_uniqueness/strategy/until_executing_spec.rb +0 -23
- data/spec/job_uniqueness/strategy/until_expired_spec.rb +0 -23
- data/spec/job_uniqueness/strategy/while_executing_spec.rb +0 -33
- data/spec/job_uniqueness/support/lock_strategy.rb +0 -28
- data/spec/job_uniqueness/support/on_conflict.rb +0 -24
- data/spec/job_uniqueness/support/test_worker.rb +0 -19
- data/spec/job_uniqueness/unique_job_common_spec.rb +0 -45
- /data/spec/{dummy → internal}/app/models/account.rb +0 -0
- /data/spec/{dummy → internal}/app/models/admin.rb +0 -0
- /data/spec/{dummy → internal}/app/models/assignment.rb +0 -0
- /data/spec/{dummy → internal}/app/models/assignment_group.rb +0 -0
- /data/spec/{dummy → internal}/app/models/assignment_override.rb +0 -0
- /data/spec/{dummy → internal}/app/models/context_module.rb +0 -0
- /data/spec/{dummy → internal}/app/models/context_module_item.rb +0 -0
- /data/spec/{dummy → internal}/app/models/course_progress.rb +0 -0
- /data/spec/{dummy → internal}/app/models/enrollment.rb +0 -0
- /data/spec/{dummy → internal}/app/models/grading_period.rb +0 -0
- /data/spec/{dummy → internal}/app/models/grading_period_group.rb +0 -0
- /data/spec/{dummy → internal}/app/models/group.rb +0 -0
- /data/spec/{dummy → internal}/app/models/group_membership.rb +0 -0
- /data/spec/{dummy → internal}/app/models/learning_outcome_result.rb +0 -0
- /data/spec/{dummy → internal}/app/models/pseudonym.rb +0 -0
- /data/spec/{dummy → internal}/app/models/role.rb +0 -0
- /data/spec/{dummy → internal}/app/models/rubric.rb +0 -0
- /data/spec/{dummy → internal}/app/models/rubric_assessment.rb +0 -0
- /data/spec/{dummy → internal}/app/models/rubric_association.rb +0 -0
- /data/spec/{dummy → internal}/app/models/score.rb +0 -0
- /data/spec/{dummy → internal}/app/models/section.rb +0 -0
- /data/spec/{dummy → internal}/app/models/submission.rb +0 -0
- /data/spec/{dummy → internal}/app/models/term.rb +0 -0
- /data/spec/{dummy → internal}/app/models/user.rb +0 -0
- /data/spec/{dummy → internal}/app/models/user_observer.rb +0 -0
- /data/spec/{dummy/db/migrate/20190702203621_create_courses.rb → internal/db/migrate/20250912205138_create_courses.rb} +0 -0
- /data/spec/{dummy/db/migrate/20190702203624_create_enrollments.rb → internal/db/migrate/20250912205143_create_enrollments.rb} +0 -0
- /data/spec/{dummy/db/migrate/20250319194134_create_course_nicknames.rb → internal/db/migrate/20250912205158_create_course_nicknames.rb} +0 -0
- /data/spec/{dummy/db/migrate/20250319194135_create_rubrics.rb → internal/db/migrate/20250912205159_create_rubrics.rb} +0 -0
- /data/spec/{dummy/db/migrate/20241223080202_create_assignment_overrides.rb → internal/db/migrate/20250912205163_create_assignment_overrides.rb} +0 -0
- /data/spec/{dummy/db/migrate/20250626194330_create_scores.rb → internal/db/migrate/20250912205164_create_scores.rb} +0 -0
@@ -1,199 +0,0 @@
|
|
1
|
-
|
2
|
-
module CanvasSync::JobUniqueness
|
3
|
-
class LockContext
|
4
|
-
def self.from_serialized(data, **kwargs)
|
5
|
-
context_class = data[:clazz]&.constantize || self
|
6
|
-
context_class.new(data, **kwargs)
|
7
|
-
end
|
8
|
-
|
9
|
-
attr_reader :lock_id
|
10
|
-
|
11
|
-
# { job_clazz, jid, queue, args?, kwargs?, base_key? }
|
12
|
-
def initialize(data, job_instance: nil, config: nil)
|
13
|
-
@base_key = data[:base_key]
|
14
|
-
@context_data = data
|
15
|
-
@job_instance = job_instance
|
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
|
20
|
-
end
|
21
|
-
|
22
|
-
# This is primarily for rehydrating in a Batch Callback, so it is unlikely that args and kwargs are needed.
|
23
|
-
def serialize
|
24
|
-
{
|
25
|
-
lid: lock_id,
|
26
|
-
clazz: self.class.to_s,
|
27
|
-
job_clazz: @context_data[:job_clazz].to_s,
|
28
|
-
jid: @context_data[:jid],
|
29
|
-
queue: @context_data[:queue],
|
30
|
-
**cache_data,
|
31
|
-
}
|
32
|
-
end
|
33
|
-
|
34
|
-
# Properties to cache on the serialized Job object to prevent issues arising from code changes between enqueue and perform
|
35
|
-
def cache_data
|
36
|
-
{
|
37
|
-
lid: lock_id,
|
38
|
-
base_key: base_key,
|
39
|
-
job_score: job_score,
|
40
|
-
# TODO Should config also be cached on the Job at time of enqueue?
|
41
|
-
# config: config,
|
42
|
-
}
|
43
|
-
end
|
44
|
-
|
45
|
-
def debug_data
|
46
|
-
{
|
47
|
-
lid: lock_id,
|
48
|
-
context_class: self.class.to_s,
|
49
|
-
job_class: job_class.to_s,
|
50
|
-
queue: job_queue,
|
51
|
-
limit: config[:limit],
|
52
|
-
timeout: config[:timeout],
|
53
|
-
ttl: config[:ttl],
|
54
|
-
strategy: config[:strategy],
|
55
|
-
time: Time.now.to_f,
|
56
|
-
at: job_scheduled_at,
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
def handle_lifecycle!(stage, *args, **kwargs, &blk)
|
61
|
-
lock_strategy.send(:"on_#{stage}", *args, **kwargs, &blk)
|
62
|
-
rescue CouldNotLockError => e
|
63
|
-
call_conflict_strategy(stage)
|
64
|
-
end
|
65
|
-
|
66
|
-
def lock_strategy
|
67
|
-
return @lock_strategy if defined?(@lock_strategy)
|
68
|
-
|
69
|
-
strat_name = config[:strategy]
|
70
|
-
@lock_strategy = Strategy.lookup(strat_name).new(self)
|
71
|
-
end
|
72
|
-
|
73
|
-
def config
|
74
|
-
@config ||= job_class.unique_job_options
|
75
|
-
end
|
76
|
-
|
77
|
-
def job_class
|
78
|
-
@job_class ||= begin
|
79
|
-
if (job_clazz = @context_data[:job_clazz]).is_a?(String)
|
80
|
-
job_clazz.constantize
|
81
|
-
else
|
82
|
-
job_clazz
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def job_id
|
88
|
-
@context_data[:jid]
|
89
|
-
end
|
90
|
-
|
91
|
-
def job_queue
|
92
|
-
@context_data[:queue]
|
93
|
-
end
|
94
|
-
|
95
|
-
def job_scheduled_at
|
96
|
-
nil
|
97
|
-
end
|
98
|
-
|
99
|
-
def job_score
|
100
|
-
@context_data[:job_score] || job_scheduled_at.to_s
|
101
|
-
end
|
102
|
-
|
103
|
-
def base_key(any_hash: false)
|
104
|
-
@base_key ||= begin
|
105
|
-
queue = @context_data[:queue] || "default"
|
106
|
-
|
107
|
-
base_key = [
|
108
|
-
CanvasSync::JobUniqueness.config.lock_prefix.presence,
|
109
|
-
].compact
|
110
|
-
|
111
|
-
scope = config[:scope]
|
112
|
-
if scope.is_a?(Proc)
|
113
|
-
base_key << scope.call(queue: queue)
|
114
|
-
elsif scope == :global
|
115
|
-
base_key << job_class.name
|
116
|
-
elsif scope == :per_queue
|
117
|
-
base_key << job_class.name
|
118
|
-
base_key << queue
|
119
|
-
else
|
120
|
-
base_key << scope
|
121
|
-
end
|
122
|
-
|
123
|
-
args = @context_data[:args] || []
|
124
|
-
kwargs = @context_data[:kwargs] || {}
|
125
|
-
hash = config[:hash]
|
126
|
-
if config[:hash].is_a?(Proc)
|
127
|
-
hash = config[:hash].call(*args, **kwargs)
|
128
|
-
elsif config[:hash].nil?
|
129
|
-
hash = [*args, kwargs]
|
130
|
-
end
|
131
|
-
|
132
|
-
hash = ":#{hash}" if hash.is_a?(Symbol)
|
133
|
-
|
134
|
-
if hash && !hash.is_a?(String)
|
135
|
-
hash = Array(hash)
|
136
|
-
|
137
|
-
# Normalize the hash to ensure that the order of any Hash keys don't matter
|
138
|
-
hash = normalize_hash_chunk(hash)
|
139
|
-
|
140
|
-
normalized = ActiveJob::Arguments.serialize(hash)
|
141
|
-
hash = OpenSSL::Digest::MD5.hexdigest(JSON.dump(normalized))
|
142
|
-
end
|
143
|
-
|
144
|
-
base_key << hash if hash
|
145
|
-
|
146
|
-
base_key.join(":")
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
def reenqueue
|
151
|
-
raise NotImplementedError, "needs to be implemented in child class"
|
152
|
-
end
|
153
|
-
|
154
|
-
protected
|
155
|
-
|
156
|
-
attr_reader :job_instance
|
157
|
-
|
158
|
-
#
|
159
|
-
# Call whatever strategy that has been configured
|
160
|
-
#
|
161
|
-
# @param [Symbol] origin the origin `:client` or `:server`
|
162
|
-
#
|
163
|
-
# @return [void] the return value is irrelevant
|
164
|
-
#
|
165
|
-
# @yieldparam [void] if a new job id was set and a block is given
|
166
|
-
# @yieldreturn [void] the yield is irrelevant, it only provides a mechanism in
|
167
|
-
# one specific situation to yield back to the middleware.
|
168
|
-
def call_conflict_strategy(origin)
|
169
|
-
strategy = conflict_strategy_for(origin)
|
170
|
-
|
171
|
-
strategy.call
|
172
|
-
end
|
173
|
-
|
174
|
-
def conflict_strategy_for(origin)
|
175
|
-
raise ArgumentError, "#origin needs to be either `:enqueue` or `:perform`" unless %i[enqueue perform].include?(origin)
|
176
|
-
|
177
|
-
@conflict_strategies ||= {}
|
178
|
-
@conflict_strategies[origin] ||= begin
|
179
|
-
cfg = config[:on_conflict]
|
180
|
-
cfg = cfg[origin] if cfg.is_a?(Hash)
|
181
|
-
|
182
|
-
cstrat_cls = OnConflict.lookup(cfg || :null_strategy)
|
183
|
-
cstrat = cstrat_cls.new(self) # TODO Pass redis_pool?
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
private
|
188
|
-
|
189
|
-
def normalize_hash_chunk(chunk)
|
190
|
-
if chunk.is_a?(Hash)
|
191
|
-
chunk.map { |k, v| [k, normalize_hash_chunk(v)] }.sort.to_h
|
192
|
-
elsif chunk.is_a?(Array)
|
193
|
-
chunk.map { |c| normalize_hash_chunk(c) }
|
194
|
-
else
|
195
|
-
chunk
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
@@ -1,92 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "sidekiq_unique_jobs"
|
4
|
-
|
5
|
-
module CanvasSync::JobUniqueness
|
6
|
-
# This class is intended to be the complete translation layer between CanvasSync::JobUniqueness and SidekiqUniqueJobs.
|
7
|
-
# In other words, you could consider it the "locking backend" and thus could potentially swap out SUJ for a more succinct solution.
|
8
|
-
#
|
9
|
-
# SUJ's implementation is somewhat complex, but is somewhat pre-tailored over (eg) https://github.com/leandromoreira/redlock-rb.
|
10
|
-
# Mainly SUJ tracks the JID so that if a process dies, another can pick up the job without having to figure out how to unlock it.
|
11
|
-
# SUJ also handles the integration into Sidekiq Web, which is a nice bonus.
|
12
|
-
class Locksmith < SidekiqUniqueJobs::Locksmith
|
13
|
-
attr_reader :lock_context
|
14
|
-
|
15
|
-
def initialize(key, lock_context, redis_pool = nil)
|
16
|
-
@lock_context = lock_context
|
17
|
-
@job_id = lock_context.lock_id # Yes, .lock_id is intentional
|
18
|
-
@item = lock_context
|
19
|
-
@key = SidekiqUniqueJobs::Key.new(key)
|
20
|
-
|
21
|
-
lcfg = lock_context.config
|
22
|
-
@config = OpenStruct.new({
|
23
|
-
:"type" => lcfg[:strategy],
|
24
|
-
:"pttl" => lcfg[:ttl] * 1000,
|
25
|
-
:"timeout" => lcfg[:timeout],
|
26
|
-
:"wait_for_lock?" => lcfg[:ttl]&.positive?,
|
27
|
-
:"lock_info" => false,
|
28
|
-
:"limit" => lcfg[:limit],
|
29
|
-
})
|
30
|
-
|
31
|
-
@redis_pool = redis_pool
|
32
|
-
end
|
33
|
-
|
34
|
-
def locked_jids
|
35
|
-
SidekiqUniqueJobs::Lock.new(@key).locked_jids
|
36
|
-
end
|
37
|
-
|
38
|
-
def swap_locks(old_jid)
|
39
|
-
olimit = lock_context.config[:limit]
|
40
|
-
new_jid = @job_id
|
41
|
-
return if old_jid == new_jid
|
42
|
-
|
43
|
-
# NB This is quite hacky, but should work
|
44
|
-
#
|
45
|
-
# Ideally the unlock(old) and lock(new) would be atomic, but that increases the amount of coupling with Sidekiq-Unique-Jobs - right now,
|
46
|
-
# we're using fairly stable (though still internal) SUJ APIs; I fear that writing custom Lua will be significantly more brittle
|
47
|
-
#
|
48
|
-
# In the general case, we'd only bump limit by 1, but that leaves a potential race-condition when limit is configured > 1:
|
49
|
-
# (Assuming until_and_while_executing, reschedule, limit = 2):
|
50
|
-
# (Workers are performing 2 Jobs, RUN lock count = 2)
|
51
|
-
# Worker 1 pulls Job A
|
52
|
-
# Worker 2 pulls Job B
|
53
|
-
# W1 and W2 both fail to get the runtime lock
|
54
|
-
# W1 and W2 call swap_locks
|
55
|
-
# W1 calls lock(limit+1), lock is granted, lock count becomes limit+1
|
56
|
-
# W2 calls lock(limit+1), lock is denied because count would be limit+2
|
57
|
-
# W1 calls unlock(old_jid)
|
58
|
-
|
59
|
-
# Force creation of another lock, ignoring the limit
|
60
|
-
@config.limit = olimit + 100
|
61
|
-
result = lock
|
62
|
-
|
63
|
-
# Release the old lock, bringing us back within the limit
|
64
|
-
@job_id = old_jid
|
65
|
-
unlock
|
66
|
-
|
67
|
-
result
|
68
|
-
ensure
|
69
|
-
@config.limit = olimit
|
70
|
-
@job_id = new_jid
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def lock_score
|
76
|
-
lock_context.job_score
|
77
|
-
end
|
78
|
-
|
79
|
-
def lock_info
|
80
|
-
@lock_info = JSON.dump(lock_context.debug_data)
|
81
|
-
end
|
82
|
-
|
83
|
-
# def taken?(conn)
|
84
|
-
# v = conn.hexists(key.locked, job_id)
|
85
|
-
# v.is_a?(Numeric) ? v != 0 : v
|
86
|
-
# end
|
87
|
-
|
88
|
-
def redis_version
|
89
|
-
@redis_version ||= CanvasSync::JobUniqueness.config.redis_version || SidekiqUniqueJobs.fetch_redis_version
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module OnConflict
|
3
|
-
class Base
|
4
|
-
attr_reader :lock_context
|
5
|
-
attr_reader :redis_pool
|
6
|
-
|
7
|
-
class_attribute :_valid_for, instance_writer: false
|
8
|
-
|
9
|
-
def self.valid_for(*origins)
|
10
|
-
if origins.present?
|
11
|
-
orgins = Array(origins).map(&:to_sym)
|
12
|
-
self._valid_for = origins
|
13
|
-
else
|
14
|
-
self._valid_for || [:enqueue, :perform]
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def initialize(lock_context, redis_pool = nil)
|
19
|
-
@lock_context = lock_context
|
20
|
-
@redis_pool = redis_pool
|
21
|
-
end
|
22
|
-
|
23
|
-
def call
|
24
|
-
raise NotImplementedError, "needs to be implemented in child class"
|
25
|
-
end
|
26
|
-
|
27
|
-
def replace?
|
28
|
-
is_a?(Replace)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module OnConflict
|
3
|
-
class Log < Base
|
4
|
-
valid_for :enqueue, :perform
|
5
|
-
|
6
|
-
def call
|
7
|
-
CanvasSync::JobUniqueness.logger.info(<<~MESSAGE.chomp)
|
8
|
-
Skipping job with id (#{lock_context.job_id}) because key: (#{lock_context.base_key}) is locked
|
9
|
-
MESSAGE
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module OnConflict
|
3
|
-
class Reject < Base
|
4
|
-
valid_for :perform
|
5
|
-
|
6
|
-
def call
|
7
|
-
# TODO Allow this to work on Sidekiq-backed ActiveJob
|
8
|
-
unless lock_context.is_a?(CanvasSync::JobUniqueness::Compat::Sidekiq::SidekiqLockContext)
|
9
|
-
CanvasSync::JobUniqueness.logger.error(":reject conflict strategy is not supported for non-Sidekiq-backed jobs")
|
10
|
-
return
|
11
|
-
end
|
12
|
-
|
13
|
-
kwargs = {}
|
14
|
-
kwargs[:notify_failure] = false if Sidekiq::DeadSet.instance_method(:kill).arity > 1
|
15
|
-
|
16
|
-
sidekiq_message = lock_context.instance_variable_get(:@job_instance)
|
17
|
-
Sidekiq::DeadSet.new.kill(JSON.dump(sidekiq_message), **kwargs)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module OnConflict
|
3
|
-
class Reschedule < OnConflict::Base
|
4
|
-
valid_for :perform
|
5
|
-
|
6
|
-
def call
|
7
|
-
Thread.current[:unique_jobs_previous_context] = lock_context
|
8
|
-
rescheduled = lock_context.reenqueue(
|
9
|
-
schedule_in: schedule_in,
|
10
|
-
)
|
11
|
-
ensure
|
12
|
-
Thread.current[:unique_jobs_previous_context] = nil
|
13
|
-
end
|
14
|
-
|
15
|
-
def schedule_in
|
16
|
-
lock_context.config[:reschedule_in] || 60
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module OnConflict
|
3
|
-
extend ActiveSupport::Autoload
|
4
|
-
|
5
|
-
autoload :Base
|
6
|
-
|
7
|
-
autoload :Log
|
8
|
-
autoload :NullStrategy
|
9
|
-
autoload :Raise
|
10
|
-
autoload :Reject
|
11
|
-
autoload :Reschedule
|
12
|
-
|
13
|
-
# Replace is... hard. It involves hacking into some of the inner workings of Sidekiq and/or ActiveJob.
|
14
|
-
# The best solution would be to somehow mark the previously-scheduled job as dead and let it be picked up by workers but not invoke
|
15
|
-
# its perform method (technically this would mess with success numbers, but does anybody really care about those?).
|
16
|
-
# Replace will also conflict with the UntilExecuted strategy as it wouldn't be able to replace a job that has begun executing.
|
17
|
-
#
|
18
|
-
# Replace is useful for two cases:
|
19
|
-
# 1. Moving the job to the back of the queue, eg for debouncing purposes
|
20
|
-
# 2. Replacing non-hashed params with updated values
|
21
|
-
# A solution to (2) would be to store the latest params outside of the Job params - eg in the DB or in Redis directly.
|
22
|
-
# (1) is solvebale in a similar out-of-band way, but other, better solutions may exist as well
|
23
|
-
#
|
24
|
-
# If the param in question is a (eg) a list that you want to append to, you could:
|
25
|
-
# Push to the list in Redis
|
26
|
-
# Queue the job
|
27
|
-
# (Push another param to the list in Redis)
|
28
|
-
# (Re-enqueue the job, but it won't enqueue because it is locked)
|
29
|
-
# When the job begins, it pulls all values from the field in Redis an processes
|
30
|
-
# Adding more params from this point would allow another job to enqueue
|
31
|
-
#
|
32
|
-
# We may implement some tooling for this at some point, but for now the recommendation is to handle this yourself,
|
33
|
-
# autoload :Replace
|
34
|
-
|
35
|
-
class << self
|
36
|
-
def lookup(strategy)
|
37
|
-
matching_strategy(strategy.to_s.camelize) ||
|
38
|
-
CanvasSync::JobUniqueness.config.conflict_strategies[strategy] ||
|
39
|
-
raise(StrategyNotFound, "on_conflict: #{strategy} is not found. Is it declared in the configuration?")
|
40
|
-
end
|
41
|
-
|
42
|
-
def validate!(on_conflict, lock_strategy)
|
43
|
-
on_conflict = { enqueue: on_conflict, perform: on_conflict } unless on_conflict.is_a?(Hash)
|
44
|
-
|
45
|
-
lock_strategy = Strategy.lookup(lock_strategy) if lock_strategy.is_a?(Symbol)
|
46
|
-
on_conflict.each do |origin, strategy|
|
47
|
-
strategy = OnConflict.lookup(strategy) if strategy.is_a?(Symbol)
|
48
|
-
|
49
|
-
if lock_strategy.locks_on.include?(origin) && !strategy.valid_for.include?(origin)
|
50
|
-
raise ArgumentError, "(#{origin.to_s.titleize}) conflict strategy #{strategy.name.underscore} is not valid for lock strategy #{lock_strategy.name.underscore}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def matching_strategy(const)
|
58
|
-
const_get(const, false) if const_defined?(const, false)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,107 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module Strategy
|
3
|
-
class Base
|
4
|
-
attr_reader :lock_context
|
5
|
-
|
6
|
-
def initialize(lock_context)
|
7
|
-
@lock_context = lock_context
|
8
|
-
@conflict_strategies = {}
|
9
|
-
end
|
10
|
-
|
11
|
-
class_attribute :_locks_on, instance_writer: false
|
12
|
-
|
13
|
-
def self.locks_on(*origins)
|
14
|
-
if origins.present?
|
15
|
-
orgins = Array(origins).map(&:to_sym)
|
16
|
-
self._locks_on = origins
|
17
|
-
else
|
18
|
-
self._locks_on || [:enqueue, :perform]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def on_enqueue; end
|
23
|
-
def on_perform; end
|
24
|
-
|
25
|
-
def batch_callback(event, batch_status)
|
26
|
-
if event == :success
|
27
|
-
unlock
|
28
|
-
else
|
29
|
-
unlock_cond = lock_context.config[:unlock_on_failure]
|
30
|
-
|
31
|
-
if (event == :complete && unlock_cond == :any) || (event == :death && unlock_cond == :death) || (event == :stagnated && unlock_cond == :stagnant)
|
32
|
-
unlock
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
delegate :locked?, to: :locksmith
|
38
|
-
|
39
|
-
protected
|
40
|
-
|
41
|
-
def key
|
42
|
-
lock_context.base_key
|
43
|
-
end
|
44
|
-
|
45
|
-
def wrap_in_batch(&blk)
|
46
|
-
if Thread.current[:unique_jobs_previous_context] # Ensure we don't re-wrap in a batch when rescheduling
|
47
|
-
return blk.call
|
48
|
-
end
|
49
|
-
|
50
|
-
batch = CanvasSync::JobBatches::Batch.new
|
51
|
-
batch.context = {
|
52
|
-
uniqueness_lock_key: key,
|
53
|
-
}
|
54
|
-
|
55
|
-
CanvasSync::JobBatches::Batch::Callback::VALID_CALLBACKS.each do |callback|
|
56
|
-
callback = callback.to_sym
|
57
|
-
batch.on(callback, self.class.to_s + ".internal_batch_callback", {
|
58
|
-
event: callback,
|
59
|
-
lock_strategy: self.class.to_s,
|
60
|
-
lock_key: key,
|
61
|
-
lock_context: lock_context.serialize,
|
62
|
-
})
|
63
|
-
end
|
64
|
-
|
65
|
-
CanvasSync::JobUniqueness.logger.debug("Wrapped job (#{lock_context.lock_id}) in Locking Batch #{batch.bid} for #{key}")
|
66
|
-
|
67
|
-
batch.jobs do
|
68
|
-
return blk.call
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def self.internal_batch_callback(batch_status, opts)
|
73
|
-
CanvasSync::JobUniqueness.logger.debug("Received Batch(#{batch_status.bid}) callback for #{opts[:lock_strategy]} #{opts[:lock_key]} - #{opts[:event]}")
|
74
|
-
CanvasSync::JobUniqueness.logger.debug("Context data: #{opts[:lock_context]}")
|
75
|
-
strategy_class = opts[:lock_strategy].constantize
|
76
|
-
lock_context = LockContext.from_serialized(opts[:lock_context])
|
77
|
-
CanvasSync::JobUniqueness.logger.debug("Rehydrated LockContext: #{lock_context.lock_id} #{lock_context.debug_data}")
|
78
|
-
strategy = strategy_class.new(lock_context)
|
79
|
-
# TODO Should this route through LockContext#handle_lifecycle!?
|
80
|
-
strategy.batch_callback(opts[:event].to_sym, batch_status)
|
81
|
-
end
|
82
|
-
|
83
|
-
def lock!(purpose, wait: nil)
|
84
|
-
locked = nil
|
85
|
-
if purpose == :enqueue
|
86
|
-
locked = locksmith.lock()
|
87
|
-
elsif purpose == :perform
|
88
|
-
locked = locksmith.execute { lock_context.lock_id }
|
89
|
-
end
|
90
|
-
|
91
|
-
CanvasSync::JobUniqueness.logger.debug { "Requested lock of #{key} for #{purpose} - (#{locked || 'Not Obtained!'})" }
|
92
|
-
|
93
|
-
raise CouldNotLockError.new(lock_context, source: purpose) if !locked
|
94
|
-
end
|
95
|
-
|
96
|
-
def unlock()
|
97
|
-
CanvasSync::JobUniqueness.logger.debug { "Trying to unlock #{key} for LID #{lock_context.lock_id}" }
|
98
|
-
result = locksmith.unlock
|
99
|
-
CanvasSync::JobUniqueness.logger.debug { "Unlocked #{key} - (#{result || 'Not Unlocked!'})" }
|
100
|
-
end
|
101
|
-
|
102
|
-
def locksmith
|
103
|
-
@locksmith ||= Locksmith.new(key, lock_context)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module Strategy
|
3
|
-
# Implements two locks - one while enqueued and one while performing
|
4
|
-
class UntilAndWhileExecuting < Base
|
5
|
-
locks_on :enqueue, :perform
|
6
|
-
|
7
|
-
def on_enqueue
|
8
|
-
# Obtain lock
|
9
|
-
lock!(:enqueue)
|
10
|
-
|
11
|
-
# Proceed with enqueuing the job, wrapping it in a batch
|
12
|
-
runtime_lock.on_enqueue do
|
13
|
-
yield
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def on_perform
|
18
|
-
# Obtain Runtime lock
|
19
|
-
runtime_lock.on_perform do
|
20
|
-
# Release Queue lock
|
21
|
-
unlock()
|
22
|
-
|
23
|
-
# Run the job
|
24
|
-
yield
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def runtime_lock
|
31
|
-
@runtime_lock ||= Strategy::WhileExecuting.new(lock_context)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module Strategy
|
3
|
-
class UntilExecuted < Base
|
4
|
-
locks_on :enqueue, :perform
|
5
|
-
|
6
|
-
def on_enqueue
|
7
|
-
lock!(:enqueue)
|
8
|
-
|
9
|
-
wrap_in_batch do
|
10
|
-
yield
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def on_perform
|
15
|
-
lock!(:perform)
|
16
|
-
yield
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module Strategy
|
3
|
-
class UntilExecuting < Base
|
4
|
-
locks_on :enqueue
|
5
|
-
|
6
|
-
def on_enqueue
|
7
|
-
lock!(:enqueue)
|
8
|
-
yield
|
9
|
-
end
|
10
|
-
|
11
|
-
def on_perform
|
12
|
-
unlock
|
13
|
-
yield
|
14
|
-
end
|
15
|
-
|
16
|
-
# TODO Define behavior when an error occurs during perform().
|
17
|
-
# SUJ's behavior is to relock, but this has some edge-cases (like how do we handle if another job already took the lock?)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
module CanvasSync::JobUniqueness
|
2
|
-
module Strategy
|
3
|
-
class WhileExecuting < Base
|
4
|
-
locks_on :perform
|
5
|
-
|
6
|
-
RUN_SUFFIX = ":RUN"
|
7
|
-
|
8
|
-
def on_enqueue
|
9
|
-
wrap_in_batch do
|
10
|
-
yield
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def on_perform
|
15
|
-
lock!(:perform)
|
16
|
-
yield
|
17
|
-
end
|
18
|
-
|
19
|
-
protected
|
20
|
-
|
21
|
-
def key
|
22
|
-
super + RUN_SUFFIX
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|