canvas_sync 0.17.3.beta1 → 0.17.5.beta2

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: a67693c73d6b2bd34ffd9cf6ac17038c4d226eab30e1795ebbbd6a40d03e1bff
4
- data.tar.gz: 2d42669d68de6ef9242e3d956ff15c82bb18fc07ee5d9e94c5eb1ac283928599
3
+ metadata.gz: ba2e2585837d6eefc1b0aa4e0f2f531598442618afc5b3d6d78ca9fda9596278
4
+ data.tar.gz: b198271129b9ff7623d203d30d3c206aea92f540a92fdd52258196745b88519e
5
5
  SHA512:
6
- metadata.gz: 8d820e38c40e77e5199ef07af41a475ad7a35d36198ee315cb552324b1159592e329d6401779ad218ed7a7c30abdbe7df2995ad26b132a1da8c33f0dea83a516
7
- data.tar.gz: 035e631264ef58221f1163e60e4e8b4167a1930800c4e54bf143f148a0e56a2447d81fa0d8150f6d2ade81a9dfb5eebec07f826413cae0fd958543fb68fe9e0f
6
+ metadata.gz: a88e9f513aac8a8b556c38a7e54d8fcc0209fecfa1e342d9873205677c8c27875d6a9a690115eabbb0aa549c8f9988b939581b897f889895bb59ed3e918ab4e8
7
+ data.tar.gz: 5438fe0b453dfbf394b6f778fcda09f423a3d7a5421b64b1252ecc090d0005b21a5d0301ac345cf8bcfe40ae87e8553e3678e63b2c0420694499f4070101d9a5
@@ -19,6 +19,8 @@ require_relative "./chain_builder"
19
19
 
20
20
  module CanvasSync
21
21
  module JobBatches
22
+ class SuccessfulFailure < RuntimeError; end
23
+
22
24
  class Batch
23
25
  include RedisModel
24
26
 
@@ -35,7 +37,6 @@ module CanvasSync
35
37
  @existing = !(!existing_bid || existing_bid.empty?) # Basically existing_bid.present?
36
38
  @initialized = false
37
39
  @bidkey = "BID-" + @bid.to_s
38
- @ready_to_queue = nil
39
40
  self.created_at = Time.now.utc.to_f unless @existing
40
41
  end
41
42
 
@@ -100,7 +101,7 @@ module CanvasSync
100
101
  if parent_bid
101
102
  r.hincrby("BID-#{parent_bid}", "children", 1)
102
103
  r.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
103
- r.sadd("BID-#{parent_bid}-bids", bid)
104
+ r.zadd("BID-#{parent_bid}-bids", created_at, bid)
104
105
  end
105
106
  end
106
107
  end
@@ -113,28 +114,20 @@ module CanvasSync
113
114
  assert_batch_is_open
114
115
  end
115
116
 
116
- job_queue = @ready_to_queue = []
117
-
118
117
  begin
119
118
  parent = Thread.current[:batch]
120
119
  Thread.current[:batch] = self
121
120
  yield
122
121
  ensure
123
- @ready_to_queue = nil
124
- append_jobs(job_queue, parent_bid)
125
122
  Thread.current[:batch] = parent
126
123
  end
127
124
 
128
- job_queue
125
+ nil
129
126
  end
130
127
 
131
128
  def increment_job_queue(jid)
132
- if @ready_to_queue
133
- @ready_to_queue << jid
134
- else
135
- assert_batch_is_open
136
- append_jobs([jid])
137
- end
129
+ assert_batch_is_open
130
+ append_jobs([jid])
138
131
  end
139
132
 
140
133
  def invalidate_all
@@ -180,27 +173,36 @@ module CanvasSync
180
173
  @bidkey
181
174
  end
182
175
 
176
+ def flush_pending_attrs
177
+ super
178
+ redis do |r|
179
+ r.zadd("batches", created_at, bid)
180
+ end
181
+ end
182
+
183
183
  private
184
184
 
185
185
  def assert_batch_is_open
186
186
  unless defined?(@closed)
187
187
  redis do |r|
188
- @closed = r.hget(@bidkey, 'closed') == 'true'
188
+ @closed = r.hget(@bidkey, 'success') == 'true'
189
189
  end
190
190
  end
191
191
  raise "Cannot add jobs to Batch #{} bid - it has already entered the callback-stage" if @closed
192
192
  end
193
193
 
194
- def append_jobs(jids, parent_bid = self.parent_bid)
194
+ def append_jobs(jids)
195
+ jids = jids.uniq
196
+ return unless jids.size > 0
197
+
195
198
  redis do |r|
199
+ tme = Time.now.utc.to_f
200
+ added = r.zadd(@bidkey + "-jids", jids.map{|jid| [tme, jid] }, nx: true)
196
201
  r.multi do
197
- r.hincrby(@bidkey, "pending", jids.size)
202
+ r.hincrby(@bidkey, "pending", added)
203
+ r.hincrby(@bidkey, "job_count", added)
198
204
  r.expire(@bidkey, BID_EXPIRE_TTL)
