canvas_sync 0.17.3.beta2 → 0.17.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 416545a03622711eba78ef420fd07aff9eb1e56ee9536b9e12369406885079c5
4
- data.tar.gz: e9cabdc870f0903bcffc60f711c0ed34a5b471d1bbdf1d8a2a4d83ce9ac745a4
3
+ metadata.gz: 99d4cd616a9692a100cfea8f3a3342e031f898311e211052f46eb16ee80c4bf1
4
+ data.tar.gz: 0d1b48e269c183b3c4df489439766474d2e31773d2743d55006a29e06d6e78ff
5
5
  SHA512:
6
- metadata.gz: 020ba0e260823c40a21273c3bda767c9774207fa36bf7e639d0927db19d544a27b1fefcebfdd64e2b4445ba5ca526098229d95e4c3ee7f5f6e336ef42da8a1f6
7
- data.tar.gz: 1840797de37257526d002507fe1849c84cd5cc71ed5bbb176a93061dce8d4c25e85528ec7312a3215e9520bcf4f708ac8cf5834539fdac4a7810919700844d33
6
+ metadata.gz: 17d7319ed2f35611f19428b85a823f218962fab5f843db9c8372e372bfa67c2267ecf4328134052857d2c157474bac36c34aa8b5b4ab7ba8598f28369b37efdf
7
+ data.tar.gz: 74d1cc1807dd95b75acda579bd9b7e0c42dd7fbfd0a3a4d7d3b6b5c08e277cc5a7661b15a63940170290d83049636433833b388a71788d7b73166deaf4bfe541
@@ -99,7 +99,7 @@ module CanvasSync
99
99
  if parent_bid
100
100
  r.hincrby("BID-#{parent_bid}", "children", 1)
101
101
  r.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
102
- r.sadd("BID-#{parent_bid}-bids", bid)
102
+ r.zadd("BID-#{parent_bid}-bids", created_at, bid)
103
103
  end
104
104
  end
105
105
  end
@@ -171,12 +171,19 @@ module CanvasSync
171
171
  @bidkey
172
172
  end
173
173
 
174
+ def flush_pending_attrs
175
+ super
176
+ redis do |r|
177
+ r.zadd("batches", created_at, bid)
178
+ end
179
+ end
180
+
174
181
  private
175
182
 
176
183
  def assert_batch_is_open
177
184
  unless defined?(@closed)
178
185
  redis do |r|
179
- @closed = r.hget(@bidkey, 'closed') == 'true'
186
+ @closed = r.hget(@bidkey, 'success') == 'true'
180
187
  end
181
188
  end
182
189
  raise "Cannot add jobs to Batch #{} bid - it has already entered the callback-stage" if @closed
@@ -184,15 +191,16 @@ module CanvasSync
184
191
 
185
192
  def append_jobs(jids)
186
193
  jids = jids.uniq
194
+ return unless jids.size > 0
195
+
187
196
  redis do |r|
197
+ tme = Time.now.utc.to_f
198
+ added = r.zadd(@bidkey + "-jids", jids.map{|jid| [tme, jid] }, nx: true)
188
199
  r.multi do
189
- r.hincrby(@bidkey, "pending", jids.size)
200
+ r.hincrby(@bidkey, "pending", added)
201
+ r.hincrby(@bidkey, "job_count", added)
190
202
  r.expire(@bidkey, BID_EXPIRE_TTL)
191
-
192
- if jids.size > 0
193
- r.sadd(@bidkey + "-jids", jids)
194
- r.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
195
- end
203
+ r.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
196
204
  end
197
205
  end
198
206
  end
@@ -200,6 +208,8 @@ module CanvasSync
200
208
  class << self
201
209
  def process_failed_job(bid, jid)
202
210
  _, pending, failed, children, complete, parent_bid = redis do |r|
211
+ return unless r.exists?("BID-#{bid}")
212
+
203
213
  r.multi do
204
214
  r.sadd("BID-#{bid}-failed", jid)
205
215
 
@@ -220,6 +230,8 @@ module CanvasSync
220
230
 
