resque-batched-job 1.3.0 → 1.4.0

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.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Resque Batched Job
1
+ # Resque Batched Job [![Build Status](https://secure.travis-ci.org/drfeelngood/resque-batched-job.png)](http://travis-ci.org/drfeelngood/resque-batched-job)
2
2
 
3
3
  A [Resque](http://github.com/defunkt/resque) plugin. Requires Resque >= 1.10.0
4
4
 
@@ -0,0 +1,72 @@
1
+ module Resque
2
+ module Plugins
3
+ module BatchedJobSupport
4
+
5
+ # Checks the size of the batched job list and returns true if the list is
6
+ # empty or if the key does not exist.
7
+ #
8
+ # @param batch_id (see Resque::Plugins::BatchedJob#batch)
9
+ def batch_complete?(batch_id)
10
+ mutex(batch_id) do |bid|
11
+ redis.llen(bid) == 0
12
+ end
13
+ end
14
+
15
+ # Check to see if the Redis key exists.
16
+ #
17
+ # @param batch_id (see Resque::Plugins::BatchedJob#batch)
18
+ def batch_exist?(batch_id)
19
+ mutex(batch_id) do |bid|
20
+ redis.exists(bid)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ # Lock a batch key before executing Redis commands. This will ensure
27
+ # no race conditions occur when modifying batch information. Here is
28
+ # an example of how this works. See http://redis.io/commands/setnx for
29
+ # more information. (fixes #4) (closes #5)
30
+ #
31
+ # * Job2 sends SETNX batch:123:lock in order to aquire a lock.
32
+ # * Job1 still has the key locked, so Job2 continues into the loop.
33
+ # * Job2 sends GET to aquire the lock timestamp.
34
+ # * If the timestamp does not exist (Job1 released the lock), Job2
35
+ # attemps to start from the beginning again.
36
+ # * If the timestamp exists and has not expired, Job2 sleeps for a
37
+ # moment and then retries from the start.
38
+ # * If the timestamp exists and has expired, Job2 sends GETSET to aquire
39
+ # a lock. This returns the previous value of the lock.
40
+ # * If the previous timestamp has not expired, another process was faster
41
+ # and aquired the lock. This means Job2 has to start from the beginnig.
42
+ # * If the previous timestamp is still expired the lock has been set and
43
+ # processing can continue safely
44
+ #
45
+ # @param id (see Resque::Plugins::BatchedJob#batch)
46
+ # @yield [bid] Yields the current batch id.
47
+ # @yieldparam [String] The current batch id.
48
+ def mutex(id, &block)
49
+ is_expired = lambda do |locked_at|
50
+ locked_at.to_f < Time.now.to_f
51
+ end
52
+ bid = batch(id)
53
+ _key_ = "#{bid}:lock"
54
+
55
+ until redis.setnx(_key_, Time.now.to_f + 0.5)
56
+ next unless timestamp = redis.get(_key_)
57
+
58
+ unless is_expired.call(timestamp)
59
+ sleep(0.1)
60
+ next
61
+ end
62
+
63
+ break unless timestamp = redis.getset(_key_, Time.now.to_f + 0.5)
64
+ break if is_expired.call(timestamp)
65
+ end
66
+ yield(bid)
67
+ ensure
68
+ redis.del(_key_)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,7 +1,7 @@
1
1
  module Resque
2
2
  module Plugins
3
3
  module BatchedJob
4
- VERSION = '1.3.0'
4
+ VERSION = '1.4.0'
5
5
  end
6
6
  end
7
- end
7
+ end
@@ -45,9 +45,7 @@ module Resque
45
45
  #
46
46
  # @param id (see Resque::Plugins::BatchedJob#after_enqueue_batch)
47
47
  def after_perform_batch(id, *args)
48
- remove_batched_job(id, *args)
49
-
50
- if batch_complete?(id)
48
+ if remove_batched_job(id, *args) == 0
51
49
  after_batch_hooks = Resque::Plugin.after_batch_hooks(self)
52
50
  after_batch_hooks.each do |hook|
53
51
  send(hook, id, *args)
@@ -60,7 +58,7 @@ module Resque
60
58
  #
61
59
  # @param id (see Resque::Plugins::BatchedJob#batch)
62
60
  def batch_complete?(id)
63
- mutex(id) do |bid|
61
+ mutex(id) do |bid|
64
62
  redis.llen(bid) == 0
65
63
  end
66
64
  end
@@ -69,7 +67,7 @@ module Resque
69
67
  #
70
68
  # @param id (see Resque::Plugins::BatchedJob#batch)
71
69
  def batch_exist?(id)
72
- mutex(id) do |bid|
70
+ mutex(id) do |bid|
73
71
  redis.exists(bid)
74
72
  end
75
73
  end
@@ -80,6 +78,7 @@ module Resque
80
78
  def remove_batched_job(id, *args)
81
79
  mutex(id) do |bid|
82
80
  redis.lrem(bid, 1, encode(:class => self.name, :args => args))
81
+ redis.llen(bid)
83
82
  end
84
83
  end
85
84
 
@@ -96,13 +95,13 @@ module Resque
96
95
  # no race conditions occur when modifying batch information. Here is
97
96
  # an example of how this works. See http://redis.io/commands/setnx for
98
97
  # more information. (fixes #4) (closes #5)
99
- #
98
+ #
100
99
  # * Job2 sends SETNX batch:123:lock in order to aquire a lock.
101
100
  # * Job1 still has the key locked, so Job2 continues into the loop.
102
101
  # * Job2 sends GET to aquire the lock timestamp.
103
- # * If the timestamp does not exist (Job1 released the lock), Job2
102
+ # * If the timestamp does not exist (Job1 released the lock), Job2
104
103
  # attemps to start from the beginning again.
105
- # * If the timestamp exists and has not expired, Job2 sleeps for a
104
+ # * If the timestamp exists and has not expired, Job2 sleeps for a
106
105
  # moment and then retries from the start.
107
106
  # * If the timestamp exists and has expired, Job2 sends GETSET to aquire
108
107
  # a lock. This returns the previous value of the lock.
@@ -46,7 +46,7 @@ class BatchedJobTest < Test::Unit::TestCase
46
46
  # Make sure the after_batch hook is fired
47
47
  def test_batch_hook
48
48
  assert_equal(false, Job.batch_exist?(@batch_id))
49
-
49
+
50
50
  assert_nothing_raised do
51
51
  5.times { Resque.enqueue(Job, @batch_id, "arg#{rand(100)}") }
52
52
  end
@@ -129,6 +129,11 @@ class BatchedJobTest < Test::Unit::TestCase
129
129
  end
130
130
 
131
131
  def test_remove_batched_job
132
+ Resque.enqueue(JobWithoutArgs, @batch_id)
133
+ Resque.enqueue(JobWithoutArgs, @batch_id)
134
+ assert_equal(1, JobWithoutArgs.remove_batched_job(@batch_id))
135
+ assert_equal(0, JobWithoutArgs.remove_batched_job(@batch_id))
136
+
132
137
  Resque.enqueue(JobWithoutArgs, @batch_id)
133
138
 
134
139
  assert_nothing_raised do
@@ -159,4 +164,4 @@ class BatchedJobTest < Test::Unit::TestCase
159
164
  Resque.redis
160
165
  end
161
166
 
162
- end
167
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-batched-job
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-20 00:00:00.000000000 Z
12
+ date: 2012-03-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: resque
16
- requirement: &70171540725400 !ruby/object:Gem::Requirement
16
+ requirement: &70106096870560 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 1.10.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70171540725400
24
+ version_requirements: *70106096870560
25
25
  description: ! " Resque plugin for batching jobs. When a batch/group of jobs are
26
26
  complete, \nadditional work can be performed usings batch hooks.\n"
27
27
  email: dan@dj-agiledev.com
@@ -33,6 +33,7 @@ files:
33
33
  - Rakefile
34
34
  - README.md
35
35
  - lib/resque/batched_job.rb
36
+ - lib/resque/plugins/batched_job/support.rb
36
37
  - lib/resque/plugins/batched_job/tasks.rb
37
38
  - lib/resque/plugins/batched_job/version.rb
38
39
  - lib/resque/plugins/batched_job.rb