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,175 +0,0 @@
|
|
1
|
-
require_relative './base_job'
|
2
|
-
|
3
|
-
module CanvasSync::JobBatches
|
4
|
-
class ManagedBatchJob < BaseJob
|
5
|
-
def self.make_batch(sub_jobs, ordered: true, concurrency: nil, context: nil, preflight_check: nil, desc_prefix: nil, &blk)
|
6
|
-
desc_prefix ||= ''
|
7
|
-
|
8
|
-
if concurrency == 0 || concurrency == nil || concurrency == true
|
9
|
-
concurrency = sub_jobs.count
|
10
|
-
elsif concurrency == false
|
11
|
-
concurrency = 1
|
12
|
-
end
|
13
|
-
|
14
|
-
root_batch = Batch.new
|
15
|
-
man_batch_id = nil
|
16
|
-
|
17
|
-
if concurrency < sub_jobs.count
|
18
|
-
man_batch_id = SecureRandom.urlsafe_base64(10)
|
19
|
-
|
20
|
-
Batch.redis do |r|
|
21
|
-
r.multi do |r|
|
22
|
-
r.hset("MNGBID-#{man_batch_id}", "root_bid", root_batch.bid)
|
23
|
-
r.hset("MNGBID-#{man_batch_id}", "ordered", ordered ? 1 : 0)
|
24
|
-
r.hset("MNGBID-#{man_batch_id}", "concurrency", concurrency)
|
25
|
-
r.hset("MNGBID-#{man_batch_id}", "preflight_check", preflight_check) if preflight_check.present?
|
26
|
-
r.expire("MNGBID-#{man_batch_id}", Batch::BID_EXPIRE_TTL)
|
27
|
-
|
28
|
-
mapped_sub_jobs = sub_jobs.each_with_index.map do |j, i|
|
29
|
-
j['_mngbid_index_'] = i # This allows duplicate jobs when a Redis Set is used
|
30
|
-
j = ::ActiveJob::Arguments.serialize([j])
|
31
|
-
JSON.unparse(j)
|
32
|
-
end
|
33
|
-
if ordered
|
34
|
-
r.rpush("MNGBID-#{man_batch_id}-jobs", mapped_sub_jobs)
|
35
|
-
else
|
36
|
-
r.sadd("MNGBID-#{man_batch_id}-jobs", mapped_sub_jobs)
|
37
|
-
end
|
38
|
-
r.expire("MNGBID-#{man_batch_id}-jobs", Batch::BID_EXPIRE_TTL)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
root_batch.allow_context_changes = (concurrency == 1)
|
43
|
-
root_batch.on(:success, "#{to_s}.cleanup_redis", managed_batch_id: man_batch_id)
|
44
|
-
|
45
|
-
desc_prefix = "MGD(#{man_batch_id}): #{desc_prefix}"
|
46
|
-
end
|
47
|
-
|
48
|
-
root_batch.context = context
|
49
|
-
|
50
|
-
blk.call(ManagedBatchProxy.new(root_batch)) if blk.present?
|
51
|
-
|
52
|
-
root_batch.description = "#{desc_prefix}#{root_batch.description || 'Root'}"
|
53
|
-
|
54
|
-
root_batch.context["managed_batch_bid"] = man_batch_id if man_batch_id
|
55
|
-
|
56
|
-
if concurrency < sub_jobs.count
|
57
|
-
root_batch.placeholder!
|
58
|
-
concurrency.times do
|
59
|
-
perform_next_sequence_job(man_batch_id, skip_preflight: true)
|
60
|
-
end
|
61
|
-
else
|
62
|
-
root_batch.jobs do
|
63
|
-
sub_jobs.each do |j|
|
64
|
-
ChainBuilder.enqueue_job(j)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
root_batch
|
70
|
-
end
|
71
|
-
|
72
|
-
def perform(sub_jobs, **kwargs)
|
73
|
-
self.class.make_batch(sub_jobs, **kwargs)
|
74
|
-
end
|
75
|
-
|
76
|
-
def self.cleanup_redis(status, options)
|
77
|
-
man_batch_id = options['managed_batch_id']
|
78
|
-
Batch.redis do |r|
|
79
|
-
r.del(
|
80
|
-
"MNGBID-#{man_batch_id}",
|
81
|
-
"MNGBID-#{man_batch_id}-jobs",
|
82
|
-
)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.job_succeeded_callback(status, options)
|
87
|
-
man_batch_id = options['managed_batch_id']
|
88
|
-
perform_next_sequence_job(man_batch_id)
|
89
|
-
end
|
90
|
-
|
91
|
-
protected
|
92
|
-
|
93
|
-
def self.perform_next_sequence_job(man_batch_id, skip_preflight: false)
|
94
|
-
root_bid, ordered, preflight_check = Batch.redis do |r|
|
95
|
-
r.multi do |r|
|
96
|
-
r.hget("MNGBID-#{man_batch_id}", "root_bid")
|
97
|
-
r.hget("MNGBID-#{man_batch_id}", "ordered")
|
98
|
-
r.hget("MNGBID-#{man_batch_id}", "preflight_check")
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
if !skip_preflight && preflight_check.present?
|
103
|
-
if preflight_check.include?(".")
|
104
|
-
clazz, method_name = preflight_check.split('.')
|
105
|
-
clazz = clazz.constantize
|
106
|
-
else
|
107
|
-
clazz = Object
|
108
|
-
method_name = preflight_check
|
109
|
-
end
|
110
|
-
preflight_check = ->(*args) { clazz.send(method_name, *args) }
|
111
|
-
else
|
112
|
-
preflight_check = ->(*args) { true }
|
113
|
-
end
|
114
|
-
|
115
|
-
ordered = CanvasSync::MiscHelper.to_boolean(ordered)
|
116
|
-
|
117
|
-
loop do
|
118
|
-
next_job_json = Batch.redis do |r|
|
119
|
-
if ordered
|
120
|
-
r.lpop("MNGBID-#{man_batch_id}-jobs")
|
121
|
-
else
|
122
|
-
r.spop("MNGBID-#{man_batch_id}-jobs")
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
break unless next_job_json.present?
|
127
|
-
|
128
|
-
next_job = JSON.parse(next_job_json)
|
129
|
-
next_job = ::ActiveJob::Arguments.deserialize(next_job)[0]
|
130
|
-
|
131
|
-
preflight_result = preflight_check.call(next_job)
|
132
|
-
if preflight_result == :abort
|
133
|
-
cleanup_redis(nil, { "managed_batch_id" => man_batch_id })
|
134
|
-
break
|
135
|
-
elsif !preflight_check
|
136
|
-
next
|
137
|
-
end
|
138
|
-
|
139
|
-
Batch.new(root_bid).jobs do
|
140
|
-
Batch.new.tap do |batch|
|
141
|
-
batch.description = "Managed Batch Fiber (#{man_batch_id})"
|
142
|
-
batch.on(:success, "#{self.to_s}.job_succeeded_callback", managed_batch_id: man_batch_id)
|
143
|
-
|
144
|
-
if next_job[:chain_link].present?
|
145
|
-
# Annotate Batch with chain-step info
|
146
|
-
batch.context["csb:chain_link"] = next_job[:chain_link]
|
147
|
-
batch.on(:complete, "#{ChainBuilder.to_s}.chain_step_complete", chain_link: next_job[:chain_link])
|
148
|
-
end
|
149
|
-
|
150
|
-
batch.jobs do
|
151
|
-
ChainBuilder.enqueue_job(next_job)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
break
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
class ManagedBatchProxy
|
161
|
-
def initialize(real_batch)
|
162
|
-
@real_batch = real_batch
|
163
|
-
end
|
164
|
-
|
165
|
-
delegate_missing_to :real_batch
|
166
|
-
|
167
|
-
def jobs
|
168
|
-
raise "Managed Batches do not support calling .jobs directly!"
|
169
|
-
end
|
170
|
-
|
171
|
-
private
|
172
|
-
attr_reader :real_batch
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require_relative './base_job'
|
2
|
-
|
3
|
-
module CanvasSync::JobBatches
|
4
|
-
class SerialBatchJob < BaseJob
|
5
|
-
def self.make_batch(sub_jobs, **kwargs, &blk)
|
6
|
-
ManagedBatchJob.make_batch(
|
7
|
-
sub_jobs,
|
8
|
-
**kwargs,
|
9
|
-
ordered: true,
|
10
|
-
concurrency: false,
|
11
|
-
desc_prefix: 'SerialBatchJob: ',
|
12
|
-
&blk
|
13
|
-
)
|
14
|
-
end
|
15
|
-
|
16
|
-
def perform(sub_jobs, **kwargs)
|
17
|
-
self.class.make_batch(sub_jobs, **kwargs)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,254 +0,0 @@
|
|
1
|
-
module CanvasSync::JobBatches
|
2
|
-
class Pool
|
3
|
-
include RedisModel
|
4
|
-
|
5
|
-
POOL_REFILL = RedisScript.new(Pathname.new(__FILE__) + "../pool_refill.lua")
|
6
|
-
|
7
|
-
attr_reader :pid
|
8
|
-
redis_attr :description
|
9
|
-
redis_attr :created_at
|
10
|
-
redis_attr :concurrency, :int
|
11
|
-
redis_attr :complete_count, :int
|
12
|
-
redis_attr :order
|
13
|
-
redis_attr :on_failed_job, :symbol
|
14
|
-
redis_attr :clean_when_empty, :bool
|
15
|
-
|
16
|
-
def initialize(pooolid = nil, **kwargs)
|
17
|
-
if pooolid
|
18
|
-
@existing = true
|
19
|
-
@pid = pooolid
|
20
|
-
else
|
21
|
-
@pid = SecureRandom.urlsafe_base64(10)
|
22
|
-
initialize_new(**kwargs)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.from_pid(pid)
|
27
|
-
raise "PID must be given" unless pid.present?
|
28
|
-
new(pid)
|
29
|
-
end
|
30
|
-
|
31
|
-
def <<(job_desc)
|
32
|
-
add_job(job_desc)
|
33
|
-
end
|
34
|
-
|
35
|
-
def add_job(job_desc)
|
36
|
-
add_jobs([job_desc])
|
37
|
-
end
|
38
|
-
|
39
|
-
def add_jobs(job_descs, skip_refill: false)
|
40
|
-
job_descs.each do |job_desc|
|
41
|
-
wrapper = Batch.new
|
42
|
-
wrapper.description = "Pool Job Wrapper (PID: #{pid})"
|
43
|
-
checkin_event = (on_failed_job == :wait) ? :success : :complete
|
44
|
-
wrapper.on(checkin_event, "#{self.class.to_s}.job_checked_in", pool_id: pid)
|
45
|
-
wrapper.placeholder!
|
46
|
-
|
47
|
-
job_desc = job_desc.symbolize_keys
|
48
|
-
job_desc = job_desc.merge!(
|
49
|
-
job: job_desc[:job].to_s,
|
50
|
-
pool_wrapper_batch: wrapper.bid,
|
51
|
-
)
|
52
|
-
|
53
|
-
push_job_to_pool(job_desc)
|
54
|
-
end
|
55
|
-
refill_allotment unless skip_refill
|
56
|
-
end
|
57
|
-
|
58
|
-
def keep_open!(token = SecureRandom.urlsafe_base64(10))
|
59
|
-
if block_given?
|
60
|
-
begin
|
61
|
-
token = keep_open!(token)
|
62
|
-
yield
|
63
|
-
ensure
|
64
|
-
let_close!(token)
|
65
|
-
end
|
66
|
-
else
|
67
|
-
redis.multi do |r|
|
68
|
-
r.sadd("#{redis_key}-holds", token)
|
69
|
-
r.expire("#{redis_key}-holds", Batch::BID_EXPIRE_TTL)
|
70
|
-
end
|
71
|
-
token
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
def let_close!(token = :unset)
|
76
|
-
if token == :unset # Legacy
|
77
|
-
redis.del("#{redis_key}-holds")
|
78
|
-
redis.hset(redis_key, 'keep_open', 'false')
|
79
|
-
else
|
80
|
-
redis.srem("#{redis_key}-holds", token)
|
81
|
-
end
|
82
|
-
|
83
|
-
cleanup_if_empty
|
84
|
-
end
|
85
|
-
|
86
|
-
def cleanup_redis
|
87
|
-
Batch.logger.debug {"Cleaning redis of pool #{pid}"}
|
88
|
-
redis do |r|
|
89
|
-
r.zrem("pools", pid)
|
90
|
-
r.unlink(
|
91
|
-
"#{redis_key}",
|
92
|
-
"#{redis_key}-jobs",
|
93
|
-
)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def cleanup_if_empty
|
98
|
-
self.order
|
99
|
-
|
100
|
-
activec, pactivec, pendingc, clean_when_empty, keep_open, holds = redis.multi do |r|
|
101
|
-
r.hlen("#{redis_key}-active")
|
102
|
-
r.hget(redis_key, "_active_count")
|
103
|
-
pending_count(r)
|
104
|
-
r.hget(redis_key, 'clean_when_empty')
|
105
|
-
r.hget(redis_key, 'keep_open')
|
106
|
-
r.scard("#{redis_key}-holds")
|
107
|
-
end
|
108
|
-
|
109
|
-
return if keep_open == 'true' || clean_when_empty == 'false' || (holds && holds > 0)
|
110
|
-
|
111
|
-
if activec <= 0 && (pactivec.try(:to_i) || 0) <= 0 && pendingc <= 0
|
112
|
-
cleanup_redis
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def active_count(r = redis)
|
117
|
-
r.hlen("#{redis_key}-active") + r.hincrby(redis_key, "_active_count", 0)
|
118
|
-
end
|
119
|
-
|
120
|
-
def active_jobs(r = redis)
|
121
|
-
r.hvals("#{redis_key}-active").map {|desc| JSON.parse(desc)[0] }
|
122
|
-
end
|
123
|
-
|
124
|
-
def pending_count(r = redis)
|
125
|
-
jobs_key = "#{redis_key}-jobs"
|
126
|
-
order = self.order || 'fifo'
|
127
|
-
case order.to_sym
|
128
|
-
when :fifo, :lifo
|
129
|
-
r.llen(jobs_key)
|
130
|
-
when :random
|
131
|
-
r.scard(jobs_key)
|
132
|
-
when :priority
|
133
|
-
r.zcard(jobs_key)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
def job_checked_in(status, options)
|
138
|
-
active_count = refill_allotment(status.bid)
|
139
|
-
cleanup_if_empty unless active_count > 0
|
140
|
-
end
|
141
|
-
|
142
|
-
def self.job_checked_in(status, options)
|
143
|
-
pid = options['pool_id']
|
144
|
-
from_pid(pid).job_checked_in(status, options)
|
145
|
-
end
|
146
|
-
|
147
|
-
# Administrative/console method to cleanup expired pools from the WebUI
|
148
|
-
def self.cleanup_redis_index!
|
149
|
-
suffixes = ["", "-active", "-jobs"]
|
150
|
-
r.zrangebyscore("pools", "0", Batch::BID_EXPIRE_TTL.seconds.ago.to_i).each do |pid|
|
151
|
-
r.zrem("pools", pid) if Batch.cleanup_redis_index_for("POOLID-#{pid}", suffixes)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
protected
|
156
|
-
|
157
|
-
def redis_key
|
158
|
-
"POOLID-#{pid}"
|
159
|
-
end
|
160
|
-
|
161
|
-
def refill_allotment(checkin_bid = nil)
|
162
|
-
active_count, job_descs = POOL_REFILL.call(redis, [redis_key, "#{redis_key}-jobs", "#{redis_key}-active"], [checkin_bid || ""])
|
163
|
-
return active_count if active_count < 0
|
164
|
-
|
165
|
-
pending_job_descs = job_descs.dup
|
166
|
-
|
167
|
-
added_jobs = {}
|
168
|
-
failed_to_add_jobs = []
|
169
|
-
add_exception = nil
|
170
|
-
|
171
|
-
while pending_job_descs.count > 0
|
172
|
-
begin
|
173
|
-
job_json = pending_job_descs.shift
|
174
|
-
job_desc = ::ActiveJob::Arguments.deserialize(JSON.parse(job_json))[0]&.symbolize_keys
|
175
|
-
|
176
|
-
wbid = job_desc[:pool_wrapper_batch]
|
177
|
-
|
178
|
-
Batch.new(wbid).jobs do
|
179
|
-
ChainBuilder.enqueue_job(job_desc)
|
180
|
-
end
|
181
|
-
|
182
|
-
added_jobs[wbid] = job_json
|
183
|
-
rescue => ex
|
184
|
-
failed_to_add_jobs << job_json
|
185
|
-
add_exception = ex
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
redis.multi do |r|
|
190
|
-
r.mapped_hmset("#{redis_key}-active", added_jobs) if added_jobs.count > 0
|
191
|
-
# Release reserved slots now that we've added the jobs to `-active`
|
192
|
-
r.hincrby(redis_key, "_active_count", -job_descs.count)
|
193
|
-
|
194
|
-
r.expire(redis_key, Batch::BID_EXPIRE_TTL)
|
195
|
-
r.expire("#{redis_key}-active", Batch::BID_EXPIRE_TTL)
|
196
|
-
r.expire("#{redis_key}-jobs", Batch::BID_EXPIRE_TTL)
|
197
|
-
end
|
198
|
-
|
199
|
-
# If this happens, we end up in a bad state (as we don't try to re-add items to the pool or refill_allotment again), but
|
200
|
-
# this should be a _really_ rare case that should only occur if we've lost connection to Redis or something, so we're
|
201
|
-
# operating on the assumption that if we get here, any recovery logic will fail too
|
202
|
-
if add_exception.present?
|
203
|
-
Batch.logger.error {"Error popping jobs from Pool #{pid}: #{add_exception}"}
|
204
|
-
raise add_exception
|
205
|
-
end
|
206
|
-
|
207
|
-
active_count + added_jobs.count
|
208
|
-
end
|
209
|
-
|
210
|
-
def push_job_to_pool(job_desc)
|
211
|
-
jobs_key = "#{redis_key}-jobs"
|
212
|
-
# This allows duplicate jobs when a Redis Set is used
|
213
|
-
job_desc[:_pool_random_key_] = SecureRandom.urlsafe_base64(10)
|
214
|
-
job_json = JSON.unparse(::ActiveJob::Arguments.serialize([job_desc]))
|
215
|
-
order = self.order
|
216
|
-
|
217
|
-
redis.multi do |r|
|
218
|
-
case order.to_sym
|
219
|
-
when :fifo, :lifo
|
220
|
-
r.rpush(jobs_key, job_json)
|
221
|
-
when :random
|
222
|
-
r.sadd(jobs_key, job_json)
|
223
|
-
when :priority
|
224
|
-
r.zadd(jobs_key, job_desc[:priority] || 0, job_json)
|
225
|
-
end
|
226
|
-
r.expire(redis_key, Batch::BID_EXPIRE_TTL)
|
227
|
-
r.expire(jobs_key, Batch::BID_EXPIRE_TTL)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def self.redis(&blk)
|
232
|
-
Batch.redis &blk
|
233
|
-
end
|
234
|
-
delegate :redis, to: :class
|
235
|
-
|
236
|
-
def flush_pending_attrs
|
237
|
-
super
|
238
|
-
redis.expire(redis_key, Batch::BID_EXPIRE_TTL)
|
239
|
-
redis.zadd("pools", created_at, pid)
|
240
|
-
end
|
241
|
-
|
242
|
-
private
|
243
|
-
|
244
|
-
def initialize_new(concurrency: nil, order: :fifo, clean_when_empty: true, on_failed_job: :wait, description: nil)
|
245
|
-
self.created_at = Time.now.utc.to_f
|
246
|
-
self.description = description
|
247
|
-
self.order = order
|
248
|
-
self.concurrency = concurrency
|
249
|
-
self.clean_when_empty = clean_when_empty
|
250
|
-
self.on_failed_job = on_failed_job
|
251
|
-
flush_pending_attrs
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
|
2
|
-
local poolkey = KEYS[1]
|
3
|
-
local qkey = KEYS[2]
|
4
|
-
local activekey = KEYS[3]
|
5
|
-
|
6
|
-
local checkin_item = ARGV[1]
|
7
|
-
|
8
|
-
if redis.call('EXISTS', poolkey) == 0 then
|
9
|
-
return { -1, {} } -- pool doesn't exist
|
10
|
-
end
|
11
|
-
|
12
|
-
if checkin_item ~= "" then
|
13
|
-
redis.call("HDEL", activekey, checkin_item)
|
14
|
-
redis.call("HINCRBY", poolkey, "complete_count", 1)
|
15
|
-
end
|
16
|
-
|
17
|
-
local pool_type = redis.call('HGET', poolkey, "order")
|
18
|
-
local allotment = tonumber(redis.call("HGET", poolkey, "concurrency"))
|
19
|
-
local active = redis.call("HLEN", activekey) + (redis.call("HGET", poolkey, "_active_count") or 0)
|
20
|
-
|
21
|
-
local pop_count = allotment - active
|
22
|
-
|
23
|
-
local popped_items = {}
|
24
|
-
|
25
|
-
if pop_count > 0 then
|
26
|
-
if pool_type == "fifo" then
|
27
|
-
popped_items = redis.call("LPOP", qkey, pop_count) or {}
|
28
|
-
elseif pool_type == "lifo" then
|
29
|
-
popped_items = redis.call("RPOP", qkey, pop_count) or {}
|
30
|
-
elseif pool_type == "random" then
|
31
|
-
popped_items = redis.call("SPOP", qkey, pop_count) or {}
|
32
|
-
elseif pool_type == "priority" then
|
33
|
-
local temp_items = redis.call("ZPOPMAX", qkey, pop_count) or {}
|
34
|
-
for i,v in ipairs(temp_items) do
|
35
|
-
if i % 2 == 1 then
|
36
|
-
table.insert(popped_items, v)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
-- Reserve slots for these jobs while we return to Ruby and deserialize them
|
43
|
-
-- This could also be inlined by just storing a key in the queue and storing parameters
|
44
|
-
-- in a Hash, but this seems more efficient.
|
45
|
-
redis.call('HINCRBY', poolkey, "_active_count", #popped_items)
|
46
|
-
|
47
|
-
return { active, popped_items }
|
@@ -1,67 +0,0 @@
|
|
1
|
-
module CanvasSync::JobBatches
|
2
|
-
module RedisModel
|
3
|
-
extend ActiveSupport::Concern
|
4
|
-
|
5
|
-
class_methods do
|
6
|
-
def redis_attr(key, type = :string, read_only: true)
|
7
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
8
|
-
def #{key}=(value)
|
9
|
-
raise "#{key} is read-only once the batch has been started" if #{read_only.to_s} && (@initialized || @existing)
|
10
|
-
@#{key} = value
|
11
|
-
if :#{type} == :json
|
12
|
-
value = JSON.unparse(value)
|
13
|
-
end
|
14
|
-
persist_bid_attr('#{key}', value)
|
15
|
-
end
|
16
|
-
|
17
|
-
def #{key}
|
18
|
-
return @#{key} if defined?(@#{key})
|
19
|
-
if (@initialized || @existing)
|
20
|
-
value = read_bid_attr('#{key}')
|
21
|
-
if :#{type} == :bool
|
22
|
-
value = value == 'true'
|
23
|
-
elsif :#{type} == :int
|
24
|
-
value = value.to_i
|
25
|
-
elsif :#{type} == :float
|
26
|
-
value = value.to_f
|
27
|
-
elsif :#{type} == :json
|
28
|
-
value = JSON.parse(value)
|
29
|
-
elsif :#{type} == :symbol
|
30
|
-
value = value&.to_sym
|
31
|
-
end
|
32
|
-
@#{key} = value
|
33
|
-
end
|
34
|
-
end
|
35
|
-
RUBY
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def persist_bid_attr(attribute, value)
|
40
|
-
if @initialized || @existing
|
41
|
-
redis do |r|
|
42
|
-
r.multi do |r|
|
43
|
-
r.hset(redis_key, attribute, value.to_s)
|
44
|
-
r.expire(redis_key, Batch::BID_EXPIRE_TTL)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
else
|
48
|
-
@pending_attrs ||= {}
|
49
|
-
@pending_attrs[attribute] = value.to_s
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def read_bid_attr(attribute)
|
54
|
-
redis do |r|
|
55
|
-
r.hget(redis_key, attribute)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def flush_pending_attrs
|
60
|
-
redis do |r|
|
61
|
-
r.mapped_hmset(redis_key, @pending_attrs)
|
62
|
-
end
|
63
|
-
@initialized = true
|
64
|
-
@pending_attrs = {}
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|