canvas_sync 0.17.3.beta1 → 0.17.5.beta2

Sign up to get free protection for your applications and to get access to all the features.
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