canvas_sync 0.23.5 → 0.24.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/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,595 +0,0 @@
|
|
1
|
-
|
2
|
-
require_relative './redis_model'
|
3
|
-
require_relative './redis_script'
|
4
|
-
require_relative "./callback"
|
5
|
-
require_relative "./context_hash"
|
6
|
-
require_relative "./status"
|
7
|
-
require_relative "./pool"
|
8
|
-
Dir[File.dirname(__FILE__) + "/jobs/*.rb"].each { |file| require file }
|
9
|
-
require_relative "./chain_builder"
|
10
|
-
|
11
|
-
# Implement Job Batching similar to Sidekiq::Batch. Supports ActiveJob and Sidekiq, or a mix thereof.
|
12
|
-
# Much of this code is modifed/extended from https://github.com/breamware/sidekiq-batch
|
13
|
-
|
14
|
-
module CanvasSync::JobBatches
|
15
|
-
CURRENT_BATCH_THREAD_KEY = :job_batches_batch
|
16
|
-
|
17
|
-
class Batch
|
18
|
-
include RedisModel
|
19
|
-
|
20
|
-
class NoBlockGivenError < StandardError; end
|
21
|
-
|
22
|
-
delegate :redis, to: :class
|
23
|
-
|
24
|
-
BID_EXPIRE_TTL = 90.days.to_i
|
25
|
-
INDEX_ALL_BATCHES = false
|
26
|
-
SCHEDULE_CALLBACK = RedisScript.new(Pathname.new(__FILE__) + "../schedule_callback.lua")
|
27
|
-
BID_HIERARCHY = RedisScript.new(Pathname.new(__FILE__) + "../hier_batch_ids.lua")
|
28
|
-
|
29
|
-
attr_reader :bid
|
30
|
-
|
31
|
-
def self.current
|
32
|
-
Thread.current[CURRENT_BATCH_THREAD_KEY]
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.current_context
|
36
|
-
self.current&.context
|
37
|
-
end
|
38
|
-
|
39
|
-
def initialize(existing_bid = nil)
|
40
|
-
@bid = existing_bid || SecureRandom.urlsafe_base64(10)
|
41
|
-
@existing = !(!existing_bid || existing_bid.empty?) # Basically existing_bid.present?
|
42
|
-
@initialized = false
|
43
|
-
@bidkey = "BID-" + @bid.to_s
|
44
|
-
self.created_at = Time.now.utc.to_f unless @existing
|
45
|
-
end
|
46
|
-
|
47
|
-
redis_attr :description
|
48
|
-
redis_attr :created_at
|
49
|
-
redis_attr :callback_queue, read_only: false
|
50
|
-
redis_attr :callback_params, :json
|
51
|
-
redis_attr :allow_context_changes, :bool
|
52
|
-
|
53
|
-
def context
|
54
|
-
return @context if defined?(@context)
|
55
|
-
|
56
|
-
if (@initialized || @existing)
|
57
|
-
@context = ContextHash.new(bid)
|
58
|
-
else
|
59
|
-
@context = ContextHash.new(bid, {})
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def context=(value)
|
64
|
-
raise "context is read-only once the batch has been started" if (@initialized || @existing) # && !allow_context_changes
|
65
|
-
raise "context must be a Hash" unless value.is_a?(Hash) || value.nil?
|
66
|
-
return nil if value.nil? && @context.nil?
|
67
|
-
|
68
|
-
value = {} if value.nil?
|
69
|
-
value = value.local if value.is_a?(ContextHash)
|
70
|
-
|
71
|
-
@context ||= ContextHash.new(bid, {})
|
72
|
-
@context.set_local(value)
|
73
|
-
# persist_bid_attr('context', JSON.unparse(@context.local))
|
74
|
-
end
|
75
|
-
|
76
|
-
def save_context_changes
|
77
|
-
@context&.save!
|
78
|
-
end
|
79
|
-
|
80
|
-
# Events:
|
81
|
-
# :complete - triggered once all jobs have been executed, regardless of success or failure.
|
82
|
-
# :success - triggered once all jobs haves successfully executed.
|
83
|
-
# :death - triggered after any job enters the dead state.
|
84
|
-
# :stagnated - triggered when a job dies and no other jobs/batches are active.
|
85
|
-
def on(event, callback, options = {})
|
86
|
-
return unless Callback::VALID_CALLBACKS.include?(event.to_sym)
|
87
|
-
|
88
|
-
callback_key = "#{@bidkey}-callbacks-#{event}"
|
89
|
-
redis.multi do |r|
|
90
|
-
r.sadd(callback_key, JSON.unparse({
|
91
|
-
callback: callback,
|
92
|
-
opts: options
|
93
|
-
}))
|
94
|
-
r.expire(callback_key, BID_EXPIRE_TTL)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def jobs
|
99
|
-
raise NoBlockGivenError unless block_given?
|
100
|
-
|
101
|
-
persist!
|
102
|
-
|
103
|
-
# TODO This keep_open! block is probably desired, but it has some caveats:
|
104
|
-
# - Old logic didn't auto-clean empty batches
|
105
|
-
# - Could be an issue if the Thread crashes at a bad moment (do we even need to plan for this?)
|
106
|
-
# - Technically there could be a race condition without it, but such hasn't been observed
|
107
|
-
|
108
|
-
# keep_open! do
|
109
|
-
begin
|
110
|
-
parent = Thread.current[CURRENT_BATCH_THREAD_KEY]
|
111
|
-
Thread.current[CURRENT_BATCH_THREAD_KEY] = self
|
112
|
-
yield
|
113
|
-
ensure
|
114
|
-
Thread.current[CURRENT_BATCH_THREAD_KEY] = parent
|
115
|
-
end
|
116
|
-
# end
|
117
|
-
|
118
|
-
nil
|
119
|
-
end
|
120
|
-
|
121
|
-
# Mark this Batch as a placeholder. It will be persisted to Redis, but no jobs will be added.
|
122
|
-
# From here, you can either use `.jobs` on the batch to add jobs as usual, or call `.let_close!` to allow cleanup to occur.
|
123
|
-
def placeholder!
|
124
|
-
# TODO Provide a stable `let_close!` token?
|
125
|
-
persist!
|
126
|
-
end
|
127
|
-
|
128
|
-
def invalidate_all
|
129
|
-
redis.setex("invalidated-bid-#{bid}", BID_EXPIRE_TTL, 1)
|
130
|
-
end
|
131
|
-
|
132
|
-
def parent_bid
|
133
|
-
redis.hget(@bidkey, "parent_bid")
|
134
|
-
end
|
135
|
-
|
136
|
-
def parent
|
137
|
-
if parent_bid
|
138
|
-
Batch.new(parent_bid)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def valid?(batch = self)
|
143
|
-
valid = !redis.exists?("invalidated-bid-#{batch.bid}")
|
144
|
-
batch.parent ? valid && valid?(batch.parent) : valid
|
145
|
-
end
|
146
|
-
|
147
|
-
def keep_open!(token = SecureRandom.urlsafe_base64(10))
|
148
|
-
if block_given?
|
149
|
-
begin
|
150
|
-
token = keep_open!(token)
|
151
|
-
yield
|
152
|
-
ensure
|
153
|
-
let_close!(token)
|
154
|
-
end
|
155
|
-
else
|
156
|
-
persist!
|
157
|
-
|
158
|
-
redis.multi do |r|
|
159
|
-
r.sadd("#{@bidkey}-holds", token)
|
160
|
-
r.expire("#{@bidkey}-holds", BID_EXPIRE_TTL)
|
161
|
-
end
|
162
|
-
|
163
|
-
assert_batch_is_open
|
164
|
-
|
165
|
-
token
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
def let_close!(token = :unset)
|
170
|
-
self.class.with_callback_check(bid, only: %i[complete success]) do |r|
|
171
|
-
if token == :unset # Legacy
|
172
|
-
r.del("#{@bidkey}-holds")
|
173
|
-
r.hset(@bidkey, 'keep_open', "false")
|
174
|
-
else
|
175
|
-
r.srem("#{@bidkey}-holds", token)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def self.with_batch(batch)
|
181
|
-
batch = self.new(batch) if batch.is_a?(String)
|
182
|
-
parent = Thread.current[CURRENT_BATCH_THREAD_KEY]
|
183
|
-
Thread.current[CURRENT_BATCH_THREAD_KEY] = batch
|
184
|
-
yield
|
185
|
-
ensure
|
186
|
-
Thread.current[CURRENT_BATCH_THREAD_KEY] = parent
|
187
|
-
end
|
188
|
-
|
189
|
-
# Any Batches or Jobs created in the given block won't be assocaiated to the current batch
|
190
|
-
def self.without_batch(&blk)
|
191
|
-
with_batch(nil, &blk)
|
192
|
-
end
|
193
|
-
|
194
|
-
def append_jobs(jids)
|
195
|
-
jids = Array(jids)
|
196
|
-
jids = jids.uniq
|
197
|
-
return unless jids.size > 0
|
198
|
-
|
199
|
-
redis do |r|
|
200
|
-
tme = Time.now.utc.to_f
|
201
|
-
added = r.zadd(@bidkey + "-jids", jids.map{|jid| [tme, jid] }, nx: true)
|
202
|
-
r.multi do |r|
|
203
|
-
r.hincrby(@bidkey, "pending", added)
|
204
|
-
r.hincrby(@bidkey, "job_count", added)
|
205
|
-
r.expire(@bidkey, BID_EXPIRE_TTL)
|
206
|
-
r.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
protected
|
212
|
-
|
213
|
-
def redis_key
|
214
|
-
@bidkey
|
215
|
-
end
|
216
|
-
|
217
|
-
def flush_pending_attrs
|
218
|
-
super
|
219
|
-
redis.zadd("batches", created_at, bid) if INDEX_ALL_BATCHES
|
220
|
-
end
|
221
|
-
|
222
|
-
def assert_batch_is_open
|
223
|
-
unless defined?(@closed)
|
224
|
-
@closed = redis.hget(@bidkey, 'complete') == 'true'
|
225
|
-
end
|
226
|
-
raise "Cannot add jobs to Batch #{} bid - it has already entered the callback-stage" if @closed
|
227
|
-
end
|
228
|
-
|
229
|
-
def persist!
|
230
|
-
if !@existing && !@initialized
|
231
|
-
parent_bid = Thread.current[CURRENT_BATCH_THREAD_KEY]&.bid
|
232
|
-
|
233
|
-
redis.multi do |r|
|
234
|
-
r.hset(@bidkey, "parent_bid", parent_bid.to_s) if parent_bid
|
235
|
-
r.expire(@bidkey, BID_EXPIRE_TTL)
|
236
|
-
|
237
|
-
if parent_bid
|
238
|
-
r.hincrby("BID-#{parent_bid}", "children", 1)
|
239
|
-
r.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
|
240
|
-
r.zadd("BID-#{parent_bid}-bids", created_at, bid)
|
241
|
-
else
|
242
|
-
r.zadd("BID-ROOT-bids", created_at, bid)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
flush_pending_attrs
|
247
|
-
@context&.save!
|
248
|
-
|
249
|
-
@initialized = true
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
class << self
|
254
|
-
def current
|
255
|
-
Thread.current[CURRENT_BATCH_THREAD_KEY]
|
256
|
-
end
|
257
|
-
|
258
|
-
def current_context
|
259
|
-
current&.context || {}
|
260
|
-
end
|
261
|
-
|
262
|
-
# Perform a success/failure/complete/etc transaction against Redis, checking
|
263
|
-
# if any callbacks should be triggered
|
264
|
-
def with_callback_check(bid, except: [], only: Callback::VALID_CALLBACKS, &blk)
|
265
|
-
except = Array(except).map(&:to_sym)
|
266
|
-
only = Array(only).map(&:to_sym)
|
267
|
-
|
268
|
-
precount = 0
|
269
|
-
|
270
|
-
all_results = redis do |r|
|
271
|
-
return unless r.exists?("BID-#{bid}")
|
272
|
-
|
273
|
-
r.multi do |r|
|
274
|
-
# The block probably doesn't _have_ to be part of the same transaction, but it was easier than
|
275
|
-
# re-assessing possible race-conditions and is technically more performant
|
276
|
-
blk.call(r)
|
277
|
-
futures = r.instance_variable_get(:@futures) || r.instance_variable_get(:@pipeline)&.futures || r.instance_variable_get(:@client)&.futures
|
278
|
-
precount = futures.size
|
279
|
-
|
280
|
-
# Misc
|
281
|
-
r.hget("BID-#{bid}", "parent_bid")
|
282
|
-
r.hget("BID-#{bid}", "keep_open")
|
283
|
-
r.scard("BID-#{bid}-holds")
|
284
|
-
|
285
|
-
# Jobs
|
286
|
-
r.hincrby("BID-#{bid}", "pending", 0)
|
287
|
-
r.scard("BID-#{bid}-failed")
|
288
|
-
r.scard("BID-#{bid}-dead")
|
289
|
-
|
290
|
-
# Batches
|
291
|
-
r.hincrby("BID-#{bid}", "children", 0)
|
292
|
-
r.scard("BID-#{bid}-batches-complete")
|
293
|
-
r.scard("BID-#{bid}-batches-success")
|
294
|
-
r.scard("BID-#{bid}-batches-failed")
|
295
|
-
r.scard("BID-#{bid}-batches-stagnated")
|
296
|
-
|
297
|
-
# Touch Expirations
|
298
|
-
r.expire("BID-#{bid}", BID_EXPIRE_TTL)
|
299
|
-
r.expire("BID-#{bid}-batches-success", BID_EXPIRE_TTL)
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
# Exclude return values from the passed block
|
304
|
-
actual_results = all_results[precount..-1]
|
305
|
-
|
306
|
-
# "pending" = not successful (yet)
|
307
|
-
# "failed" = dead or retrying
|
308
|
-
# "complete" = successful or failed
|
309
|
-
|
310
|
-
parent_bid, keep_open, holds, \
|
311
|
-
pending_jobs, failed_jobs, dead_jobs, \
|
312
|
-
child_batches, complete_batches, success_batches, failed_batches, stagnated_batches \
|
313
|
-
= actual_results
|
314
|
-
|
315
|
-
pending_batches = child_batches - success_batches
|
316
|
-
|
317
|
-
if keep_open == 'true' || (holds && holds > 0)
|
318
|
-
except << :complete
|
319
|
-
except << :success
|
320
|
-
end
|
321
|
-
|
322
|
-
trigger_callback = ->(callback) {
|
323
|
-
next if except.include?(callback.to_sym)
|
324
|
-
next unless only.include?(callback.to_sym)
|
325
|
-
|
326
|
-
Batch.logger.debug {"Finalize #{callback} bid: #{parent_bid}"}
|
327
|
-
enqueue_callbacks(callback, bid)
|
328
|
-
}
|
329
|
-
|
330
|
-
# Handling all of these cases in one method may be a little less performant than in more specialized methods, but
|
331
|
-
# I was dealing with duplicate Redis queries and checks being made in up to 4 places and it was getting out of
|
332
|
-
# hand - this combined method should be easier to maintain and reason about
|
333
|
-
|
334
|
-
all_successful = pending_jobs.zero? && child_batches == success_batches
|
335
|
-
|
336
|
-
if all_successful || (pending_jobs == failed_jobs && child_batches == complete_batches) # All Complete
|
337
|
-
trigger_callback.call(:complete)
|
338
|
-
end
|
339
|
-
|
340
|
-
if all_successful # All Successful
|
341
|
-
trigger_callback.call(:success)
|
342
|
-
elsif pending_jobs == dead_jobs && pending_batches == stagnated_batches # Stagnated
|
343
|
-
trigger_callback.call(:stagnated)
|
344
|
-
end
|
345
|
-
|
346
|
-
all_results[0...precount]
|
347
|
-
end
|
348
|
-
|
349
|
-
def process_failed_job(bid, jid)
|
350
|
-
with_callback_check(bid, except: [:success]) do |r|
|
351
|
-
r.sadd("BID-#{bid}-failed", jid)
|
352
|
-
r.expire("BID-#{bid}-failed", BID_EXPIRE_TTL)
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
# Dead jobs are a Sidekiq feature.
|
357
|
-
# If this is called for a job, process_failed_job was also called
|
358
|
-
def process_dead_job(bid, jid)
|
359
|
-
enqueue_callbacks(:death, bid)
|
360
|
-
|
361
|
-
with_callback_check(bid) do |r|
|
362
|
-
r.sadd("BID-#{bid}-dead", jid)
|
363
|
-
r.expire("BID-#{bid}-dead", BID_EXPIRE_TTL)
|
364
|
-
end
|
365
|
-
end
|
366
|
-
|
367
|
-
def process_successful_job(bid, jid)
|
368
|
-
with_callback_check(bid) do |r|
|
369
|
-
r.srem("BID-#{bid}-failed", jid)
|
370
|
-
r.hincrby("BID-#{bid}", "pending", -1)
|
371
|
-
r.hincrby("BID-#{bid}", "successful-jobs", 1)
|
372
|
-
r.zrem("BID-#{bid}-jids", jid)
|
373
|
-
end
|
374
|
-
end
|
375
|
-
|
376
|
-
def enqueue_callbacks(event, bid)
|
377
|
-
batch_key = "BID-#{bid}"
|
378
|
-
callback_key = "#{batch_key}-callbacks-#{event}"
|
379
|
-
|
380
|
-
exists, callbacks, queue, parent_bid, callback_params = redis do |r|
|
381
|
-
r.multi do |r|
|
382
|
-
r.exists?(batch_key)
|
383
|
-
|
384
|
-
r.smembers(callback_key)
|
385
|
-
r.hget(batch_key, "callback_queue")
|
386
|
-
r.hget(batch_key, "parent_bid")
|
387
|
-
r.hget(batch_key, "callback_params")
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
return unless exists
|
392
|
-
|
393
|
-
queue ||= "default"
|
394
|
-
parent_bid = !parent_bid || parent_bid.empty? ? nil : parent_bid # Basically parent_bid.blank?
|
395
|
-
|
396
|
-
# Internal callback params. If this is present, we're trying to enqueue callbacks for a callback, which is a special case that
|
397
|
-
# indicates that the callback completed and we need to close the triggering batch (which is in a done-but-not-cleaned state)
|
398
|
-
callback_params = JSON.parse(callback_params) if callback_params.present?
|
399
|
-
|
400
|
-
# User-configured parameters/arguments to pass to the callback
|
401
|
-
callback_args = callbacks.reduce([]) do |memo, jcb|
|
402
|
-
cb = JSON.load(jcb)
|
403
|
-
memo << [cb['callback'], event.to_s, cb['opts'], bid, parent_bid]
|
404
|
-
end
|
405
|
-
|
406
|
-
opts = {"bid" => bid, "event" => event}
|
407
|
-
should_schedule_batch = callback_args.present? && !callback_params.present?
|
408
|
-
already_processed = redis do |r|
|
409
|
-
SCHEDULE_CALLBACK.call(r, [batch_key], [event.to_s, should_schedule_batch.to_s, BID_EXPIRE_TTL])
|
410
|
-
end
|
411
|
-
|
412
|
-
return if already_processed == 'true'
|
413
|
-
|
414
|
-
if should_schedule_batch
|
415
|
-
logger.debug {"Enqueue callback bid: #{bid} event: #{event} args: #{callback_args.inspect}"}
|
416
|
-
|
417
|
-
# Create a new Batch to handle the callbacks and add it to the _parent_ batch
|
418
|
-
# (this ensures that the parent's lifecycle status can't change until the child's callbacks are done)
|
419
|
-
with_batch(parent_bid) do
|
420
|
-
cb_batch = self.new
|
421
|
-
cb_batch.callback_params = {
|
422
|
-
for_bid: bid,
|
423
|
-
event: event,
|
424
|
-
}
|
425
|
-
opts['callback_bid'] = cb_batch.bid
|
426
|
-
|
427
|
-
logger.debug {"Adding callback batch: #{cb_batch.bid} for batch: #{bid}"}
|
428
|
-
cb_batch.jobs do
|
429
|
-
push_callbacks(callback_args, queue)
|
430
|
-
end
|
431
|
-
end
|
432
|
-
end
|
433
|
-
|
434
|
-
if callback_params.present?
|
435
|
-
# This is a callback for a callback. Passing `origin` to the Finalizer allows it to also cleanup the original/callback-triggering batch
|
436
|
-
opts['origin'] = callback_params
|
437
|
-
end
|
438
|
-
|
439
|
-
# The Finalizer marks this batch as complete, bumps any necessary counters, cleans up this Batch _if_ no callbacks were scheduled,
|
440
|
-
# and enqueues parent-Batch callbacks if needed.
|
441
|
-
logger.debug {"Run batch finalizer bid: #{bid} event: #{event} args: #{callback_args.inspect}"}
|
442
|
-
finalizer = Batch::Callback::Finalize.new
|
443
|
-
status = Status.new bid
|
444
|
-
finalizer.dispatch(status, opts)
|
445
|
-
end
|
446
|
-
|
447
|
-
def cleanup_redis(bid)
|
448
|
-
logger.debug {"Cleaning redis of batch #{bid}"}
|
449
|
-
redis do |r|
|
450
|
-
r.zrem("batches", bid)
|
451
|
-
r.zrem("BID-ROOT-bids", bid)
|
452
|
-
r.unlink(
|
453
|
-
"BID-#{bid}",
|
454
|
-
"BID-#{bid}-callbacks-complete",
|
455
|
-
"BID-#{bid}-callbacks-success",
|
456
|
-
"BID-#{bid}-failed",
|
457
|
-
"BID-#{bid}-dead",
|
458
|
-
|
459
|
-
"BID-#{bid}-batches-success",
|
460
|
-
"BID-#{bid}-batches-complete",
|
461
|
-
"BID-#{bid}-batches-failed",
|
462
|
-
"BID-#{bid}-bids",
|
463
|
-
"BID-#{bid}-jids",
|
464
|
-
"BID-#{bid}-pending_callbacks",
|
465
|
-
)
|
466
|
-
end
|
467
|
-
end
|
468
|
-
|
469
|
-
def delete_prematurely!(bid)
|
470
|
-
child_bids = redis do |r|
|
471
|
-
r.zrange("BID-#{bid}-bids", 0, -1)
|
472
|
-
end
|
473
|
-
child_bids.each do |cbid|
|
474
|
-
delete_prematurely!(cbid)
|
475
|
-
end
|
476
|
-
cleanup_redis(bid)
|
477
|
-
end
|
478
|
-
|
479
|
-
# Internal method to cleanup a Redis Hash and related keys
|
480
|
-
def cleanup_redis_index_for(key, suffixes = [""])
|
481
|
-
redis do |r|
|
482
|
-
if r.hget(k, "created_at").present?
|
483
|
-
r.multi do |r|
|
484
|
-
suffixes.each do |suffix|
|
485
|
-
r.expire(key + suffix, BID_EXPIRE_TTL)
|
486
|
-
end
|
487
|
-
end
|
488
|
-
false
|
489
|
-
else
|
490
|
-
r.multi do |r|
|
491
|
-
suffixes.each do |suffix|
|
492
|
-
r.unlink(key + suffix)
|
493
|
-
end
|
494
|
-
end
|
495
|
-
true
|
496
|
-
end
|
497
|
-
end
|
498
|
-
end
|
499
|
-
|
500
|
-
# Administrative/console method to cleanup expired batches from the WebUI
|
501
|
-
def cleanup_redis_index!
|
502
|
-
suffixes = ["", "-callbacks-complete", "-callbacks-success", "-failed", "-dead", "-batches-success", "-batches-complete", "-batches-failed", "-bids", "-jids", "-pending_callbacks"]
|
503
|
-
|
504
|
-
redis do |r|
|
505
|
-
cleanup_index = ->(index) {
|
506
|
-
r.zrangebyscore(index, "0", BID_EXPIRE_TTL.seconds.ago.to_i).each do |bid|
|
507
|
-
r.zrem(index, bid) if cleanup_redis_index_for("BID-#{bid}", suffixes)
|
508
|
-
end
|
509
|
-
}
|
510
|
-
|
511
|
-
cleanup_index.("BID-ROOT-bids")
|
512
|
-
cleanup_index.("batches")
|
513
|
-
end
|
514
|
-
end
|
515
|
-
|
516
|
-
def redis(&blk)
|
517
|
-
return RedisProxy.new unless block_given?
|
518
|
-
|
519
|
-
if Thread.current[:job_batches_redis]
|
520
|
-
yield Thread.current[:job_batches_redis]
|
521
|
-
else
|
522
|
-
::Bearcat.redis do |r|
|
523
|
-
Thread.current[:job_batches_redis] = r
|
524
|
-
yield r
|
525
|
-
ensure
|
526
|
-
Thread.current[:job_batches_redis] = nil
|
527
|
-
end
|
528
|
-
end
|
529
|
-
end
|
530
|
-
|
531
|
-
def logger
|
532
|
-
::CanvasSync.logger
|
533
|
-
end
|
534
|
-
|
535
|
-
def push_callbacks(args, queue)
|
536
|
-
Batch::Callback::worker_class.enqueue_all(args, queue)
|
537
|
-
end
|
538
|
-
|
539
|
-
def bid_hierarchy(bid, depth: 4, per_depth: 5, slice: nil)
|
540
|
-
args = [bid, depth, per_depth]
|
541
|
-
args << slice if slice
|
542
|
-
redis do |r|
|
543
|
-
BID_HIERARCHY.call(r, [], args)
|
544
|
-
end
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
|
-
class RedisProxy
|
549
|
-
def multi(*args, &block)
|
550
|
-
Batch.redis do |r|
|
551
|
-
r.multi(*args) do |r|
|
552
|
-
block.call(r)
|
553
|
-
end
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
def pipelined(*args, &block)
|
558
|
-
Batch.redis do |r|
|
559
|
-
r.pipelined(*args) do |r2|
|
560
|
-
block.call(r2 || r)
|
561
|
-
end
|
562
|
-
end
|
563
|
-
end
|
564
|
-
|
565
|
-
def uget(key)
|
566
|
-
Batch.redis do |r|
|
567
|
-
case r.type(key)
|
568
|
-
when 'string'
|
569
|
-
r.get(key)
|
570
|
-
when 'list'
|
571
|
-
r.lrange(key, 0, -1)
|
572
|
-
when 'hash'
|
573
|
-
r.hgetall(key)
|
574
|
-
when 'set'
|
575
|
-
r.smembers(key)
|
576
|
-
when 'zset'
|
577
|
-
r.zrange(key, 0, -1)
|
578
|
-
end
|
579
|
-
end
|
580
|
-
end
|
581
|
-
|
582
|
-
def method_missing(method_name, *arguments, &block)
|
583
|
-
Batch.redis do |r|
|
584
|
-
r.send(method_name, *arguments, &block)
|
585
|
-
end
|
586
|
-
end
|
587
|
-
|
588
|
-
def respond_to_missing?(method_name, include_private = false)
|
589
|
-
super || Redis.method_defined?(method_name)
|
590
|
-
end
|
591
|
-
end
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
require_relative './compat'
|