199
-
200
- if jids.size > 0
201
- r.sadd(@bidkey + "-jids", jids)
202
- r.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
203
- end
205
+ r.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
204
206
  end
205
207
  end
206
208
  end
@@ -208,6 +210,8 @@ module CanvasSync
208
210
  class << self
209
211
  def process_failed_job(bid, jid)
210
212
  _, pending, failed, children, complete, parent_bid = redis do |r|
213
+ return unless r.exists?("BID-#{bid}")
214
+
211
215
  r.multi do
212
216
  r.sadd("BID-#{bid}-failed", jid)
213
217
 
@@ -228,6 +232,8 @@ module CanvasSync
228
232
 
229
233
  def process_dead_job(bid, jid)
230
234
  _, failed, children, complete, parent_bid = redis do |r|
235
+ return unless r.exists?("BID-#{bid}")
236
+
231
237
  r.multi do
232
238
  r.sadd("BID-#{bid}-dead", jid)
233
239
 
@@ -254,6 +260,8 @@ module CanvasSync
254
260
 
255
261
  def process_successful_job(bid, jid)
256
262
  _, failed, pending, children, complete, success, parent_bid = redis do |r|
263
+ return unless r.exists?("BID-#{bid}")
264
+
257
265
  r.multi do
258
266
  r.srem("BID-#{bid}-failed", jid)
259
267
 
@@ -264,7 +272,8 @@ module CanvasSync
264
272
  r.scard("BID-#{bid}-batches-success")
265
273
  r.hget("BID-#{bid}", "parent_bid")
266
274
 
267
- r.srem("BID-#{bid}-jids", jid)
275
+ r.hincrby("BID-#{bid}", "successful-jobs", 1)
276
+ r.zrem("BID-#{bid}-jids", jid)
268
277
  r.expire("BID-#{bid}", BID_EXPIRE_TTL)
269
278
  end
270
279
  end
@@ -277,29 +286,13 @@ module CanvasSync
277
286
  end
278
287
  end
279
288
 
280
- def possibly_enqueue_callbacks(bid, types: [:complete, :success])
281
- pending_jobs, failed_jobs, total_batches, completed_batches, successful_batches = redis do |r|
282
- r.multi do
283
-
284
- end
285
- end
286
-
287
- if (types.include?(:complete)) && (pending_jobs.to_i == failed_jobs.to_i && total_batches.to_i == completed_batches.to_i)
288
- enqueue_callbacks(:complete, bid)
289
- end
290
- if (types.include?(:success)) && (pending_jobs.to_i.zero? && total_batches.to_i == successful_batches.to_i)
291
- enqueue_callbacks(:success, bid)
292
- end
293
- end
294
-
295
289
  def enqueue_callbacks(event, bid)
296
290
  batch_key = "BID-#{bid}"
297
291
  callback_key = "#{batch_key}-callbacks-#{event}"
298
- already_processed, _, _, callbacks, queue, parent_bid, callback_params = redis do |r|
292
+ already_processed, _, callbacks, queue, parent_bid, callback_params = redis do |r|
299
293
  return unless r.exists?(batch_key)
300
294
  r.multi do
301
295
  r.hget(batch_key, event)
302
- r.hset(batch_key, "closed", true)
303
296
  r.hset(batch_key, event, true)
304
297
  r.smembers(callback_key)
305
298
  r.hget(batch_key, "callback_queue")
@@ -323,6 +316,11 @@ module CanvasSync
323
316
  if callback_args.present? && !callback_params.present?
324
317
  logger.debug {"Enqueue callback bid: #{bid} event: #{event} args: #{callback_args.inspect}"}
325
318
 
319
+ redis do |r|
320
+ r.sadd("#{batch_key}-pending_callbacks", event)
321
+ r.expire("#{batch_key}-pending_callbacks", BID_EXPIRE_TTL)
322
+ end
323
+
326
324
  with_batch(parent_bid) do
327
325
  cb_batch = self.new
