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.
Files changed (205) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -4
  3. data/lib/canvas_sync/version.rb +1 -1
  4. data/lib/canvas_sync.rb +6 -4
  5. data/spec/canvas_sync/canvas_sync_spec.rb +11 -11
  6. data/spec/canvas_sync/live_events/process_event_job_spec.rb +1 -0
  7. data/spec/{dummy → internal}/app/models/application_record.rb +1 -0
  8. data/spec/{dummy → internal}/app/models/content_migration.rb +0 -0
  9. data/spec/{dummy → internal}/app/models/course.rb +0 -1
  10. data/spec/{dummy → internal}/app/models/course_nickname.rb +0 -0
  11. data/spec/{dummy → internal}/app/models/learning_outcome.rb +0 -0
  12. data/spec/{dummy → internal}/app/services/live_events/assignment_event.rb +0 -0
  13. data/spec/{dummy → internal}/app/services/live_events/course_event.rb +0 -0
  14. data/spec/{dummy → internal}/app/services/live_events/course_section_event.rb +0 -0
  15. data/spec/{dummy → internal}/app/services/live_events/enrollment_event.rb +0 -0
  16. data/spec/{dummy → internal}/app/services/live_events/grade_event.rb +0 -0
  17. data/spec/{dummy → internal}/app/services/live_events/module_event.rb +0 -0
  18. data/spec/{dummy → internal}/app/services/live_events/module_item_event.rb +0 -0
  19. data/spec/{dummy → internal}/app/services/live_events/submission_event.rb +0 -0
  20. data/spec/{dummy → internal}/app/services/live_events/syllabus_event.rb +0 -0
  21. data/spec/{dummy → internal}/app/services/live_events/user_event.rb +0 -0
  22. data/spec/internal/bin/rails +9 -0
  23. data/spec/internal/config/database.yml +5 -0
  24. data/spec/internal/config/routes.rb +5 -0
  25. data/spec/internal/config/storage.yml +3 -0
  26. data/spec/{dummy/db/migrate/20220926221926_create_users.rb → internal/db/migrate/20250912205136_create_users.rb} +0 -0
  27. data/spec/{dummy/db/migrate/20201016181346_create_pseudonyms.rb → internal/db/migrate/20250912205137_create_pseudonyms.rb} +0 -0
  28. data/spec/{dummy/db/migrate/20200415171620_create_groups.rb → internal/db/migrate/20250912205139_create_groups.rb} +0 -0
  29. data/spec/{dummy/db/migrate/20200416214248_create_group_memberships.rb → internal/db/migrate/20250912205140_create_group_memberships.rb} +0 -0
  30. data/spec/{dummy/db/migrate/20190702203622_create_accounts.rb → internal/db/migrate/20250912205141_create_accounts.rb} +0 -0
  31. data/spec/{dummy/db/migrate/20190702203623_create_terms.rb → internal/db/migrate/20250912205142_create_terms.rb} +0 -0
  32. data/spec/{dummy/db/migrate/20190702203625_create_sections.rb → internal/db/migrate/20250912205144_create_sections.rb} +0 -0
  33. data/spec/{dummy/db/migrate/20190702203626_create_assignments.rb → internal/db/migrate/20250912205145_create_assignments.rb} +0 -0
  34. data/spec/{dummy/db/migrate/20190702203627_create_submissions.rb → internal/db/migrate/20250912205146_create_submissions.rb} +0 -0
  35. data/spec/{dummy/db/migrate/20190927204545_create_roles.rb → internal/db/migrate/20250912205147_create_roles.rb} +2 -2
  36. data/spec/{dummy/db/migrate/20190927204546_create_admins.rb → internal/db/migrate/20250912205148_create_admins.rb} +0 -0
  37. data/spec/{dummy/db/migrate/20190702203630_create_assignment_groups.rb → internal/db/migrate/20250912205149_create_assignment_groups.rb} +0 -0
  38. data/spec/{dummy/db/migrate/20190702203631_create_context_modules.rb → internal/db/migrate/20250912205150_create_context_modules.rb} +0 -0
  39. data/spec/{dummy/db/migrate/20190702203632_create_context_module_items.rb → internal/db/migrate/20250912205151_create_context_module_items.rb} +0 -0
  40. data/spec/{dummy/db/migrate/20210907233329_create_user_observers.rb → internal/db/migrate/20250912205152_create_user_observers.rb} +0 -0
  41. data/spec/{dummy/db/migrate/20210907233330_create_grading_periods.rb → internal/db/migrate/20250912205153_create_grading_periods.rb} +0 -0
  42. data/spec/{dummy/db/migrate/20211001184920_create_grading_period_groups.rb → internal/db/migrate/20250912205154_create_grading_period_groups.rb} +0 -0
  43. data/spec/{dummy/db/migrate/20220308072643_create_content_migrations.rb → internal/db/migrate/20250912205155_create_content_migrations.rb} +0 -0
  44. data/spec/{dummy/db/migrate/20220712210559_create_learning_outcomes.rb → internal/db/migrate/20250912205156_create_learning_outcomes.rb} +0 -0
  45. data/spec/{dummy/db/migrate/20240523101010_create_learning_outcome_results.rb → internal/db/migrate/20250912205157_create_learning_outcome_results.rb} +0 -0
  46. data/spec/{dummy/db/migrate/20240510094100_create_rubric_associations.rb → internal/db/migrate/20250912205160_create_rubric_associations.rb} +0 -0
  47. data/spec/{dummy/db/migrate/20240510101100_create_rubric_assessments.rb → internal/db/migrate/20250912205161_create_rubric_assessments.rb} +0 -0
  48. data/spec/{dummy/db/migrate/20240828161300_create_course_progresses.rb → internal/db/migrate/20250912205162_create_course_progresses.rb} +0 -0
  49. data/spec/internal/db/schema.rb +6 -0
  50. data/spec/spec_helper.rb +8 -4
  51. metadata +182 -372
  52. data/lib/canvas_sync/job_batches/batch.rb +0 -595
  53. data/lib/canvas_sync/job_batches/callback.rb +0 -135
  54. data/lib/canvas_sync/job_batches/chain_builder.rb +0 -247
  55. data/lib/canvas_sync/job_batches/compat/active_job.rb +0 -108
  56. data/lib/canvas_sync/job_batches/compat/sidekiq/web/batches_assets/css/styles.less +0 -182
  57. data/lib/canvas_sync/job_batches/compat/sidekiq/web/batches_assets/js/batch_tree.js +0 -108
  58. data/lib/canvas_sync/job_batches/compat/sidekiq/web/batches_assets/js/util.js +0 -2
  59. data/lib/canvas_sync/job_batches/compat/sidekiq/web/helpers.rb +0 -41
  60. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_batch_tree.erb +0 -6
  61. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_batches_table.erb +0 -44
  62. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_common.erb +0 -13
  63. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_jobs_table.erb +0 -21
  64. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/_pagination.erb +0 -26
  65. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/batch.erb +0 -81
  66. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/batches.erb +0 -23
  67. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/pool.erb +0 -137
  68. data/lib/canvas_sync/job_batches/compat/sidekiq/web/views/pools.erb +0 -47
  69. data/lib/canvas_sync/job_batches/compat/sidekiq/web.rb +0 -218
  70. data/lib/canvas_sync/job_batches/compat/sidekiq.rb +0 -149
  71. data/lib/canvas_sync/job_batches/compat.rb +0 -20
  72. data/lib/canvas_sync/job_batches/context_hash.rb +0 -157
  73. data/lib/canvas_sync/job_batches/hier_batch_ids.lua +0 -25
  74. data/lib/canvas_sync/job_batches/jobs/base_job.rb +0 -5
  75. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +0 -20
  76. data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +0 -175
  77. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +0 -20
  78. data/lib/canvas_sync/job_batches/pool.rb +0 -254
  79. data/lib/canvas_sync/job_batches/pool_refill.lua +0 -47
  80. data/lib/canvas_sync/job_batches/redis_model.rb +0 -67
  81. data/lib/canvas_sync/job_batches/redis_script.rb +0 -161
  82. data/lib/canvas_sync/job_batches/schedule_callback.lua +0 -14
  83. data/lib/canvas_sync/job_batches/status.rb +0 -89
  84. data/lib/canvas_sync/job_uniqueness/compat/active_job.rb +0 -75
  85. data/lib/canvas_sync/job_uniqueness/compat/sidekiq.rb +0 -135
  86. data/lib/canvas_sync/job_uniqueness/compat.rb +0 -20
  87. data/lib/canvas_sync/job_uniqueness/configuration.rb +0 -25
  88. data/lib/canvas_sync/job_uniqueness/job_uniqueness.rb +0 -47
  89. data/lib/canvas_sync/job_uniqueness/lock_context.rb +0 -199
  90. data/lib/canvas_sync/job_uniqueness/locksmith.rb +0 -92
  91. data/lib/canvas_sync/job_uniqueness/on_conflict/base.rb +0 -32
  92. data/lib/canvas_sync/job_uniqueness/on_conflict/log.rb +0 -13
  93. data/lib/canvas_sync/job_uniqueness/on_conflict/null_strategy.rb +0 -9
  94. data/lib/canvas_sync/job_uniqueness/on_conflict/raise.rb +0 -11
  95. data/lib/canvas_sync/job_uniqueness/on_conflict/reject.rb +0 -21
  96. data/lib/canvas_sync/job_uniqueness/on_conflict/reschedule.rb +0 -20
  97. data/lib/canvas_sync/job_uniqueness/on_conflict.rb +0 -62
  98. data/lib/canvas_sync/job_uniqueness/strategy/base.rb +0 -107
  99. data/lib/canvas_sync/job_uniqueness/strategy/until_and_while_executing.rb +0 -35
  100. data/lib/canvas_sync/job_uniqueness/strategy/until_executed.rb +0 -20
  101. data/lib/canvas_sync/job_uniqueness/strategy/until_executing.rb +0 -20
  102. data/lib/canvas_sync/job_uniqueness/strategy/until_expired.rb +0 -16
  103. data/lib/canvas_sync/job_uniqueness/strategy/while_executing.rb +0 -26
  104. data/lib/canvas_sync/job_uniqueness/strategy.rb +0 -27
  105. data/lib/canvas_sync/job_uniqueness/unique_job_common.rb +0 -79
  106. data/spec/dummy/README.rdoc +0 -1
  107. data/spec/dummy/Rakefile +0 -6
  108. data/spec/dummy/app/services/live_events/assignment_created_event.rb +0 -12
  109. data/spec/dummy/app/services/live_events/assignment_updated_event.rb +0 -12
  110. data/spec/dummy/app/services/live_events/base_event.rb +0 -52
  111. data/spec/dummy/app/services/live_events/course_created_event.rb +0 -12
  112. data/spec/dummy/app/services/live_events/course_section_created_event.rb +0 -12
  113. data/spec/dummy/app/services/live_events/course_section_updated_event.rb +0 -12
  114. data/spec/dummy/app/services/live_events/course_updated_event.rb +0 -12
  115. data/spec/dummy/app/services/live_events/enrollment_created_event.rb +0 -12
  116. data/spec/dummy/app/services/live_events/enrollment_updated_event.rb +0 -12
  117. data/spec/dummy/app/services/live_events/grade_changed_event.rb +0 -12
  118. data/spec/dummy/app/services/live_events/module_created_event.rb +0 -12
  119. data/spec/dummy/app/services/live_events/module_item_created_event.rb +0 -12
  120. data/spec/dummy/app/services/live_events/module_item_updated_event.rb +0 -12
  121. data/spec/dummy/app/services/live_events/module_updated_event.rb +0 -12
  122. data/spec/dummy/app/services/live_events/submission_created_event.rb +0 -12
  123. data/spec/dummy/app/services/live_events/submission_updated_event.rb +0 -12
  124. data/spec/dummy/app/services/live_events/syllabus_updated_event.rb +0 -12
  125. data/spec/dummy/app/services/live_events/user_created_event.rb +0 -12
  126. data/spec/dummy/app/services/live_events/user_updated_event.rb +0 -12
  127. data/spec/dummy/bin/rails +0 -4
  128. data/spec/dummy/config/application.rb +0 -39
  129. data/spec/dummy/config/boot.rb +0 -5
  130. data/spec/dummy/config/database.yml +0 -25
  131. data/spec/dummy/config/environment.rb +0 -5
  132. data/spec/dummy/config/environments/development.rb +0 -41
  133. data/spec/dummy/config/environments/test.rb +0 -44
  134. data/spec/dummy/config/initializers/assets.rb +0 -11
  135. data/spec/dummy/config/initializers/session_store.rb +0 -3
  136. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -14
  137. data/spec/dummy/config/routes.rb +0 -3
  138. data/spec/dummy/config/secrets.yml +0 -22
  139. data/spec/dummy/config.ru +0 -4
  140. data/spec/dummy/db/schema.rb +0 -563
  141. data/spec/job_batching/batch_spec.rb +0 -531
  142. data/spec/job_batching/callback_spec.rb +0 -38
  143. data/spec/job_batching/compat/active_job_spec.rb +0 -107
  144. data/spec/job_batching/compat/sidekiq_spec.rb +0 -127
  145. data/spec/job_batching/context_hash_spec.rb +0 -54
  146. data/spec/job_batching/flow_spec.rb +0 -82
  147. data/spec/job_batching/integration/fail_then_succeed.rb +0 -42
  148. data/spec/job_batching/integration/integration.rb +0 -57
  149. data/spec/job_batching/integration/nested.rb +0 -88
  150. data/spec/job_batching/integration/simple.rb +0 -47
  151. data/spec/job_batching/integration/workflow.rb +0 -134
  152. data/spec/job_batching/integration_helper.rb +0 -50
  153. data/spec/job_batching/pool_spec.rb +0 -161
  154. data/spec/job_batching/status_spec.rb +0 -76
  155. data/spec/job_batching/support/base_job.rb +0 -14
  156. data/spec/job_batching/support/sample_callback.rb +0 -2
  157. data/spec/job_uniqueness/compat/active_job_spec.rb +0 -49
  158. data/spec/job_uniqueness/compat/sidekiq_spec.rb +0 -68
  159. data/spec/job_uniqueness/lock_context_spec.rb +0 -106
  160. data/spec/job_uniqueness/on_conflict/log_spec.rb +0 -11
  161. data/spec/job_uniqueness/on_conflict/raise_spec.rb +0 -10
  162. data/spec/job_uniqueness/on_conflict/reschedule_spec.rb +0 -63
  163. data/spec/job_uniqueness/on_conflict_spec.rb +0 -16
  164. data/spec/job_uniqueness/spec_helper.rb +0 -17
  165. data/spec/job_uniqueness/strategy/base_spec.rb +0 -100
  166. data/spec/job_uniqueness/strategy/until_and_while_executing_spec.rb +0 -48
  167. data/spec/job_uniqueness/strategy/until_executed_spec.rb +0 -23
  168. data/spec/job_uniqueness/strategy/until_executing_spec.rb +0 -23
  169. data/spec/job_uniqueness/strategy/until_expired_spec.rb +0 -23
  170. data/spec/job_uniqueness/strategy/while_executing_spec.rb +0 -33
  171. data/spec/job_uniqueness/support/lock_strategy.rb +0 -28
  172. data/spec/job_uniqueness/support/on_conflict.rb +0 -24
  173. data/spec/job_uniqueness/support/test_worker.rb +0 -19
  174. data/spec/job_uniqueness/unique_job_common_spec.rb +0 -45
  175. /data/spec/{dummy → internal}/app/models/account.rb +0 -0
  176. /data/spec/{dummy → internal}/app/models/admin.rb +0 -0
  177. /data/spec/{dummy → internal}/app/models/assignment.rb +0 -0
  178. /data/spec/{dummy → internal}/app/models/assignment_group.rb +0 -0
  179. /data/spec/{dummy → internal}/app/models/assignment_override.rb +0 -0
  180. /data/spec/{dummy → internal}/app/models/context_module.rb +0 -0
  181. /data/spec/{dummy → internal}/app/models/context_module_item.rb +0 -0
  182. /data/spec/{dummy → internal}/app/models/course_progress.rb +0 -0
  183. /data/spec/{dummy → internal}/app/models/enrollment.rb +0 -0
  184. /data/spec/{dummy → internal}/app/models/grading_period.rb +0 -0
  185. /data/spec/{dummy → internal}/app/models/grading_period_group.rb +0 -0
  186. /data/spec/{dummy → internal}/app/models/group.rb +0 -0
  187. /data/spec/{dummy → internal}/app/models/group_membership.rb +0 -0
  188. /data/spec/{dummy → internal}/app/models/learning_outcome_result.rb +0 -0
  189. /data/spec/{dummy → internal}/app/models/pseudonym.rb +0 -0
  190. /data/spec/{dummy → internal}/app/models/role.rb +0 -0
  191. /data/spec/{dummy → internal}/app/models/rubric.rb +0 -0
  192. /data/spec/{dummy → internal}/app/models/rubric_assessment.rb +0 -0
  193. /data/spec/{dummy → internal}/app/models/rubric_association.rb +0 -0
  194. /data/spec/{dummy → internal}/app/models/score.rb +0 -0
  195. /data/spec/{dummy → internal}/app/models/section.rb +0 -0
  196. /data/spec/{dummy → internal}/app/models/submission.rb +0 -0
  197. /data/spec/{dummy → internal}/app/models/term.rb +0 -0
  198. /data/spec/{dummy → internal}/app/models/user.rb +0 -0
  199. /data/spec/{dummy → internal}/app/models/user_observer.rb +0 -0
  200. /data/spec/{dummy/db/migrate/20190702203621_create_courses.rb → internal/db/migrate/20250912205138_create_courses.rb} +0 -0
  201. /data/spec/{dummy/db/migrate/20190702203624_create_enrollments.rb → internal/db/migrate/20250912205143_create_enrollments.rb} +0 -0
  202. /data/spec/{dummy/db/migrate/20250319194134_create_course_nicknames.rb → internal/db/migrate/20250912205158_create_course_nicknames.rb} +0 -0
  203. /data/spec/{dummy/db/migrate/20250319194135_create_rubrics.rb → internal/db/migrate/20250912205159_create_rubrics.rb} +0 -0
  204. /data/spec/{dummy/db/migrate/20241223080202_create_assignment_overrides.rb → internal/db/migrate/20250912205163_create_assignment_overrides.rb} +0 -0
  205. /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