resque-batched-job 1.5.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/resque/plugins/batched_job/version.rb +1 -1
- data/lib/resque/plugins/batched_job.rb +26 -0
- metadata +21 -18
- data/README.md +0 -33
- data/Rakefile +0 -52
- data/lib/resque/plugins/batched_job/support.rb +0 -72
- data/lib/resque/plugins/batched_job/tasks.rb +0 -6
- data/test/test_batched_job.rb +0 -185
- data/test/test_helper.rb +0 -34
@@ -46,6 +46,7 @@ module Resque
|
|
46
46
|
# @param id (see Resque::Plugins::BatchedJob#after_enqueue_batch)
|
47
47
|
def after_perform_batch(id, *args)
|
48
48
|
if remove_batched_job(id, *args) == 0
|
49
|
+
|
49
50
|
after_batch_hooks = Resque::Plugin.after_batch_hooks(self)
|
50
51
|
after_batch_hooks.each do |hook|
|
51
52
|
send(hook, id, *args)
|
@@ -96,6 +97,31 @@ module Resque
|
|
96
97
|
after_perform_batch(id, *args)
|
97
98
|
end
|
98
99
|
|
100
|
+
# Build a collection of Resque::Job objects that represent each job in a
|
101
|
+
# batch of the same class.
|
102
|
+
#
|
103
|
+
# @param id (see Resque::Plugins::BatchedJob#remove_batched_job)
|
104
|
+
# @returns [Array] Collection of Resque::Job objects
|
105
|
+
def batched_jobs(id)
|
106
|
+
bid = batch(id)
|
107
|
+
regexp = /\A\{\"class\":\"#{self.name}\",\"args\":\[/
|
108
|
+
redis.lrange(bid, 0, redis.llen(bid)-1).grep(regexp).map do |string|
|
109
|
+
payload = decode(string)
|
110
|
+
payload['args'].unshift(id)
|
111
|
+
Resque::Job.new(@queue, payload)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Collect and recreate all jobs for the given batch.
|
116
|
+
#
|
117
|
+
# @param id (see Resque::Plugins::BatchedJob#remove_batched_job)
|
118
|
+
# @returns [Integer] Number of jobs recreated.
|
119
|
+
def recreate_batched_jobs(id)
|
120
|
+
batched_jobs(id).each do |job|
|
121
|
+
job.recreate
|
122
|
+
end.size
|
123
|
+
end
|
124
|
+
|
99
125
|
private
|
100
126
|
|
101
127
|
# Lock a batch key before executing Redis commands. This will ensure
|
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.
|
4
|
+
version: 1.7.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-
|
12
|
+
date: 2012-08-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: resque
|
16
|
-
requirement: &
|
16
|
+
requirement: &70188523453360 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 1.10.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70188523453360
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: turn
|
27
|
-
requirement: &
|
27
|
+
requirement: &70188523451760 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,18 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70188523451760
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &70188523449820 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70188523449820
|
36
47
|
description: ! " Resque plugin for batching jobs. When a batch/group of jobs are
|
37
48
|
complete, \nadditional work can be performed usings batch hooks.\n"
|
38
49
|
email: dan@dj-agiledev.com
|
@@ -41,16 +52,10 @@ extensions: []
|
|
41
52
|
extra_rdoc_files: []
|
42
53
|
files:
|
43
54
|
- LICENSE
|
44
|
-
-
|
45
|
-
- README.md
|
55
|
+
- lib/resque-batched-job.rb
|
46
56
|
- lib/resque/batched_job.rb
|
47
|
-
- lib/resque/plugins/batched_job/support.rb
|
48
|
-
- lib/resque/plugins/batched_job/tasks.rb
|
49
|
-
- lib/resque/plugins/batched_job/version.rb
|
50
57
|
- lib/resque/plugins/batched_job.rb
|
51
|
-
- lib/resque
|
52
|
-
- test/test_batched_job.rb
|
53
|
-
- test/test_helper.rb
|
58
|
+
- lib/resque/plugins/batched_job/version.rb
|
54
59
|
homepage: https://github.com/drfeelngood/resque-batched-job
|
55
60
|
licenses: []
|
56
61
|
post_install_message:
|
@@ -71,11 +76,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
76
|
version: '0'
|
72
77
|
requirements: []
|
73
78
|
rubyforge_project:
|
74
|
-
rubygems_version: 1.8.
|
79
|
+
rubygems_version: 1.8.16
|
75
80
|
signing_key:
|
76
81
|
specification_version: 3
|
77
82
|
summary: Resque plugin
|
78
|
-
test_files:
|
79
|
-
- test/test_batched_job.rb
|
80
|
-
- test/test_helper.rb
|
83
|
+
test_files: []
|
81
84
|
has_rdoc:
|
data/README.md
DELETED
@@ -1,33 +0,0 @@
|
|
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
|
-
|
3
|
-
A [Resque](http://github.com/defunkt/resque) plugin. Requires Resque >= 1.10.0
|
4
|
-
|
5
|
-
This plugin adds the ability to batch jobs and run additional hooks after the
|
6
|
-
last job in a batch is performed. Using the '*after_enqueue*' hook, the job
|
7
|
-
is encoded and stored in a Redis List identified by the batch id provided. By default,
|
8
|
-
the batch keys look like '*batch:#{id}*'. After each job is performed, it's removed
|
9
|
-
from the batch list. If the last job performed happens to be the last in the list,
|
10
|
-
additional hooks are executed. These hooks are prefixed with '*after_batch*'.
|
11
|
-
|
12
|
-
## Installation
|
13
|
-
|
14
|
-
$ gem install resque-batched-job
|
15
|
-
|
16
|
-
## Example
|
17
|
-
|
18
|
-
```ruby
|
19
|
-
require 'resque/batched_job'
|
20
|
-
|
21
|
-
module Job
|
22
|
-
extend Resque::Plugins::BatchedJob
|
23
|
-
|
24
|
-
def self.perform(bid, *args)
|
25
|
-
prime(bid, args)
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.after_batch_heavy_lifting(bid, *args)
|
29
|
-
heavy_lifting(bid)
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
```
|
data/Rakefile
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
require 'rake/testtask'
|
4
|
-
|
5
|
-
require File.dirname(__FILE__) + '/lib/resque/plugins/batched_job/version'
|
6
|
-
|
7
|
-
task :default => :test
|
8
|
-
|
9
|
-
Rake::TestTask.new do |task|
|
10
|
-
task.pattern = 'test/test_*.rb'
|
11
|
-
task.verbose = true
|
12
|
-
end
|
13
|
-
|
14
|
-
begin
|
15
|
-
require 'yard'
|
16
|
-
YARD::Rake::YardocTask.new do |t|
|
17
|
-
t.files = ['lib/**/*.rb', '-', 'README.md']
|
18
|
-
end
|
19
|
-
rescue LoadError
|
20
|
-
end
|
21
|
-
|
22
|
-
desc "Publish RubyGem and source."
|
23
|
-
task :publish => [:build, :tag] do
|
24
|
-
sh "git push origin v#{Resque::Plugins::BatchedJob::VERSION}"
|
25
|
-
sh "git push origin master"
|
26
|
-
sh "gem push resque-batched-job-#{Resque::Plugins::BatchedJob::VERSION}.gem"
|
27
|
-
end
|
28
|
-
|
29
|
-
desc "Tag project with current version."
|
30
|
-
task :tag do
|
31
|
-
sh "git tag v#{Resque::Plugins::BatchedJob::VERSION}"
|
32
|
-
end
|
33
|
-
|
34
|
-
desc "Build resque-batched-job RubyGem."
|
35
|
-
task :build do
|
36
|
-
sh "gem build resque-batched-job.gemspec"
|
37
|
-
end
|
38
|
-
|
39
|
-
desc "Install current resque-batched-job RubyGem."
|
40
|
-
task :install => :build do
|
41
|
-
sh "gem install --local resque-batched-job-#{Resque::Plugins::BatchedJob::VERSION}.gem"
|
42
|
-
end
|
43
|
-
|
44
|
-
desc "View changelog"
|
45
|
-
task :changelog do
|
46
|
-
tags = `git tag`.split("\n").reverse
|
47
|
-
|
48
|
-
tags.each_slice(2) do |tags|
|
49
|
-
puts "========== #{tags[1]}..#{tags[0]} =========="
|
50
|
-
`git log --pretty=format:'%h : %s' --graph #{tags[1]}..#{tags[0]}`
|
51
|
-
end
|
52
|
-
end
|
@@ -1,72 +0,0 @@
|
|
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
|
data/test/test_batched_job.rb
DELETED
@@ -1,185 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/test_helper'
|
2
|
-
|
3
|
-
class BatchedJobTest < Test::Unit::TestCase
|
4
|
-
|
5
|
-
def setup
|
6
|
-
$batch_complete = false
|
7
|
-
@batch_id = :foo
|
8
|
-
@batch = "batch:#{@batch_id}"
|
9
|
-
end
|
10
|
-
|
11
|
-
def teardown
|
12
|
-
redis.del(@batch)
|
13
|
-
redis.del("queue:test")
|
14
|
-
redis.del("#{@batch}:lock")
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_list
|
18
|
-
assert_nothing_raised do
|
19
|
-
Resque::Plugin.lint(Resque::Plugins::BatchedJob)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_encoding
|
24
|
-
Resque.enqueue(Job, @batch_id, 123)
|
25
|
-
Resque.enqueue(JobWithoutArgs, @batch_id)
|
26
|
-
|
27
|
-
assert_equal("{\"class\":\"Job\",\"args\":[123]}", redis.lindex(@batch, 0))
|
28
|
-
assert_equal("{\"class\":\"JobWithoutArgs\",\"args\":[]}", redis.lindex(@batch, 1))
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_batch_key
|
32
|
-
assert_nothing_raised do
|
33
|
-
Resque.enqueue(Job, @batch_id, 'foobar')
|
34
|
-
end
|
35
|
-
assert_equal(@batch, Job.batch(@batch_id))
|
36
|
-
end
|
37
|
-
|
38
|
-
# Ensure the length of the Redis list matches the number of jobs we enqueue.
|
39
|
-
def test_batch_size
|
40
|
-
assert_nothing_raised do
|
41
|
-
5.times { Resque.enqueue(Job, @batch_id, "arg#{rand(100)}") }
|
42
|
-
end
|
43
|
-
assert_equal(5, redis.llen(@batch))
|
44
|
-
end
|
45
|
-
|
46
|
-
# Make sure the after_batch hook is fired
|
47
|
-
def test_batch_hooks
|
48
|
-
assert_equal(false, Job.batch_exist?(@batch_id))
|
49
|
-
|
50
|
-
assert_nothing_raised do
|
51
|
-
5.times { Resque.enqueue(Job, @batch_id, "arg#{rand(100)}") }
|
52
|
-
end
|
53
|
-
|
54
|
-
assert_equal(false, $batch_complete)
|
55
|
-
assert_equal(false, Job.batch_complete?(@batch_id))
|
56
|
-
assert(Job.batch_exist?(@batch_id))
|
57
|
-
|
58
|
-
assert_nothing_raised do
|
59
|
-
4.times { Resque.reserve(:test).perform }
|
60
|
-
end
|
61
|
-
|
62
|
-
assert_equal(false, $batch_complete)
|
63
|
-
assert_equal(false, Job.batch_complete?(@batch_id))
|
64
|
-
assert(Job.batch_exist?(@batch_id))
|
65
|
-
|
66
|
-
assert_nothing_raised do
|
67
|
-
Resque.reserve(:test).perform
|
68
|
-
end
|
69
|
-
|
70
|
-
assert($batch_complete)
|
71
|
-
assert(Job.batch_complete?(@batch_id))
|
72
|
-
assert_equal(false, Job.batch_exist?(@batch_id))
|
73
|
-
end
|
74
|
-
|
75
|
-
# Test that jobs with identical args behave properly.
|
76
|
-
def test_duplicate_args
|
77
|
-
assert_equal(false, Job.batch_exist?(@batch_id))
|
78
|
-
|
79
|
-
assert_nothing_raised do
|
80
|
-
5.times { Resque.enqueue(JobWithoutArgs, @batch_id) }
|
81
|
-
end
|
82
|
-
|
83
|
-
assert_equal(false, $batch_complete)
|
84
|
-
assert_equal(false, Job.batch_complete?(@batch_id))
|
85
|
-
assert(Job.batch_exist?(@batch_id))
|
86
|
-
|
87
|
-
assert_nothing_raised do
|
88
|
-
2.times { Resque.reserve(:test).perform }
|
89
|
-
end
|
90
|
-
|
91
|
-
assert_equal(false, $batch_complete)
|
92
|
-
assert_equal(false, Job.batch_complete?(@batch_id))
|
93
|
-
assert(Job.batch_exist?(@batch_id))
|
94
|
-
|
95
|
-
assert_nothing_raised do
|
96
|
-
3.times { Resque.reserve(:test).perform }
|
97
|
-
end
|
98
|
-
|
99
|
-
assert($batch_complete)
|
100
|
-
assert(Job.batch_complete?(@batch_id))
|
101
|
-
assert_equal(false, Job.batch_exist?(@batch_id))
|
102
|
-
end
|
103
|
-
|
104
|
-
# Make sure the block is executed and the lock is removed.
|
105
|
-
def test_mutex
|
106
|
-
Job.send(:mutex, @batch_id) do
|
107
|
-
assert(true)
|
108
|
-
end
|
109
|
-
assert_equal(false, redis.exists("#{@batch}:lock"))
|
110
|
-
end
|
111
|
-
|
112
|
-
# Make sure no race conditions occur.
|
113
|
-
def test_locking
|
114
|
-
threads = []
|
115
|
-
x, y = 10, 5
|
116
|
-
|
117
|
-
x.times do
|
118
|
-
threads << Thread.new do
|
119
|
-
y.times do
|
120
|
-
Job.send(:mutex, @batch_id) do
|
121
|
-
redis.incr(@batch)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
end
|
125
|
-
end
|
126
|
-
threads.each { |t| t.join }
|
127
|
-
|
128
|
-
assert_equal(x * y, Integer(redis.get(@batch)))
|
129
|
-
end
|
130
|
-
|
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
|
-
|
137
|
-
Resque.enqueue(JobWithoutArgs, @batch_id)
|
138
|
-
|
139
|
-
assert_nothing_raised do
|
140
|
-
JobWithoutArgs.remove_batched_job(@batch_id)
|
141
|
-
end
|
142
|
-
assert(Job.batch_complete?(@batch_id))
|
143
|
-
assert_equal(false, Job.batch_exist?(@batch_id))
|
144
|
-
assert_equal(false, $batch_complete)
|
145
|
-
|
146
|
-
Resque.enqueue(JobWithoutArgs, @batch_id)
|
147
|
-
|
148
|
-
assert_nothing_raised do
|
149
|
-
JobWithoutArgs.remove_batched_job!(@batch_id)
|
150
|
-
end
|
151
|
-
assert($batch_complete)
|
152
|
-
assert(Job.batch_complete?(@batch_id))
|
153
|
-
assert_equal(false, Job.batch_exist?(@batch_id))
|
154
|
-
end
|
155
|
-
|
156
|
-
def test_enqueue_batched_job
|
157
|
-
Resque.enqueue_batched_job(JobWithoutArgs, @batch_id)
|
158
|
-
assert(Job.batch_exist?(@batch_id))
|
159
|
-
end
|
160
|
-
|
161
|
-
def test_dequeue
|
162
|
-
assert_nothing_raised do
|
163
|
-
2.times do
|
164
|
-
Resque.enqueue(JobWithoutArgs, @batch_id)
|
165
|
-
Resque.enqueue(Job, @batch_id, "foo")
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
Resque.dequeue(JobWithoutArgs, @batch_id)
|
170
|
-
assert_equal(3, redis.llen(@batch))
|
171
|
-
Resque.dequeue(Job, @batch_id, "foo")
|
172
|
-
assert_equal(2, redis.llen(@batch))
|
173
|
-
Resque.dequeue(JobWithoutArgs, @batch_id)
|
174
|
-
Resque.dequeue(Job, @batch_id, "foo")
|
175
|
-
assert(Job.batch_complete?(@batch_id))
|
176
|
-
assert_equal(false, Job.batch_exist?(@batch_id))
|
177
|
-
end
|
178
|
-
|
179
|
-
private
|
180
|
-
|
181
|
-
def redis
|
182
|
-
Resque.redis
|
183
|
-
end
|
184
|
-
|
185
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'test/unit'
|
3
|
-
require 'thread'
|
4
|
-
require 'turn/autorun'
|
5
|
-
require 'resque'
|
6
|
-
|
7
|
-
$:.unshift(File.expand_path(File.dirname(__FILE__)) + '/../lib')
|
8
|
-
require 'resque/batched_job'
|
9
|
-
|
10
|
-
class Job
|
11
|
-
extend Resque::Plugins::BatchedJob
|
12
|
-
@queue = :test
|
13
|
-
|
14
|
-
def self.perform(batch_id, arg)
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.after_batch_hook(batch_id, arg)
|
18
|
-
$batch_complete = true
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
class JobWithoutArgs
|
24
|
-
extend Resque::Plugins::BatchedJob
|
25
|
-
@queue = :test
|
26
|
-
|
27
|
-
def self.perform(id)
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.after_batch_hook(id)
|
31
|
-
$batch_complete = true
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|