mloughran-job_queue 0.0.5 → 0.0.6
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/VERSION.yml +1 -1
- data/lib/job_queue/adapters/beanstalk_adapter.rb +64 -10
- data/lib/job_queue/job_queue.rb +25 -14
- data/spec/beanstalk_adapter_spec.rb +132 -1
- metadata +2 -2
data/VERSION.yml
CHANGED
@@ -4,33 +4,87 @@ class JobQueue::BeanstalkAdapter
|
|
4
4
|
def initialize(options = {})
|
5
5
|
@hosts = options[:hosts] || 'localhost:11300'
|
6
6
|
end
|
7
|
-
|
8
|
-
def put(string, queue, priority)
|
9
|
-
|
7
|
+
|
8
|
+
def put(string, queue, priority, ttr)
|
9
|
+
ttr = ttr.floor #rounding because Beanstalk doesnt accept float numbers
|
10
|
+
raise JobQueue::ArgumentError, "TTR must be greater than 1" if ttr < 2
|
11
|
+
|
12
|
+
delay = 0
|
13
|
+
job_info = beanstalk_pool(queue).put_and_report_conn \
|
14
|
+
string, priority, delay, ttr
|
15
|
+
"#{job_info[:host]}_#{job_info[:id]}"
|
16
|
+
rescue Beanstalk::NotConnected
|
17
|
+
raise JobQueue::NoConnectionAvailable
|
10
18
|
end
|
11
|
-
|
12
|
-
def subscribe(error_report, queue, &block)
|
19
|
+
|
20
|
+
def subscribe(error_report, cleanup_task, queue, &block)
|
13
21
|
pool = Beanstalk::Pool.new([@hosts].flatten, queue)
|
14
22
|
loop do
|
15
23
|
begin
|
16
24
|
job = pool.reserve(1)
|
25
|
+
time_left = job.stats["time-left"]
|
17
26
|
JobQueue.logger.info "Beanstalk received #{job.body}"
|
18
|
-
|
27
|
+
Timeout::timeout([time_left - 1, 1].max) do
|
19
28
|
yield job.body
|
20
|
-
|
21
|
-
|
29
|
+
end
|
30
|
+
job.delete
|
31
|
+
rescue Timeout::Error
|
32
|
+
cleanup_task.call(job.body)
|
33
|
+
JobQueue.logger.warn "Job timed out"
|
34
|
+
begin
|
22
35
|
job.delete
|
36
|
+
rescue Beanstalk::NotFoundError
|
37
|
+
JobQueue.logger.error "Job timed out and could not be deleted"
|
23
38
|
end
|
24
39
|
rescue Beanstalk::TimedOut
|
25
40
|
# Do nothing - retry to reseve (from another host?)
|
41
|
+
rescue => e
|
42
|
+
if job
|
43
|
+
error_report.call(job.body, e)
|
44
|
+
begin
|
45
|
+
job.delete
|
46
|
+
rescue Beanstalk::NotFoundError
|
47
|
+
JobQueue.logger.error "Job failed but could not be deleted"
|
48
|
+
end
|
49
|
+
else
|
50
|
+
JobQueue.logger.error "Unhandled exception: #{e.message}\n" \
|
51
|
+
"#{e.backtrace.join("\n")}\n"
|
52
|
+
end
|
26
53
|
end
|
27
54
|
end
|
28
55
|
end
|
29
|
-
|
30
|
-
def
|
56
|
+
|
57
|
+
def job_stats(job_id)
|
58
|
+
host, id = job_id.split('_')
|
59
|
+
beanstalk_pool.job_stats(id).select { |k, v| k == host }[0][1]
|
60
|
+
rescue Beanstalk::NotFoundError
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def beanstalk_pool(queue='default')
|
31
65
|
@beanstalk_pools ||= {}
|
32
66
|
@beanstalk_pools[queue] ||= begin
|
33
67
|
Beanstalk::Pool.new([@hosts].flatten, queue)
|
34
68
|
end
|
35
69
|
end
|
70
|
+
|
71
|
+
module BeanstalkExtension
|
72
|
+
def put_and_report_conn(body, pri=65536, delay=0, ttr=120)
|
73
|
+
send_to_rand_conn_and_report(:put, body, pri, delay, ttr)
|
74
|
+
end
|
75
|
+
|
76
|
+
def send_to_rand_conn_and_report(*args)
|
77
|
+
connect()
|
78
|
+
retry_wrap{
|
79
|
+
conn = pick_connection
|
80
|
+
{:host => conn.addr, :id => call_wrap(conn, *args)}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def job_stats(id)
|
85
|
+
make_hash(send_to_all_conns(:job_stats, id))
|
86
|
+
end
|
87
|
+
end
|
36
88
|
end
|
89
|
+
|
90
|
+
Beanstalk::Pool.send(:include, JobQueue::BeanstalkAdapter::BeanstalkExtension)
|
data/lib/job_queue/job_queue.rb
CHANGED
@@ -29,28 +29,39 @@ class JobQueue
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def self.put(string, options = {})
|
34
34
|
queue = options[:queue] || 'default'
|
35
35
|
priority = options[:priority] || 50
|
36
|
-
|
36
|
+
ttr = options[:ttr] || 60
|
37
|
+
adapter.put(string, queue, priority, ttr)
|
37
38
|
end
|
38
|
-
|
39
|
+
|
39
40
|
def self.subscribe(options = {}, &block)
|
40
41
|
queue = options[:queue] || 'default'
|
41
|
-
error_report = options[:error_report] ||
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
"\n"
|
50
|
-
end
|
42
|
+
error_report = options[:error_report] || Proc.new do |job_body, e|
|
43
|
+
JobQueue.logger.error \
|
44
|
+
"Job failed\n" \
|
45
|
+
"==========\n" \
|
46
|
+
"Job content: #{job_body.inspect}\n" \
|
47
|
+
"Exception: #{e.message}\n" \
|
48
|
+
"#{e.backtrace.join("\n")}\n" \
|
49
|
+
"\n"
|
51
50
|
end
|
51
|
+
cleanup_task = options[:cleanup] || lambda {}
|
52
52
|
catch :stop do
|
53
|
-
adapter.subscribe(error_report, queue, &block)
|
53
|
+
adapter.subscribe(error_report, cleanup_task, queue, &block)
|
54
54
|
end
|
55
55
|
end
|
56
|
+
|
57
|
+
# Returns a hash of info (exact details dependent on adapter)
|
58
|
+
def self.job_stats(job_id)
|
59
|
+
adapter.job_stats(job_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
class NoConnectionAvailable < RuntimeError
|
63
|
+
end
|
64
|
+
|
65
|
+
class ArgumentError < ::ArgumentError
|
66
|
+
end
|
56
67
|
end
|
@@ -40,6 +40,137 @@ describe JobQueue::BeanstalkAdapter do
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
describe "put" do
|
44
|
+
before :all do
|
45
|
+
JobQueue.adapter = JobQueue::BeanstalkAdapter.new
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return the job id" do
|
49
|
+
job_id = JobQueue.put("hello 1")
|
50
|
+
job_id.should == "localhost:11300_1"
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should assign job priority" do
|
54
|
+
jobs = ["1","2","3"]
|
55
|
+
JobQueue.put(jobs[2], :priority => 3)
|
56
|
+
JobQueue.put(jobs[1], :priority => 2)
|
57
|
+
JobQueue.put(jobs[0], :priority => 1)
|
58
|
+
|
59
|
+
jobs_received = []
|
60
|
+
should_not_timeout(0.5) {
|
61
|
+
index = 0
|
62
|
+
JobQueue.subscribe do |job_body|
|
63
|
+
index += 1
|
64
|
+
jobs_received << job_body
|
65
|
+
throw :stop if index == 3
|
66
|
+
end
|
67
|
+
}
|
68
|
+
|
69
|
+
jobs_received.should == jobs
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should be able to retrieve job stats by id" do
|
73
|
+
job_id = JobQueue.put("hello 1")
|
74
|
+
job_id.should == "localhost:11300_1"
|
75
|
+
JobQueue.put("hello 2")
|
76
|
+
stats = JobQueue.job_stats("localhost:11300_1")
|
77
|
+
|
78
|
+
stats["id"].should == 1
|
79
|
+
stats["tube"].should == "default"
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should raise error when no connections exist" do
|
83
|
+
system "killall beanstalkd"
|
84
|
+
lambda {
|
85
|
+
JobQueue.put('test')
|
86
|
+
}.should raise_error(JobQueue::NoConnectionAvailable)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should succeed when one connection fails" do
|
90
|
+
JobQueue.adapter = JobQueue::BeanstalkAdapter.new({
|
91
|
+
:hosts => ['localhost:10001', 'localhost:666']
|
92
|
+
})
|
93
|
+
10.times{ job_id = JobQueue.put("hello 1")}
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should raise an error if a ttr of < 2 is specified" do
|
97
|
+
lambda {
|
98
|
+
JobQueue.put('test', :ttr => 1.9)
|
99
|
+
}.should raise_error(JobQueue::ArgumentError)
|
100
|
+
|
101
|
+
lambda {
|
102
|
+
JobQueue.put('test', :ttr => 2)
|
103
|
+
}.should_not raise_error(JobQueue::ArgumentError)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "subscribe" do
|
108
|
+
before :all do
|
109
|
+
JobQueue.adapter = JobQueue::BeanstalkAdapter.new
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should delete a job once it has been succesfully excecuted" do
|
113
|
+
job_id = JobQueue.put('testdeleted')
|
114
|
+
JobQueue.put('foo')
|
115
|
+
index = 0
|
116
|
+
JobQueue.subscribe do |body|
|
117
|
+
index += 1
|
118
|
+
throw :stop if index == 2
|
119
|
+
end
|
120
|
+
JobQueue.job_stats(job_id).should be_nil
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should report and error and delete the job if a job times out" do
|
124
|
+
job_id = JobQueue.put("job1", :ttr => 2)
|
125
|
+
JobQueue.put('test')
|
126
|
+
|
127
|
+
JobQueue.logger.should_receive(:warn).with("Job timed out")
|
128
|
+
|
129
|
+
index = 0
|
130
|
+
JobQueue.subscribe do |body|
|
131
|
+
index += 1
|
132
|
+
throw :stop if index == 2
|
133
|
+
sleep 2.2
|
134
|
+
end
|
135
|
+
|
136
|
+
JobQueue.job_stats(job_id).should be_nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should allow a client to cleanup if a job times out" do
|
140
|
+
JobQueue.put('jobcleanup', :ttr => 2)
|
141
|
+
JobQueue.put('test')
|
142
|
+
|
143
|
+
cleanup = nil
|
144
|
+
|
145
|
+
index = 0
|
146
|
+
JobQueue.subscribe(:cleanup => lambda { |job| FileUtils.rm(job) }) do |body|
|
147
|
+
file = File.open(body, 'w')
|
148
|
+
file << "hello"
|
149
|
+
file.flush
|
150
|
+
|
151
|
+
index += 1
|
152
|
+
throw :stop if index == 2
|
153
|
+
sleep 2.2
|
154
|
+
end
|
155
|
+
|
156
|
+
File.exists?('jobcleanup').should be_false
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "job_stats" do
|
161
|
+
before :all do
|
162
|
+
JobQueue.adapter = JobQueue::BeanstalkAdapter.new
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should gracefully deal with jobs where connection no longer exists" do
|
166
|
+
JobQueue.job_stats("localhost:11305_1").should be_nil
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should gracefully deal with jobs where job doesn't exist" do
|
170
|
+
JobQueue.job_stats("localhost:11300_1").should be_nil
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
43
174
|
describe "when connecting to one instance" do
|
44
175
|
before :all do
|
45
176
|
JobQueue.adapter = JobQueue::BeanstalkAdapter.new
|
@@ -126,7 +257,7 @@ describe JobQueue::BeanstalkAdapter do
|
|
126
257
|
throw :stop
|
127
258
|
end
|
128
259
|
end
|
129
|
-
|
260
|
+
|
130
261
|
# TODO: This test is brittle.
|
131
262
|
it "should be possible to retrieve all jobs supplied" do
|
132
263
|
# Put some jobs on the queue
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mloughran-job_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martyn Loughran
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-06-
|
12
|
+
date: 2009-06-12 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|