328
326
  cb_batch.callback_params = {
@@ -351,7 +349,8 @@ module CanvasSync
351
349
  def cleanup_redis(bid)
352
350
  logger.debug {"Cleaning redis of batch #{bid}"}
353
351
  redis do |r|
354
- r.del(
352
+ r.zrem("batches", bid)
353
+ r.unlink(
355
354
  "BID-#{bid}",
356
355
  "BID-#{bid}-callbacks-complete",
357
356
  "BID-#{bid}-callbacks-success",
@@ -362,10 +361,21 @@ module CanvasSync
362
361
  "BID-#{bid}-batches-failed",
363
362
  "BID-#{bid}-bids",
364
363
  "BID-#{bid}-jids",
364
+ "BID-#{bid}-pending_callbacks",
365
365
  )
366
366
  end
367
367
  end
368
368
 
369
+ def delete_prematurely!(bid)
370
+ child_bids = redis do |r|
371
+ r.zrange("BID-#{bid}-bids", 0, -1)
372
+ end
373
+ child_bids.each do |cbid|
374
+ delete_prematurely!(cbid)
375
+ end
376
+ cleanup_redis(bid)
377
+ end
378
+
369
379
  def redis(*args, &blk)
370
380
  defined?(::Sidekiq) ? ::Sidekiq.redis(*args, &blk) : nil # TODO
371
381
  end
@@ -374,8 +384,6 @@ module CanvasSync
374
384
  defined?(::Sidekiq) ? ::Sidekiq.logger : Rails.logger
375
385
  end
376
386
 
377
- private
378
-
379
387
  def push_callbacks(args, queue)
380
388
  Batch::Callback::worker_class.enqueue_all(args, queue)
381
389
  end
@@ -10,8 +10,12 @@ 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
+ rescue SuccessfulFailure => err
16
+ Thread.current[:batch].save_context_changes
17
+ Batch.process_successful_job(@bid, job_id)
18
+ raise
15
19
  rescue
16
20
  Batch.process_failed_job(@bid, job_id)
17
21
  raise
@@ -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,15 +93,21 @@ 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
100
+
93
101
  # If the job finished successfully and parent batch is completed, call parent :complete callback
94
102
  # Parent :success callback will be called by its :complete callback
95
103
  if complete == children && pending == failure
96
104
  Batch.logger.debug {"Finalize parent complete bid: #{parent_bid}"}
97
105
  Batch.enqueue_callbacks(:complete, parent_bid)
98
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
99
111
  end
100
112
 
101
113
  def complete(bid, status, parent_bid)
@@ -107,13 +119,7 @@ module CanvasSync
107
119
  end
108
120
  end
109
121
 
110
- # If the batch was successful run :success callback, which will call the parent's :complete callback (if necessary)
111
- # Also, only trigger the success callback if the :complete callback_batch was successful
112
- if pending.to_i.zero? && children == success
113
- Batch.enqueue_callbacks(:success, bid)
114
-
115
- # otherwise check for a parent and call its :complete if needed
116
- elsif parent_bid
122
+ if parent_bid && !(pending.to_i.zero? && children == success)
117
123
  # if batch was not successfull check and see if its parent is complete
118
124
  # if the parent is complete we trigger the complete callback
119
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,9 +39,9 @@ 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.on(:complete, "#{self.class.to_s}.job_completed_callback", pool_id: pid)
43
- wrapper.on(:success, "#{self.class.to_s}.job_success_callback", pool_id: pid)
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)
44
45
  wrapper.jobs {}
45
46
 
46
47
  job_desc = job_desc.with_indifferent_access
@@ -57,43 +58,50 @@ 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
 
67
- def handle_event(event, options)
68
- queue_next = false
69
- if event == :success && on_failed_job.to_sym == :wait # We trigger on the complete event, unless on_failed_job is :wait
70
- queue_next = true
71
- elsif event == :complete
72
- if on_failed_job.to_sym != :wait
73
- queue_next = true
74
- end
69
+ def active_count
70
+ redis do |r|
71
+ r.hincrby(redis_key, "active_count", 0)
75
72
  end
73
+ end
76
74
 
77
- if queue_next
78
- active_count = redis do |r|
79
- r.hincrby(redis_key, "active_count", -1)
80
- end
81
- added_count = refill_allotment
82
-
83
- if active_count == 0 && added_count == 0
84
- cleanup_redis if clean_when_empty
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)
85
86
  end
86
87
  end
87
88
  end
88
89
 
89
- def self.job_completed_callback(status, options)
90
- pid = options['pool_id']
91
- from_pid(pid).handle_event(:complete, options)
90
+ def job_checked_in(status, options)
91
+ active_count = redis do |r|
92
+ return unless r.exists?(redis_key)
93
+ r.hincrby(redis_key, "active_count", -1)
94
+ end
95
+
96
+ added_count = refill_allotment
97
+ if active_count == 0 && added_count == 0
98
+ cleanup_redis if clean_when_empty
99
+ end
92
100
  end
93
101
 
94
- def self.job_success_callback(status, options)
102
+ def self.job_checked_in(status, options)
95
103
  pid = options['pool_id']
96
- from_pid(pid).handle_event(:success, options)
104
+ from_pid(pid).job_checked_in(status, options)
97
105
  end
98
106
 
99
107
  protected
@@ -175,29 +183,25 @@ module CanvasSync
175
183
  ActiveJob::Arguments.deserialize(JSON.parse(job_json))[0]
176
184
  end
177
185
 
178
- def pending_count
179
- order = self.order
180
- redis do |r|
181
- case order.to_sym
182
- when :fifo, :lifo
183
- r.llen(jobs_key)
184
- when :random
185
- r.scard(jobs_key)
186
- when :priority
187
- r.zcard(jobs_key)
188
- end
189
- end
190
- end
191
-
192
186
  def self.redis(&blk)
193
187
  Batch.redis &blk
194
188
  end
195
189
  delegate :redis, to: :class
196
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
+
197
200
  private
198
201
 
199
- 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)
200
203
  self.created_at = Time.now.utc.to_f
204
+ self.description = description
201
205
  self.order = order
202
206
  self.concurrency = concurrency
203
207
  self.clean_when_empty = clean_when_empty