stalk_climber 0.0.6 → 0.1.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/.rdoc_options +7 -0
- data/.travis.yml +8 -10
- data/Gemfile +1 -0
- data/lib/stalk_climber.rb +5 -0
- data/lib/stalk_climber/climber.rb +42 -54
- data/lib/stalk_climber/climber_enumerable.rb +110 -0
- data/lib/stalk_climber/climber_enumerables.rb +7 -0
- data/lib/stalk_climber/connection.rb +49 -37
- data/lib/stalk_climber/connection_pool.rb +37 -7
- data/lib/stalk_climber/job.rb +270 -67
- data/lib/stalk_climber/lazy_enumerable.rb +1 -0
- data/lib/stalk_climber/tube.rb +182 -0
- data/lib/stalk_climber/tubes.rb +37 -0
- data/lib/stalk_climber/version.rb +2 -1
- data/test/test_helper.rb +4 -2
- data/test/unit/beaneater_job_test.rb +260 -0
- data/test/unit/climber_enumerable.rb +18 -0
- data/test/unit/climber_test.rb +31 -100
- data/test/unit/connection_pool_test.rb +48 -33
- data/test/unit/connection_test.rb +200 -149
- data/test/unit/job_test.rb +240 -147
- data/test/unit/jobs_test.rb +106 -0
- data/test/unit/tube_test.rb +89 -0
- data/test/unit/tubes_test.rb +52 -0
- metadata +17 -2
@@ -0,0 +1,182 @@
|
|
1
|
+
module StalkClimber
|
2
|
+
class Tube < Beaneater::Tube
|
3
|
+
|
4
|
+
# List of available attributes available via the stats object.
|
5
|
+
STATS_ATTRIBUTES = %w[
|
6
|
+
cmd-delete cmd-pause-tube current-jobs-buried current-jobs-delayed
|
7
|
+
current-jobs-ready current-jobs-reserved current-jobs-urgent current-using
|
8
|
+
current-waiting current-watching name pause pause-time-left total-jobs
|
9
|
+
]
|
10
|
+
|
11
|
+
# Lookup table of which method to call to retrieve each stat
|
12
|
+
STATS_METHODS = Hash[
|
13
|
+
STATS_ATTRIBUTES.zip(STATS_ATTRIBUTES.map {|stat| stat.gsub(/-/, '_')})
|
14
|
+
]
|
15
|
+
|
16
|
+
# Pause and name are available as stats but are not included in this list
|
17
|
+
# because Beaneater::Tube already defines methods with those names. Instead,
|
18
|
+
# the pause stat can be obtained via Tube#pause_time and name can be obtained
|
19
|
+
# via the superclass method.
|
20
|
+
STATS_METHODS.values.reject {|k,v| %w[name pause].include?(k)}.each do |method_name|
|
21
|
+
define_method method_name do |force_refresh = true|
|
22
|
+
return stats(force_refresh).send(method_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# :call-seq:
|
28
|
+
# exists?() => Boolean
|
29
|
+
#
|
30
|
+
# Determines if a tube exists by retrieving stats for the tube. If Beaneater
|
31
|
+
# can't find the tube then it oes not exist and false is returned. If stats
|
32
|
+
# are retrieved successfully then true is returned. The stats command is used
|
33
|
+
# because it will return a response of near constant size, whereas, depending
|
34
|
+
# on the environment, list-tubes could return a much larger response.
|
35
|
+
# Additionally, updated stats information is usually more valuable than a list
|
36
|
+
# of tubes that is immediately discarded.
|
37
|
+
def exists?
|
38
|
+
return !stats.nil?
|
39
|
+
rescue Beaneater::NotFoundError
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# :call-seq:
|
45
|
+
# pause_time(force_refresh = false) => Integer
|
46
|
+
#
|
47
|
+
# Returns pause stat which indicates the duration the tube is currently
|
48
|
+
# paused for. The name pause_time is used because Beaneater::Tube already
|
49
|
+
# defines a pause method.
|
50
|
+
def pause_time(force_refresh = false)
|
51
|
+
return stats(force_refresh).pause
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# :call-seq:
|
56
|
+
# stats(force_refresh = true) => Beaneater::StatStruct
|
57
|
+
#
|
58
|
+
# Returns related stats for this tube.
|
59
|
+
#
|
60
|
+
# @tube.stats.current_jobs_delayed
|
61
|
+
# #=> 3
|
62
|
+
def stats(force_refresh = true)
|
63
|
+
if force_refresh || @stats.nil?
|
64
|
+
stats = transmit_to_all("stats-tube #{name}", :merge => true)[:body]
|
65
|
+
@stats = Beaneater::StatStruct.from_hash(stats)
|
66
|
+
end
|
67
|
+
return @stats
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# :call-seq:
|
72
|
+
# to_h() => Hash
|
73
|
+
#
|
74
|
+
# Returns a hash of all tube attributes derived from updated stats
|
75
|
+
#
|
76
|
+
# tube = StalkClimber::Tube.new(connection_pool, 'stalk_climber')
|
77
|
+
# tube.to_h
|
78
|
+
# #=> {"cmd-delete"=>100, "cmd-pause-tube"=>101, "current-jobs-buried"=>102, "current-jobs-delayed"=>103, "current-jobs-ready"=>104, "current-jobs-reserved"=>105, "current-jobs-urgent"=>106, "current-using"=>107, "current-waiting"=>108, "current-watching"=>109, "name"=>"stalk_climber", "pause"=>111, "pause-time-left"=>110, "total-jobs"=>112}
|
79
|
+
def to_h
|
80
|
+
stats
|
81
|
+
return Hash[
|
82
|
+
STATS_METHODS.map do |stat, method_name|
|
83
|
+
[stat, stats(false).send(method_name)]
|
84
|
+
end
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
# :call-seq:
|
90
|
+
# to_s() => String
|
91
|
+
#
|
92
|
+
# Returns string representation of the tube
|
93
|
+
#
|
94
|
+
# tube = StalkClimber::Tube.new(connection_pool, 'stalk_climber')
|
95
|
+
# tube.to_s
|
96
|
+
# #=> #<StalkClimber::Tube name="stalk_climber">
|
97
|
+
def to_s
|
98
|
+
"#<StalkClimber::Tube name=#{name}>"
|
99
|
+
end
|
100
|
+
alias :inspect :to_s
|
101
|
+
|
102
|
+
|
103
|
+
# :method: cmd_delete
|
104
|
+
# :call-seq:
|
105
|
+
# cmd_delete() => Integer
|
106
|
+
#
|
107
|
+
# Returns the number of times the delete command has been called for
|
108
|
+
# jobs in the tube.
|
109
|
+
|
110
|
+
# :method: cmd_pause_tube
|
111
|
+
# :call-seq:
|
112
|
+
# cmd_pause_tube() => Integer
|
113
|
+
#
|
114
|
+
# Returns the number of times the pause-tube command has been called on
|
115
|
+
# the tube.
|
116
|
+
|
117
|
+
# :method: current_jobs_buried
|
118
|
+
# :call-seq:
|
119
|
+
# current_jobs_buried() => Integer
|
120
|
+
#
|
121
|
+
# Returns the number of jobs in the tube that are currently buried.
|
122
|
+
|
123
|
+
# :method: current_jobs_delayed
|
124
|
+
# :call-seq:
|
125
|
+
# current_jobs_buried() => Integer
|
126
|
+
#
|
127
|
+
# Returns the number of jobs in the tube that are currently buried.
|
128
|
+
|
129
|
+
# :method: current_jobs_ready
|
130
|
+
# :call-seq:
|
131
|
+
# current_jobs_ready() => Integer
|
132
|
+
#
|
133
|
+
# Returns the number of jobs in the tube that are currently ready.
|
134
|
+
|
135
|
+
# :method: current_jobs_reserved
|
136
|
+
# :call-seq:
|
137
|
+
# current_jobs_reserved() => Integer
|
138
|
+
#
|
139
|
+
# Returns the number of jobs in the tube that are currently reserved.
|
140
|
+
|
141
|
+
# :method: current_jobs_urgent
|
142
|
+
# :call-seq:
|
143
|
+
# current_jobs_urgent() => Integer
|
144
|
+
#
|
145
|
+
# Returns the number of jobs currently in the tube with a priority
|
146
|
+
# less than 1024, and thus deemed urgent.
|
147
|
+
|
148
|
+
# :method: current_using
|
149
|
+
# :call-seq:
|
150
|
+
# current_using() => Integer
|
151
|
+
#
|
152
|
+
# Returns the number of connections that are currently using the tube.
|
153
|
+
|
154
|
+
# :method: current_waiting
|
155
|
+
# :call-seq:
|
156
|
+
# current_waiting() => Integer
|
157
|
+
#
|
158
|
+
# Returns the number of connections that have issued reserve requests
|
159
|
+
# for the tube and are currently awaiting a reply.
|
160
|
+
|
161
|
+
# :method: current_watching
|
162
|
+
# :call-seq:
|
163
|
+
# current_watching() => Integer
|
164
|
+
#
|
165
|
+
# Returns the number of connections that are watching the tube.
|
166
|
+
|
167
|
+
# :method: pause_time_left
|
168
|
+
# :call-seq:
|
169
|
+
# pause_time_left() => Integer
|
170
|
+
#
|
171
|
+
# Returns the number of seconds remaining before the tube is no longer
|
172
|
+
# paused.
|
173
|
+
|
174
|
+
# :method: total_jobs
|
175
|
+
# :call-seq:
|
176
|
+
# total_jobs() => Integer
|
177
|
+
#
|
178
|
+
# Returns the total number of jobs that have been registered with the tube
|
179
|
+
# since the tube was most recently created.
|
180
|
+
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module StalkClimber
|
2
|
+
class Tubes < Beaneater::Tubes
|
3
|
+
|
4
|
+
extend Forwardable
|
5
|
+
include RUBY_VERSION >= '2.0.0' ? LazyEnumerable : Enumerable
|
6
|
+
|
7
|
+
def_delegator :all, :each
|
8
|
+
|
9
|
+
|
10
|
+
# :call-seq:
|
11
|
+
# all() => Array[StalkClimber::Tube]
|
12
|
+
#
|
13
|
+
# List of all known beanstalk tubes in the connection pool.
|
14
|
+
# Adapted with minor modification from Beaneater::Tubes#all
|
15
|
+
def all
|
16
|
+
return transmit_to_all('list-tubes', :merge => true)[:body].map do |tube_name|
|
17
|
+
StalkClimber::Tube.new(self.pool, tube_name)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# :call-seq:
|
22
|
+
# each() => Enumerator
|
23
|
+
# each {|tube| block }
|
24
|
+
#
|
25
|
+
# Interface for tube enumerator/enumeration. Tubes are mostly in creation order,
|
26
|
+
# though order can vary depending on the order in which responses to list-tubes are
|
27
|
+
# received from each of the Beanstalkd servers. Returns an instance of
|
28
|
+
# StalkClimber::Tube for each tube. Unlike Jobs, tubes are not cached because
|
29
|
+
# they are more easily accessible
|
30
|
+
#
|
31
|
+
# tubes = StalkClimber::Tubes.new(connection_pool)
|
32
|
+
# tubes.each do |tube|
|
33
|
+
# tube.clear
|
34
|
+
# end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -6,13 +6,15 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
6
|
|
7
7
|
require 'test/unit'
|
8
8
|
require 'mocha/setup'
|
9
|
-
|
9
|
+
require 'minitest/autorun'
|
10
|
+
require 'minitest/should'
|
10
11
|
require 'stalk_climber'
|
12
|
+
require 'securerandom'
|
11
13
|
|
12
14
|
BEANSTALK_ADDRESS = ENV['BEANSTALK_ADDRESS'] || 'beanstalk://localhost'
|
13
15
|
BEANSTALK_ADDRESSES = ENV['BEANSTALK_ADDRESSES'] || BEANSTALK_ADDRESS
|
14
16
|
|
15
|
-
class
|
17
|
+
class StalkClimber::TestCase < MiniTest::Should::TestCase
|
16
18
|
|
17
19
|
def seed_jobs(count = 5)
|
18
20
|
count.times.map do
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BeaneaterJobTest < StalkClimber::TestCase
|
4
|
+
|
5
|
+
setup do
|
6
|
+
@pool = Beaneater::Pool.new(['localhost'])
|
7
|
+
@tube = @pool.tubes.find 'tube'
|
8
|
+
end
|
9
|
+
|
10
|
+
context "for #bury" do
|
11
|
+
setup do
|
12
|
+
@time = Time.now.to_i
|
13
|
+
@tube.put "foo bury #{@time}", :pri => 5
|
14
|
+
end
|
15
|
+
|
16
|
+
should "be buried with same pri" do
|
17
|
+
job = @tube.reserve
|
18
|
+
assert_equal "foo bury #{@time}", job.body
|
19
|
+
assert_equal 'reserved', job.stats.state
|
20
|
+
job.bury
|
21
|
+
assert_equal 'buried', job.stats.state
|
22
|
+
assert_equal 5, job.stats.pri
|
23
|
+
assert_equal "foo bury #{@time}", @tube.peek(:buried).body
|
24
|
+
end
|
25
|
+
|
26
|
+
should "be released with new pri" do
|
27
|
+
job = @tube.reserve
|
28
|
+
assert_equal "foo bury #{@time}", job.body
|
29
|
+
assert_equal 'reserved', job.stats.state
|
30
|
+
job.bury(:pri => 10)
|
31
|
+
assert_equal 'buried', job.stats.state
|
32
|
+
assert_equal 10, job.stats.pri
|
33
|
+
assert_equal "foo bury #{@time}", @tube.peek(:buried).body
|
34
|
+
end
|
35
|
+
|
36
|
+
should "not bury if not reserved" do
|
37
|
+
job = @tube.peek(:ready)
|
38
|
+
assert_raises(Beaneater::JobNotReserved) { job.bury }
|
39
|
+
end
|
40
|
+
|
41
|
+
should "not bury if reserved and deleted" do
|
42
|
+
job = @tube.reserve
|
43
|
+
job.delete
|
44
|
+
assert_equal false, job.reserved
|
45
|
+
assert_raises(Beaneater::NotFoundError) { job.bury }
|
46
|
+
end
|
47
|
+
end # bury
|
48
|
+
|
49
|
+
context "for #release" do
|
50
|
+
setup do
|
51
|
+
@time = Time.now.to_i
|
52
|
+
@tube.put "foo release #{@time}", :pri => 5
|
53
|
+
end
|
54
|
+
|
55
|
+
should "be released with same pri" do
|
56
|
+
job = @tube.reserve
|
57
|
+
assert_equal "foo release #{@time}", job.body
|
58
|
+
assert_equal 'reserved', job.stats.state
|
59
|
+
job.release
|
60
|
+
assert_equal 'ready', job.stats.state
|
61
|
+
assert_equal 5, job.stats.pri
|
62
|
+
assert_equal 0, job.stats.delay
|
63
|
+
end
|
64
|
+
|
65
|
+
should "be released with new pri" do
|
66
|
+
job = @tube.reserve
|
67
|
+
assert_equal "foo release #{@time}", job.body
|
68
|
+
assert_equal 'reserved', job.stats.state
|
69
|
+
job.release :pri => 10, :delay => 2
|
70
|
+
assert_equal 'delayed', job.stats.state
|
71
|
+
assert_equal 10, job.stats.pri
|
72
|
+
assert_equal 2, job.stats.delay
|
73
|
+
end
|
74
|
+
|
75
|
+
should "not released if not reserved" do
|
76
|
+
job = @tube.peek(:ready)
|
77
|
+
assert_raises(Beaneater::JobNotReserved) { job.release }
|
78
|
+
end
|
79
|
+
|
80
|
+
should "not release if not reserved and buried" do
|
81
|
+
job = @tube.reserve
|
82
|
+
job.bury
|
83
|
+
assert_raises(Beaneater::JobNotReserved) { job.release }
|
84
|
+
end
|
85
|
+
end # release
|
86
|
+
|
87
|
+
describe "for #delete" do
|
88
|
+
setup do
|
89
|
+
@tube.put 'foo'
|
90
|
+
end
|
91
|
+
|
92
|
+
should "deletable" do
|
93
|
+
job = @tube.peek(:ready)
|
94
|
+
assert_equal 'foo', job.body
|
95
|
+
job.delete
|
96
|
+
assert_nil @tube.peek(:ready)
|
97
|
+
end
|
98
|
+
end # delete
|
99
|
+
|
100
|
+
describe "for #touch" do
|
101
|
+
setup do
|
102
|
+
@tube.put 'foo touch', :ttr => 1
|
103
|
+
end
|
104
|
+
|
105
|
+
should "be toucheable" do
|
106
|
+
job = @tube.reserve
|
107
|
+
assert_equal 'foo touch', job.body
|
108
|
+
job.touch
|
109
|
+
assert_equal 1, job.stats.reserves
|
110
|
+
job.delete
|
111
|
+
end
|
112
|
+
|
113
|
+
should "not touch if not reserved" do
|
114
|
+
job = @tube.peek(:ready)
|
115
|
+
assert_raises(Beaneater::JobNotReserved) { job.touch }
|
116
|
+
end
|
117
|
+
|
118
|
+
should "not touch if not reserved and released" do
|
119
|
+
job = @tube.reserve
|
120
|
+
job.release
|
121
|
+
assert_raises(Beaneater::JobNotReserved) { job.touch }
|
122
|
+
end
|
123
|
+
|
124
|
+
should "not touch if reserved and deleted" do
|
125
|
+
job = @tube.reserve
|
126
|
+
job.delete
|
127
|
+
assert_raises(Beaneater::NotFoundError) { job.touch }
|
128
|
+
end
|
129
|
+
end # touch
|
130
|
+
|
131
|
+
describe "for #kick" do
|
132
|
+
setup do
|
133
|
+
@tube.put 'foo touch', :ttr => 1
|
134
|
+
end
|
135
|
+
|
136
|
+
should "be toucheable" do
|
137
|
+
job = @tube.reserve
|
138
|
+
assert_equal 'foo touch', job.body
|
139
|
+
job.bury
|
140
|
+
assert_equal 1, @tube.stats.current_jobs_buried
|
141
|
+
if @pool.stats.version.to_f > 1.7
|
142
|
+
job.kick
|
143
|
+
assert_equal 0, @tube.stats.current_jobs_buried
|
144
|
+
assert_equal 1, @tube.stats.current_jobs_ready
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end # kick
|
148
|
+
|
149
|
+
describe "for #stats" do
|
150
|
+
setup do
|
151
|
+
@tube.put 'foo'
|
152
|
+
@job = @tube.peek(:ready)
|
153
|
+
end
|
154
|
+
|
155
|
+
should "have stats" do
|
156
|
+
assert_equal 'tube', @job.stats['tube']
|
157
|
+
assert_equal 'ready', @job.stats.state
|
158
|
+
end
|
159
|
+
|
160
|
+
should "return nil for deleted job with no stats" do
|
161
|
+
@job.delete
|
162
|
+
assert_raises(Beaneater::NotFoundError) { @job.stats }
|
163
|
+
end
|
164
|
+
end # stats
|
165
|
+
|
166
|
+
describe "for #reserved?" do
|
167
|
+
setup do
|
168
|
+
@tube.put 'foo'
|
169
|
+
@job = @tube.peek(:ready)
|
170
|
+
end
|
171
|
+
|
172
|
+
should "have stats" do
|
173
|
+
assert_equal false, @job.reserved?
|
174
|
+
job = @tube.reserve
|
175
|
+
assert_equal job.id, @job.id
|
176
|
+
assert_equal true, @job.reserved?
|
177
|
+
@job.delete
|
178
|
+
assert_raises(Beaneater::NotFoundError) { @job.reserved? }
|
179
|
+
end
|
180
|
+
end # reserved?
|
181
|
+
|
182
|
+
describe "for #exists?" do
|
183
|
+
setup do
|
184
|
+
@tube.put 'foo'
|
185
|
+
@job = @tube.peek(:ready)
|
186
|
+
end
|
187
|
+
|
188
|
+
should "exists?" do
|
189
|
+
assert @job.exists?
|
190
|
+
end
|
191
|
+
|
192
|
+
should "not exist" do
|
193
|
+
@job.delete
|
194
|
+
assert !@job.exists?
|
195
|
+
end
|
196
|
+
end # exists?
|
197
|
+
|
198
|
+
describe "for #tube" do
|
199
|
+
setup do
|
200
|
+
@tube.put 'bar'
|
201
|
+
@job = @tube.peek(:ready)
|
202
|
+
end
|
203
|
+
|
204
|
+
should "have stats" do
|
205
|
+
job = @tube.reserve
|
206
|
+
assert_equal @tube.name, job.tube
|
207
|
+
job.release
|
208
|
+
end
|
209
|
+
end # tube
|
210
|
+
|
211
|
+
describe "for #pri" do
|
212
|
+
setup do
|
213
|
+
@tube.put 'bar', :pri => 1
|
214
|
+
@job = @tube.peek(:ready)
|
215
|
+
end
|
216
|
+
|
217
|
+
should "return pri" do
|
218
|
+
job = @tube.reserve
|
219
|
+
assert_equal 1, job.pri
|
220
|
+
job.release
|
221
|
+
end
|
222
|
+
end # tube
|
223
|
+
|
224
|
+
|
225
|
+
describe "for #ttr" do
|
226
|
+
setup do
|
227
|
+
@tube.put 'bar', :ttr => 5
|
228
|
+
@job = @tube.peek(:ready)
|
229
|
+
end
|
230
|
+
|
231
|
+
should "return ttr" do
|
232
|
+
job = @tube.reserve
|
233
|
+
assert_equal 5, job.ttr
|
234
|
+
job.release
|
235
|
+
end
|
236
|
+
end # tube
|
237
|
+
|
238
|
+
describe "for #delay" do
|
239
|
+
setup do
|
240
|
+
@tube.put 'bar', :delay => 5
|
241
|
+
@job = @tube.peek(:delayed)
|
242
|
+
end
|
243
|
+
|
244
|
+
should "return delay" do
|
245
|
+
assert_equal 5, @job.delay
|
246
|
+
end
|
247
|
+
end # tube
|
248
|
+
|
249
|
+
after do
|
250
|
+
cleanup_tubes!(['tube'])
|
251
|
+
end
|
252
|
+
|
253
|
+
def cleanup_tubes!(tubes, bp=nil)
|
254
|
+
bp ||= @pool
|
255
|
+
tubes.each do |name|
|
256
|
+
bp.tubes.find(name).clear
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
end # Beaneater::Job
|