sidekiq-batch 0.1.2 → 0.1.3

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
  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