sidekiq-batch 0.1.2 → 0.1.3

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
  SHA1:
3
- metadata.gz: 1fe037d539fb1492eb690004dfd48a03727beadf
4
- data.tar.gz: 3bad40562aa7abd37fc677ae4bf65c6393053d4b
3
+ metadata.gz: d41af0ded27d9233c6731d110bcc24d3c31a7e0f
4
+ data.tar.gz: 2a6a63c2b2bd2159cc0456970d013819344c4670
5
5
  SHA512:
6
- metadata.gz: d12d34ce2b8d9ee4d823f1c32406403322a402ab5fc4104c46b6b1cfffca94d12732ff50c09c238666fd333787b3e546eac412529a8c840860bc182ad57014a8
7
- data.tar.gz: bdbc01d26ef53401a420fc78baf0e715ff3d609a950b0987c318d17a5ac9ce67c6e97fb9005fff79a31e2f1e33eb16b9c26ffbb9fb09e6f645a9e3c4977aa8a6
6
+ metadata.gz: c297e8094b0de083870db5850dee5eacccc59cb30aa85961fd71bff9076119092b7721cf1502b9d2556372337a5d4bdb3a1bc1c2d1630d8949d06b1bc6b4a03c
7
+ data.tar.gz: 3c9d0365850379328118ed6a8f25b59a1e96c2fde7f4a7ae0ef8695024d589ba33de38d4344d321f480d2b41e74115fe8bae7d0c815efa8b55e2171f9bc5164b
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  /tmp/
10
10
  dump.rdb
11
11
  sidekiq-batch-*.gem
12
+ .DS_Store
data/Gemfile CHANGED
@@ -5,4 +5,5 @@ gemspec
5
5
  group :test do
6
6
  gem "simplecov"
7
7
  gem "codeclimate-test-reporter", "~> 1.0.0"
8
+ gem 'pry-byebug'
8
9
  end
data/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  # Sidekiq::Batch
6
6
 