221
231
  def process_dead_job(bid, jid)
222
232
  _, failed, children, complete, parent_bid = redis do |r|
233
+ return unless r.exists?("BID-#{bid}")
234
+
223
235
  r.multi do
224
236
  r.sadd("BID-#{bid}-dead", jid)
225
237
 
@@ -246,6 +258,8 @@ module CanvasSync
246
258
 
247
259
  def process_successful_job(bid, jid)
248
260
  _, failed, pending, children, complete, success, parent_bid = redis do |r|
261
+ return unless r.exists?("BID-#{bid}")
262
+
249
263
  r.multi do
250
264
  r.srem("BID-#{bid}-failed", jid)
251
265
 
@@ -256,13 +270,12 @@ module CanvasSync
256
270
  r.scard("BID-#{bid}-batches-success")
257
271
  r.hget("BID-#{bid}", "parent_bid")
258
272
 
259
- r.srem("BID-#{bid}-jids", jid)
273
+ r.hincrby("BID-#{bid}", "successful-jobs", 1)
274
+ r.zrem("BID-#{bid}-jids", jid)
260
275
  r.expire("BID-#{bid}", BID_EXPIRE_TTL)
261
276
  end
262
277
  end
263
278
 
264
- # TODO - There seems to be an issue where a :complete callback batch will occasionally run its job, but won't enqueue_callbacks()
265
-
266
279
  all_success = pending.to_i.zero? && children == success
267
280
  # if complete or successfull call complete callback (the complete callback may then call successful)
268
281
  if (pending.to_i == failed.to_i && children == complete) || all_success
@@ -271,29 +284,13 @@ module CanvasSync
271
284
  end
272
285
  end
273
286
 
274
- def possibly_enqueue_callbacks(bid, types: [:complete, :success])
275
- pending_jobs, failed_jobs, total_batches, completed_batches, successful_batches = redis do |r|
276
- r.multi do
277
-
278
- end
279
- end
280
-
281
- if (types.include?(:complete)) && (pending_jobs.to_i == failed_jobs.to_i && total_batches.to_i == completed_batches.to_i)
282
- enqueue_callbacks(:complete, bid)
283
- end
284
- if (types.include?(:success)) && (pending_jobs.to_i.zero? && total_batches.to_i == successful_batches.to_i)
285
- enqueue_callbacks(:success, bid)
286
- end
287
- end
288
-
289
287
  def enqueue_callbacks(event, bid)
290
288
  batch_key = "BID-#{bid}"
291
289
  callback_key = "#{batch_key}-callbacks-#{event}"
292
- already_processed, _, _, callbacks, queue, parent_bid, callback_params = redis do |r|
290
+ already_processed, _, callbacks, queue, parent_bid, callback_params = redis do |r|
293
291
  return unless r.exists?(batch_key)
294
292
  r.multi do
295
293
  r.hget(batch_key, event)
296
- r.hset(batch_key, "closed", true)
297
294
  r.hset(batch_key, event, true)
298
295
  r.smembers(callback_key)
299
296
  r.hget(batch_key, "callback_queue")
@@ -317,6 +314,11 @@ module CanvasSync
317
314
  if callback_args.present? && !callback_params.present?
318
315
  logger.debug {"Enqueue callback bid: #{bid} event: #{event} args: #{callback_args.inspect}"}
319
316
 
317
+ redis do |r|
318
+ r.sadd("#{batch_key}-pending_callbacks", event)
319
+ r.expire("#{batch_key}-pending_callbacks", BID_EXPIRE_TTL)
320
+ end
321
+
320
322
  with_batch(parent_bid) do
321
323
  cb_batch = self.new