7
+ [![Join the chat at https://gitter.im/breamware/sidekiq-batch](https://badges.gitter.im/breamware/sidekiq-batch.svg)](https://gitter.im/breamware/sidekiq-batch?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
8
+
7
9
  [![Gem Version](https://badge.fury.io/rb/sidekiq-batch.svg)][gem]
8
10
  [![Build Status](https://travis-ci.org/breamware/sidekiq-batch.svg?branch=master)][travis]
9
11
  [![Code Climate](https://codeclimate.com/github/breamware/sidekiq-batch/badges/gpa.svg)][codeclimate]
@@ -12,16 +12,15 @@ module Sidekiq
12
12
 
13
13
  BID_EXPIRE_TTL = 108_000
14
14
 
15
- attr_reader :bid, :description, :callback_queue
15
+ attr_reader :bid, :description, :callback_queue, :created_at
16
16
 
17
17
  def initialize(existing_bid = nil)
18
18
  @bid = existing_bid || SecureRandom.urlsafe_base64(10)
19
- Sidekiq.redis do |r|
20
- r.multi do
21
- r.hset("BID-#{bid}", 'created_at', Time.now)
22
- r.expire("BID-#{bid}", BID_EXPIRE_TTL)
23
- end
24
- end
19
+ @existing = !(!existing_bid || existing_bid.empty?) # Basically existing_bid.present?
20
+ @initialized = false
21
+ @created_at = Time.now.utc.to_f
22
+ @bidkey = "BID-" + @bid.to_s
23
+ @ready_to_queue = []
25
24
  end
26
25
 
27
26
  def description=(description)
@@ -36,11 +35,14 @@ module Sidekiq
36
35
 
37
36
  def on(event, callback, options = {})
38
37
  return unless %w(success complete).include?(event.to_s)
38
+ callback_key = "#{@bidkey}-callbacks-#{event}"
39
39
  Sidekiq.redis do |r|
40
40
  r.multi do
41
- r.hset("BID-#{bid}", "callback_#{event}", callback)
42
- r.hset("BID-#{bid}", "callback_#{event}_opts", options.to_json)
43
- r.expire("BID-#{bid}", BID_EXPIRE_TTL)
41
+ r.sadd(callback_key, JSON.unparse({
42
+ callback: callback,
43
+ opts: options
44
+ }))
45
+ r.expire(callback_key, BID_EXPIRE_TTL)
44
46
  end
45
47
  end
46
48
  end
@@ -48,11 +50,82 @@ module Sidekiq
48
50
  def jobs
49
51
  raise NoBlockGivenError unless block_given?
50
52
 
51
- Sidekiq.redis { |r| r.hincrby("BID-#{bid}", 'to_process', 1) }
52
- Thread.current[:bid] = bid
53
- yield
54
- Thread.current[:bid] = nil
55
- Sidekiq.redis { |r| r.hincrby("BID-#{bid}", 'to_process', -1) }
53
+ bid_data, Thread.current[:bid_data] = Thread.current[:bid_data], []
54
+
55
+ begin
56
+ if !@existing && !@initialized
57
+ parent_bid = Thread.current[:bid].bid if Thread.current[:bid]
58
+
59
+ Sidekiq.redis do |r|
60
+ r.multi do
61
+ r.hset(@bidkey, "created_at", @created_at)
62
+ r.hset(@bidkey, "parent_bid", parent_bid.to_s) if parent_bid
63
+ r.expire(@bidkey, BID_EXPIRE_TTL)
64
+ end
65
+ end
66
+
67
+ @initialized = true
68
+ end
69
+
70
+ @ready_to_queue = []
71
+
72
+ begin
73
+ parent = Thread.current[:bid]
74
+ Thread.current[:bid] = self
75
+ yield
76
+ ensure
77
+ Thread.current[:bid] = parent
78
+ end
79
+
80
+ return [] if @ready_to_queue.size == 0
81
+
82
+ Sidekiq.redis do |r|
83
+ r.multi do
84
+ if parent_bid
85
+ r.hincrby("BID-#{parent_bid}", "children", 1)
86
+ r.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
87
+ end
88
+
89
+ r.hincrby(@bidkey, "pending", @ready_to_queue.size)
90
+ r.hincrby(@bidkey, "total", @ready_to_queue.size)
91
+ r.expire(@bidkey, BID_EXPIRE_TTL)
92
+
93
+ r.sadd(@bidkey + "-jids", @ready_to_queue)
94
+ r.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
95
+ end
96
+ end
97
+
98
+ @ready_to_queue
99
+ ensure
100
+ Thread.current[:bid_data] = bid_data
101
+ end
102
+ end
103
+
104
+ def increment_job_queue(jid)
105
+ @ready_to_queue << jid
106
+ end
107
+
108
+ def invalidate_all
109
+ Sidekiq.redis do |r|
110
+ r.setex("invalidated-bid-#{bid}", BID_EXPIRE_TTL, 1)
111
+ end
112
+ end
113
+
114
+ def parent_bid
115
+ Sidekiq.redis do |r|
116
+ r.hget(@bidkey, "parent_bid")
117
+ end
118
+ end
119
+
120
+ def parent
121
+ if parent_bid
122
+ Sidekiq::Batch.new(parent_bid)
123
+ end
124
+ end
125
+
126
+ def valid?(batch = self)
127
+ valid = !Sidekiq.redis { |r| r.exists("invalidated-bid-#{batch.bid}") }
128
+ batch.parent ? valid && valid?(batch.parent) : valid
56
129
  end
57
130
 
58
131
  private
@@ -60,57 +133,88 @@ module Sidekiq
60
133
  def persist_bid_attr(attribute, value)
61
134
  Sidekiq.redis do |r|
62
135
  r.multi do
63
- r.hset("BID-#{bid}", attribute, value)
64
- r.expire("BID-#{bid}", BID_EXPIRE_TTL)
136
+ r.hset(@bidkey, attribute, value)
137
+ r.expire(@bidkey, BID_EXPIRE_TTL)
65
138
  end
66
139
  end
67
140
  end
68
141
 
69
142
  class << self
70
143
  def process_failed_job(bid, jid)
71
- to_process = Sidekiq.redis do |r|
144
+ _, pending, failed, children, complete = Sidekiq.redis do |r|
72
145
  r.multi do
73
146
  r.sadd("BID-#{bid}-failed", jid)
147
+
148
+ r.hincrby("BID-#{bid}", "pending", 0)
74
149
  r.scard("BID-#{bid}-failed")
75
- r.hget("BID-#{bid}", 'to_process')
150
+ r.hincrby("BID-#{bid}", "children", 0)
151
+ r.scard("BID-#{bid}-complete")
152
+
76
153
  r.expire("BID-#{bid}-failed", BID_EXPIRE_TTL)
77
154
  end
78
155
  end
79
- if to_process[2].to_i == to_process[1].to_i
80
- Callback.call_if_needed(:complete, bid)
81
- end
156
+
157
+ enqueue_callbacks(:complete, bid) if pending.to_i == failed.to_i && children == complete
82
158
  end
83
159
 
84
- def process_successful_job(bid)
85
- out = Sidekiq.redis do |r|
160
+ def process_successful_job(bid, jid)
161
+ failed, pending, children, complete, success, total, parent_bid = Sidekiq.redis do |r|
86
162
  r.multi do
87
- r.hincrby("BID-#{bid}", 'to_process', -1)
88
163
  r.scard("BID-#{bid}-failed")
89
- r.hincrby("BID-#{bid}", 'pending', -1)
164
+ r.hincrby("BID-#{bid}", "pending", -1)
165
+ r.hincrby("BID-#{bid}", "children", 0)
166
+ r.scard("BID-#{bid}-complete")
167
+ r.scard("BID-#{bid}-success")
168
+ r.hget("BID-#{bid}", "total")
169
+ r.hget("BID-#{bid}", "parent_bid")
170
+
171
+ r.srem("BID-#{bid}-failed", jid)
172
+ r.srem("BID-#{bid}-jids", jid)
90
173
  r.expire("BID-#{bid}", BID_EXPIRE_TTL)
91
174
  end
92
175
  end
93
176
 
94
177
  puts "processed process_successful_job"
95
- Callback.call_if_needed(:complete, bid) if out[1].to_i == out[0].to_i
96
- Callback.call_if_needed(:success, bid) if out[0].to_i.zero?
178
+
179
+ enqueue_callbacks(:complete, bid) if pending.to_i == failed.to_i && children == complete
180
+ enqueue_callbacks(:success, bid) if pending.to_i.zero? && children == success
97
181
  end
98
182
 
99
- def cleanup_redis(bid)
100
- Sidekiq.redis do |r|
101
- r.del("BID-#{bid}",
102
- "BID-#{bid}-failed")
183
+ def enqueue_callbacks(event, bid)
184
+ batch_key = "BID-#{bid}"
185
+ callback_key = "#{batch_key}-callbacks-#{event}"
186
+ needed, _, callbacks, queue, parent_bid = Sidekiq.redis do |r|
187
+ r.multi do
188
+ r.hget(batch_key, event)
189
+ r.hset(batch_key, event, true)
190
+ r.smembers(callback_key)
191
+ r.hget(batch_key, "callback_queue")
192
+ r.hget(batch_key, "parent_bid")
193
+ end
194
+ end
195
+ return if needed == 'true'
196
+
197
+ begin
198
+ parent_bid = !parent_bid || parent_bid.empty? ? nil : parent_bid # Basically parent_bid.blank?
199
+ Sidekiq::Client.push_bulk(
200
+ 'class' => Sidekiq::Batch::Callback::Worker,
201
+ 'args' => callbacks.reduce([]) do |memo, jcb|
202
+ cb = Sidekiq.load_json(jcb)
203
+ memo << [cb['callback'], event, cb['opts'], bid, parent_bid]
204
+ end,
205
+ 'queue' => queue ||= 'default'
206
+ ) unless callbacks.empty?
207
+ ensure
208
+ cleanup_redis(bid) if event == :success
103
209
  end
104
210
  end
105
211
 
106
- def increment_job_queue(bid)
212
+ def cleanup_redis(bid)
107
213
  Sidekiq.redis do |r|
108
- r.multi do
109
- %w(to_process pending total).each do |c|
110
- r.hincrby("BID-#{bid}", c, 1)
111
- end
112
- r.expire("BID-#{bid}", BID_EXPIRE_TTL)
113
- end
214
+ r.del("BID-#{bid}",
215
+ "BID-#{bid}-callbacks-complete",
216
+ "BID-#{bid}-callbacks-success",
217
+ "BID-#{bid}-failed")
114
218
  end
115
219
  end
116
220
  end
@@ -4,38 +4,64 @@ module Sidekiq
4
4
  class Worker
5
5
  include Sidekiq::Worker
6
6
 
7
- def perform(clazz, event, opts, bid)
7
+ def perform(clazz, event, opts, bid, parent_bid)
8
8
  return unless %w(success complete).include?(event)
9
9
  clazz, method = clazz.split("#") if (clazz.class == String && clazz.include?("#"))
10
10
  method = "on_#{event}" if method.nil?
11
- clazz.constantize.new.send(method, Sidekiq::Batch::Status.new(bid), opts) rescue nil
11
+ status = Sidekiq::Batch::Status.new(bid)
12
+ clazz.constantize.new.send(method, status, opts) rescue nil
13
+
14
+ send(event.to_sym, bid, status, parent_bid)
12
15
  end
13
- end
14
16
 
15
- class << self
16
- def call_if_needed(event, bid)
17
- needed = Sidekiq.redis do |r|
18
- r.multi do
19
- r.hget("BID-#{bid}", event)
20
- r.hset("BID-#{bid}", event, true)
17
+
18
+ def success(bid, status, parent_bid)
19
+ if (parent_bid)
20
+ _, _, success, pending, children = Sidekiq.redis do |r|
21
+ r.multi do
22
+ r.sadd("BID-#{parent_bid}-success", bid)
23
+ r.expire("BID-#{parent_bid}-success", Sidekiq::Batch::BID_EXPIRE_TTL)
24
+ r.scard("BID-#{parent_bid}-success")
25
+ r.hincrby("BID-#{parent_bid}", "pending", 0)
26
+ r.hincrby("BID-#{parent_bid}", "children", 0)
27
+ end
28
+ end
29
+
30
+ Batch.enqueue_callbacks(:success, parent_bid) if pending.to_i.zero? && children == success
31
+ end
32
+
33
+ Sidekiq.redis do |r|
34
+ r.del "BID-#{bid}-success", "BID-#{bid}-complete", "BID-#{bid}-jids", "BID-#{bid}-failed"
35
+ end
36
+ end
37
+
38
+ def complete(bid, status, parent_bid)
39
+ if (parent_bid)
40
+ _, complete, pending, children, failure = Sidekiq.redis do |r|
41
+ r.multi do
42
+ r.sadd("BID-#{parent_bid}-complete", bid)
43
+ r.scard("BID-#{parent_bid}-complete")
44
+ r.hincrby("BID-#{parent_bid}", "pending", 0)
45
+ r.hincrby("BID-#{parent_bid}", "children", 0)
46
+ r.hlen("BID-#{parent_bid}-failed")
47
+ end
21
48
  end
49
+
50
+ Batch.enqueue_callbacks(:complete, parent_bid) if complete == children && pending == failure
22
51
  end
23
- return if 'true' == needed[0]
24
- callback, opts, queue = Sidekiq.redis do |r|
25
- r.hmget("BID-#{bid}",
26
- "callback_#{event}", "callback_#{event}_opts",
27
- 'callback_queue')
52
+
53
+ pending, children, success = Sidekiq.redis do |r|
54
+ r.multi do
55
+ r.hincrby("BID-#{bid}", "pending", 0)
56
+ r.hincrby("BID-#{bid}", "children", 0)
57
+ r.scard("BID-#{bid}-success")
58
+ end
28
59
  end
29
- return unless callback
30
- opts = JSON.parse(opts) if opts
31
- opts ||= {}
32
- queue ||= 'default'
33
- Sidekiq::Client.push('class' => Sidekiq::Batch::Callback::Worker,
34
- 'args' => [callback, event, opts, bid],
35
- 'queue' => queue)
36
- ensure
37
- Sidekiq::Batch.cleanup_redis(bid) if event == :success
60
+
61
+ Batch.enqueue_callbacks(:success, bid) if pending.to_i.zero? && children == success
62
+
38
63
  end
64
+
39
65
  end
40
66
  end
41
67
  end
@@ -0,0 +1,15 @@
1
+ module Sidekiq::Batch::Extension
2
+ module Worker
3
+ def bid
4
+ Thread.current[:bid]
5
+ end
6
+
7
+ def batch
8
+ Sidekiq::Batch.new(Thread.current[:bid].bid) if Thread.current[:bid]
9
+ end
10
+
11
+ def valid_within_batch?
12
+ batch.valid?
13
+ end
14
+ end
15
+ end
@@ -1,10 +1,12 @@
1
+ require_relative 'extension/worker'
2
+
1
3
  module Sidekiq
2
4
  class Batch
3
5
  module Middleware
4
6
  class ClientMiddleware
5
7
  def call(_worker, msg, _queue, _redis_pool = nil)
6
- if (bid = Thread.current[:bid])
7
- Batch.increment_job_queue(bid) if (msg[:bid] = bid)
8
+ if (batch = Thread.current[:bid])
9
+ batch.increment_job_queue(msg['jid']) if (msg[:bid] = batch.bid)
8
10
  end
9
11
  yield
10
12
  end
@@ -14,10 +16,10 @@ module Sidekiq
14
16
  def call(_worker, msg, _queue)
15
17
  if (bid = msg['bid'])
16
18
  begin
17
- Thread.current[:bid] = bid
19
+ Thread.current[:bid] = Sidekiq::Batch.new(bid)
18
20
  yield
19
21
  Thread.current[:bid] = nil
20
- Batch.process_successful_job(bid)
22
+ Batch.process_successful_job(bid, msg['jid'])
21
23
  rescue
22
24
  Batch.process_failed_job(bid, msg['jid'])
23
25
  raise
@@ -44,12 +46,7 @@ module Sidekiq
44
46
  chain.add Sidekiq::Batch::Middleware::ServerMiddleware
45
47
  end
46
48
  end
47
- Sidekiq::Worker.send(:define_method, 'bid') do
48
- Thread.current[:bid]
49
- end
50
- Sidekiq::Worker.send(:define_method, 'batch') do
51
- Sidekiq::Batch.new(Thread.current[:bid]) if Thread.current[:bid]
52
- end
49
+ Sidekiq::Worker.send(:include, Sidekiq::Batch::Extension::Worker)
53
50
  end
54
51
  end
55
52
  end
@@ -27,6 +27,10 @@ module Sidekiq
27
27
  Sidekiq.redis { |r| r.hget("BID-#{bid}", 'total') }.to_i
28
28
  end
29
29
 
30
+ def parent_bid
31
+ Sidekiq.redis { |r| r.hget("BID-#{bid}", "parent_bid") }
32
+ end
33
+
30
34
  def failure_info
31
35
  Sidekiq.redis { |r| r.smembers("BID-#{bid}-failed") } || []
32
36
  end
@@ -35,6 +39,10 @@ module Sidekiq
35
39
  'true' == Sidekiq.redis { |r| r.hget("BID-#{bid}", 'complete') }
36
40
  end
37
41
 
42
+ def child_count
43
+ Sidekiq.redis { |r| r.hget("BID-#{bid}", 'children') }.to_i
44
+ end
45
+
38
46
  def data
39
47
  {
40
48
  total: total,
@@ -42,7 +50,8 @@ module Sidekiq
42
50
  pending: pending,
43
51
  created_at: created_at,
44
52
  complete: complete?,
45
- failure_info: failure_info
53
+ failure_info: failure_info,
54
+ parent_bid: parent_bid
46
55
  }
47
56
  end
48
57
  end
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  class Batch
3
- VERSION = '0.1.2'.freeze
3
+ VERSION = '0.1.3'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-batch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcin Naglik
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-29 00:00:00.000000000 Z
11
+ date: 2017-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -97,6 +97,7 @@ files:
97
97
  - Rakefile
98
98
  - lib/sidekiq/batch.rb
99
99
  - lib/sidekiq/batch/callback.rb
100
+ - lib/sidekiq/batch/extension/worker.rb
100
101
  - lib/sidekiq/batch/middleware.rb
101
102
  - lib/sidekiq/batch/status.rb
102
103
  - lib/sidekiq/batch/version.rb
@@ -121,7 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
122
  version: '0'
122
123
  requirements: []
123
124
  rubyforge_project:
124
- rubygems_version: 2.5.1
125
+ rubygems_version: 2.5.2
125
126
  signing_key:
126
127
  specification_version: 4
127
128
  summary: Sidekiq Batch Jobs