322
324
  cb_batch.callback_params = {
@@ -345,7 +347,8 @@ module CanvasSync
345
347
  def cleanup_redis(bid)
346
348
  logger.debug {"Cleaning redis of batch #{bid}"}
347
349
  redis do |r|
348
- r.del(
350
+ r.zrem("batches", bid)
351
+ r.unlink(
349
352
  "BID-#{bid}",
350
353
  "BID-#{bid}-callbacks-complete",
351
354
  "BID-#{bid}-callbacks-success",
@@ -356,10 +359,21 @@ module CanvasSync
356
359
  "BID-#{bid}-batches-failed",
357
360
  "BID-#{bid}-bids",
358
361
  "BID-#{bid}-jids",
362
+ "BID-#{bid}-pending_callbacks",
359
363
  )
360
364
  end
361
365
  end
362
366
 
367
+ def delete_prematurely!(bid)
368
+ child_bids = redis do |r|
369
+ r.zrange("BID-#{bid}-bids", 0, -1)
370
+ end
371
+ child_bids.each do |cbid|
372
+ delete_prematurely!(cbid)
373
+ end
374
+ cleanup_redis(bid)
375
+ end
376
+
363
377
  def redis(*args, &blk)
364
378
  defined?(::Sidekiq) ? ::Sidekiq.redis(*args, &blk) : nil # TODO
365
379
  end
@@ -368,8 +382,6 @@ module CanvasSync
368
382
  defined?(::Sidekiq) ? ::Sidekiq.logger : Rails.logger
369
383
  end
370
384
 
371
- private
372
-
373
385
  def push_callbacks(args, queue)
374
386
  Batch::Callback::worker_class.enqueue_all(args, queue)
375
387
  end
@@ -10,7 +10,7 @@ module CanvasSync
10
10
  begin
11
11
  Thread.current[:batch] = Batch.new(@bid)
12
12
  block.call
13
- batch&.save_context_changes
13
+ Thread.current[:batch].save_context_changes
14
14
  Batch.process_successful_job(@bid, job_id)
15
15
  rescue
16
16
  Batch.process_failed_job(@bid, job_id)
@@ -52,9 +52,6 @@ module CanvasSync
52
52
 
53
53
  class Finalize
54
54
  def dispatch(status, opts)
55
- is_callback_batch = opts['origin'].present?
56
- has_callback_batch = opts['callback_bid'].present?
57
-
58
55
  bid = opts["bid"]
59
56
  event = opts["event"].to_sym
60
57
 
@@ -63,19 +60,28 @@ module CanvasSync
63
60
  batch_status = Status.new bid
64
61
  send(event, bid, batch_status, batch_status.parent_bid)
65
62
 
66
- if event == :success && !has_callback_batch
67
- Batch.cleanup_redis(bid)
68
- end
69
-
70
- if event == :success && is_callback_batch && opts['origin']['event'].to_sym == :success
71
- Batch.cleanup_redis(opts['origin']['for_bid'])
63
+ if event == :success
64
+ if opts['origin'].present?
65
+ origin_bid = opts['origin']['for_bid']
66
+ _, pending, success_ran = Batch.redis do |r|
67
+ r.multi do
68
+ r.srem("BID-#{origin_bid}-pending_callbacks", opts['origin']['event'])
69
+ r.scard("BID-#{origin_bid}-pending_callbacks")
70
+ r.hget("BID-#{origin_bid}", "success")
71
+ end
72
+ end
73
+ Batch.cleanup_redis(origin_bid) if pending == 0 && success_ran == 'true'
74
+ end
75
+ if (Batch.redis {|r| r.scard("BID-#{bid}-pending_callbacks") }) == 0
76
+ Batch.cleanup_redis(bid)
77
+ end
72
78
  end
73
79
  end
74
80
 
75
81
  def success(bid, status, parent_bid)
76
82
  return unless parent_bid
77
83
 
78
- _, _, success, _, _, complete, pending, children, failure = Batch.redis do |r|
84
+ _, _, success, _, _, complete, pending, children, success, failure = Batch.redis do |r|
79
85
  r.multi do
80
86
  r.sadd("BID-#{parent_bid}-batches-success", bid)
81
87
  r.expire("BID-#{parent_bid}-batches-success", Batch::BID_EXPIRE_TTL)
@@ -87,6 +93,7 @@ module CanvasSync
87
93
 
88
94
  r.hincrby("BID-#{parent_bid}", "pending", 0)
89
95
  r.hincrby("BID-#{parent_bid}", "children", 0)
96
+ r.scard("BID-#{parent_bid}-batches-success")
90
97
  r.scard("BID-#{parent_bid}-failed")
91
98
  end
92
99
  end
@@ -97,6 +104,10 @@ module CanvasSync
97
104
  Batch.logger.debug {"Finalize parent complete bid: #{parent_bid}"}
98
105
  Batch.enqueue_callbacks(:complete, parent_bid)
99
106
  end
107
+ if pending.to_i.zero? && children == success
108
+ Batch.logger.debug {"Finalize parent success bid: #{parent_bid}"}
109
+ Batch.enqueue_callbacks(:success, parent_bid)
110
+ end
100
111
  end
101
112
 
102
113
  def complete(bid, status, parent_bid)
@@ -108,13 +119,7 @@ module CanvasSync
108
119
  end
109
120
  end
110
121
 
111
- # If the batch was successful run :success callback, which will call the parent's :complete callback (if necessary)
112
- # Also, only trigger the success callback if the :complete callback_batch was successful
113
- if pending.to_i.zero? && children == success
114
- Batch.enqueue_callbacks(:success, bid)
115
-
116
- # otherwise check for a parent and call its :complete if needed
117
- elsif parent_bid
122
+ if parent_bid && !(pending.to_i.zero? && children == success)
118
123
  # if batch was not successfull check and see if its parent is complete
119
124
  # if the parent is complete we trigger the complete callback
120
125
  # We don't want to run this if the batch was successfull because the success
@@ -28,6 +28,10 @@ module CanvasSync
28
28
  @hash_map[local_bid]
29
29
  end
30
30
 
31
+ def own
32
+ resolve_hash(@bid_stack[-1]) || {}
33
+ end
34
+
31
35
  def set_local(new_hash)
32
36
  @dirty = true
33
37
  local.clear.merge!(new_hash)
@@ -106,7 +110,8 @@ module CanvasSync
106
110
  @bid_stack[index]
107
111
  else
108
112
  pbid = Batch.redis do |r|
109
- r.hget("BID-#{bid}", "parent_bid") || r.hget("BID-#{bid}", "callback_for")
113
+ callback_params = JSON.parse(r.hget("BID-#{bid}", "callback_params") || "{}")
114
+ callback_params['for_bid'] || r.hget("BID-#{bid}", "parent_bid")
110
115
  end
111
116
  @bid_stack.unshift(pbid)
112
117
  pbid
@@ -10,7 +10,7 @@ module CanvasSync
10
10
  redis_attr :created_at
11
11
  redis_attr :concurrency, :int
12
12
  redis_attr :order
13
- redis_attr :on_failed_job
13
+ redis_attr :on_failed_job, :symbol
14
14
  redis_attr :clean_when_empty, :bool
15
15
 
16
16
  def initialize(pooolid = nil, **kwargs)
@@ -24,6 +24,7 @@ module CanvasSync
24
24
  end
25
25
 
26
26
  def self.from_pid(pid)
27
+ raise "PID must be given" unless pid.present?
27
28
  new(pid)
28
29
  end
29
30
 
@@ -38,7 +39,7 @@ module CanvasSync
38
39
  def add_jobs(job_descs)
39
40
  job_descs.each do |job_desc|
40
41
  wrapper = Batch.new
41
- wrapper.description = "Pool Job Wrapper"
42
+ wrapper.description = "Pool Job Wrapper (PID: #{pid})"
42
43
  checkin_event = (on_failed_job == :wait) ? :success : :complete
43
44
  wrapper.on(checkin_event, "#{self.class.to_s}.job_checked_in", pool_id: pid)
44
45
  wrapper.jobs {}
@@ -57,19 +58,42 @@ module CanvasSync
57
58
  def cleanup_redis
58
59
  Batch.logger.debug {"Cleaning redis of pool #{pid}"}
59
60
  redis do |r|
60
- r.del(
61
+ r.zrem("pools", pid)
62
+ r.unlink(
61
63
  "#{redis_key}",
62
64
  "#{redis_key}-jobs",
63
65
  )
64
66
  end
65
67
  end
66
68
 
69
+ def active_count
70
+ redis do |r|
71
+ r.hincrby(redis_key, "active_count", 0)
72
+ end
73
+ end
74
+
75
+ def pending_count
76
+ jobs_key = "#{redis_key}-jobs"
77
+ order = self.order || 'fifo'
78
+ redis do |r|
79
+ case order.to_sym
80
+ when :fifo, :lifo
81
+ r.llen(jobs_key)
82
+ when :random
83
+ r.scard(jobs_key)
84
+ when :priority
85
+ r.zcard(jobs_key)
86
+ end
87
+ end
88
+ end
89
+
67
90
  def job_checked_in(status, options)
68
91
  active_count = redis do |r|
92
+ return unless r.exists?(redis_key)
69
93
  r.hincrby(redis_key, "active_count", -1)
70
94
  end
71
- added_count = refill_allotment
72
95
 
96
+ added_count = refill_allotment
73
97
  if active_count == 0 && added_count == 0
74
98
  cleanup_redis if clean_when_empty
75
99
  end
@@ -159,29 +183,25 @@ module CanvasSync
159
183
  ActiveJob::Arguments.deserialize(JSON.parse(job_json))[0]
160
184
  end
161
185
 
162
- def pending_count
163
- order = self.order
164
- redis do |r|
165
- case order.to_sym
166
- when :fifo, :lifo
167
- r.llen(jobs_key)
168
- when :random
169
- r.scard(jobs_key)
170
- when :priority
171
- r.zcard(jobs_key)
172
- end
173
- end
174
- end
175
-
176
186
  def self.redis(&blk)
177
187
  Batch.redis &blk
178
188
  end
179
189
  delegate :redis, to: :class
180
190
 
191
+ protected
192
+
193
+ def flush_pending_attrs
194
+ super
195
+ redis do |r|
196
+ r.zadd("pools", created_at, pid)
197
+ end
198
+ end
199
+
181
200
  private
182
201
 
183
- def initialize_new(concurrency: nil, order: :fifo, clean_when_empty: true, on_failed_job: :wait)
202
+ def initialize_new(concurrency: nil, order: :fifo, clean_when_empty: true, on_failed_job: :wait, description: nil)
184
203
  self.created_at = Time.now.utc.to_f
204
+ self.description = description
185
205
  self.order = order
186
206
  self.concurrency = concurrency
187
207
  self.clean_when_empty = clean_when_empty
@@ -27,6 +27,8 @@ module CanvasSync
27
27
  value = value.to_f
28
28
  elsif :#{type} == :json
29
29
  value = JSON.parse(value)
30
+ elsif :#{type} == :symbol
31
+ value = value&.to_sym
30
32
  end
31
33
  @#{key} = value
32
34
  end
@@ -57,16 +57,17 @@ module CanvasSync
57
57
  class ServerMiddleware
58
58
  def call(_worker, msg, _queue)
59
59
  if (bid = msg['bid'])
60
+ prev_batch = Thread.current[:batch]
60
61
  begin
61
62
  Thread.current[:batch] = Batch.new(bid)
62
63
  yield
63
- Thread.current[:batch] = nil
64
+ Thread.current[:batch].save_context_changes
64
65
  Batch.process_successful_job(bid, msg['jid'])
65
66
  rescue
66
67
  Batch.process_failed_job(bid, msg['jid'])
67
68
  raise
68
69
  ensure
69
- Thread.current[:batch] = nil
70
+ Thread.current[:batch] = prev_batch
70
71
  end
71
72
  else
72
73
  yield
@@ -117,3 +118,5 @@ module CanvasSync
117
118
  end
118
119
  end
119
120
  end
121
+
122
+ require_relative 'sidekiq/web'
@@ -0,0 +1,114 @@
1
+
2
+ begin
3
+ require "sidekiq/web"
4
+ rescue LoadError
5
+ # client-only usage
6
+ end
7
+
8
+ require_relative "web/helpers"
9
+
10
+ module CanvasSync::JobBatches::Sidekiq
11
+ module Web
12
+ def self.registered(app) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
13
+ app.helpers do
14
+ include Web::Helpers
15
+ end
16
+
17
+ app.get "/batches" do
18
+ @count = (params['count'] || 25).to_i
19
+ @current_page, @total_size, @batches = page('batches', params['page'], @count)
20
+ @batches = @batches.map {|b, score| CanvasSync::JobBatches::Batch.new(b) }
21
+
22
+ erb(get_template(:batches))
23
+ end
24
+
25
+ app.get "/batches/:bid" do
26
+ @bid = params[:bid]
27
+ @batch = CanvasSync::JobBatches::Batch.new(@bid)
28
+
29
+ @count = (params['count'] || 25).to_i
30
+ @current_batches_page, @total_batches_size, @sub_batches = page("BID-#{@batch.bid}-bids", params['batch_page'], @count)
31
+ @sub_batches = @sub_batches.map {|b, score| CanvasSync::JobBatches::Batch.new(b) }
32
+
33
+ @current_jobs_page, @total_jobs_size, @jobs = page("BID-#{@batch.bid}-jids", params['job_page'], @count)
34
+ @jobs = @jobs.map {|jid, score| jid }
35
+
36
+ erb(get_template(:batch))
37
+ end
38
+
39
+ app.post "/batches/all" do
40
+ if params['delete']
41
+ drain_zset('batches') do |batches|
42
+ batches.each do |bid|
43
+ CanvasSync::JobBatches::Batch.cleanup_redis(bid)
44
+ end
45
+ end
46
+ end
47
+
48
+ redirect "#{root_path}batches"
49
+ end
50
+
51
+ app.post "/batches/:bid" do
52
+ @bid = params[:bid]
53
+ @batch = CanvasSync::JobBatches::Batch.new(@bid)
54
+
55
+ if params['delete']
56
+ CanvasSync::JobBatches::Batch.delete_prematurely!(@bid)
57
+ end
58
+
59
+ redirect_with_query("#{root_path}batches")
60
+ end
61
+
62
+ # =============== POOLS =============== #
63
+
64
+ app.get "/pools" do
65
+ @count = (params['count'] || 25).to_i
66
+ @current_page, @total_size, @pools = page('pools', params['page'], @count)
67
+ @pools = @pools.map {|b, score| CanvasSync::JobBatches::Pool.new(b) }
68
+
69
+ erb(get_template(:pools))
70
+ end
71
+
72
+ app.get "/pools/:pid" do
73
+ @pid = params[:pid]
74
+ @pool = CanvasSync::JobBatches::Pool.new(@pid)
75
+
76
+ @count = (params['count'] || 25).to_i
77
+ @current_jobs_page, @total_jobs_size, @jobs = page("POOLID-#{@pool.pid}-jobs", params['job_page'], @count)
78
+ @jobs = @jobs.map {|desc, score=nil| JSON.parse(desc)[0] }
79
+
80
+ erb(get_template(:pool))
81
+ end
82
+
83
+ app.post "/pools/all" do
84
+ if params['delete']
85
+ drain_zset('pools') do |pools|
86
+ pools.each do |pid|
87
+ CanvasSync::JobBatches::Pool.from_pid(pid).cleanup_redis
88
+ end
89
+ end
90
+ end
91
+
92
+ redirect "#{root_path}pools"
93
+ end
94
+
95
+ app.post "/pools/:pid" do
96
+ @pid = params[:pid]
97
+ @pool = CanvasSync::JobBatches::Pool.from_pid(@pid)
98
+
99
+ if params['delete']
100
+ @pool.cleanup_redis
101
+ end
102
+
103
+ redirect_with_query("#{root_path}pools")
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ if defined?(::Sidekiq::Web)
110
+ ::Sidekiq::Web.register CanvasSync::JobBatches::Sidekiq::Web
111
+ ::Sidekiq::Web.tabs["Batches"] = "batches"
112
+ ::Sidekiq::Web.tabs["Pools"] = "pools"
113
+ ::Sidekiq::Web.settings.locales << File.join(File.dirname(__FILE__), "locales")
114
+ end