timcharper-beanstalk-client 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest.txt ADDED
@@ -0,0 +1,27 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/beanstalk-client.rb
9
+ lib/beanstalk-client/connection.rb
10
+ lib/beanstalk-client/errors.rb
11
+ lib/beanstalk-client/job.rb
12
+ lib/beanstalk-client/version.rb
13
+ log/debug.log
14
+ script/destroy
15
+ script/generate
16
+ script/txt2html
17
+ setup.rb
18
+ tasks/deployment.rake
19
+ tasks/environment.rake
20
+ tasks/website.rake
21
+ test/test_beanstalk-client.rb
22
+ test/test_helper.rb
23
+ website/index.html
24
+ website/index.txt
25
+ website/javascripts/rounded_corners_lite.inc.js
26
+ website/stylesheets/screen.css
27
+ website/template.rhtml
data/README.txt ADDED
@@ -0,0 +1,7 @@
1
+ = README
2
+
3
+ For more detail on what many of the commands do, see the beanstalkd protocol documentation:
4
+
5
+ http://github.com/kr/beanstalkd/blob/master/doc/protocol.txt?raw=true
6
+
7
+
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'beanstalk-client/version'
2
+
3
+ AUTHOR = 'Keith Rarick' # can also be an array of Authors
4
+ EMAIL = 'kr@causes.com'
5
+ DESCRIPTION = 'Ruby client library for the Beanstalk protocol'
6
+ GEM_NAME = 'beanstalk-client' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'beanstalk' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "unknown"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Beanstalk::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'beanstalk-client documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
62
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ #PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ PATH = RUBYFORGE_PROJECT
71
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
72
+ hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'beanstalk-client'
@@ -0,0 +1,26 @@
1
+ # beanstalk-client.rb - client library for beanstalk
2
+
3
+ # Copyright (C) 2007 Philotic Inc.
4
+
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ $:.unshift(File.dirname(__FILE__))
19
+
20
+ module Beanstalk
21
+ extend self
22
+
23
+ attr_accessor :select
24
+ end
25
+
26
+ require 'beanstalk-client/connection'
@@ -0,0 +1,428 @@
1
+ # beanstalk-client/connection.rb - client library for beanstalk
2
+
3
+ # Copyright (C) 2007 Philotic Inc.
4
+
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'socket'
19
+ require 'fcntl'
20
+ require 'yaml'
21
+ require 'set'
22
+ require 'beanstalk-client/errors'
23
+ require 'beanstalk-client/job'
24
+
25
+ module Beanstalk
26
+ class Connection
27
+ attr_reader :addr
28
+
29
+ def initialize(addr, default_tube=nil)
30
+ @waiting = false
31
+ @addr = addr
32
+ connect
33
+ @last_used = 'default'
34
+ @watch_list = [@last_used]
35
+ self.use(default_tube) if default_tube
36
+ self.watch(default_tube) if default_tube
37
+ end
38
+
39
+ def connect
40
+ host, port = addr.split(':')
41
+ @socket = TCPSocket.new(host, port.to_i)
42
+
43
+ # Don't leak fds when we exec.
44
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
45
+ end
46
+
47
+ def close
48
+ @socket.close
49
+ @socket = nil
50
+ end
51
+
52
+ def put(body, pri=65536, delay=0, ttr=120)
53
+ pri = pri.to_i
54
+ delay = delay.to_i
55
+ ttr = ttr.to_i
56
+ body = body.to_s # Make sure that body.size gives a useful number
57
+ interact("put #{pri} #{delay} #{ttr} #{body.size}\r\n#{body}\r\n",
58
+ %w(INSERTED BURIED))[0].to_i
59
+ end
60
+
61
+ def yput(obj, pri=65536, delay=0, ttr=120)
62
+ put(YAML.dump(obj), pri, delay, ttr)
63
+ end
64
+
65
+ def peek_job(id)
66
+ interact("peek #{id}\r\n", :job)
67
+ end
68
+
69
+ def peek_ready()
70
+ interact("peek-ready\r\n", :job)
71
+ end
72
+
73
+ def peek_delayed()
74
+ interact("peek-delayed\r\n", :job)
75
+ end
76
+
77
+ def peek_buried()
78
+ interact("peek-buried\r\n", :job)
79
+ end
80
+
81
+ def reserve(timeout=nil)
82
+ raise WaitingForJobError if @waiting
83
+ if timeout.nil?
84
+ @socket.write("reserve\r\n")
85
+ else
86
+ @socket.write("reserve-with-timeout #{timeout}\r\n")
87
+ end
88
+
89
+ begin
90
+ @waiting = true
91
+ # Give the user a chance to select on multiple fds.
92
+ Beanstalk.select.call([@socket]) if Beanstalk.select
93
+ rescue WaitingForJobError
94
+ # just continue
95
+ ensure
96
+ @waiting = false
97
+ end
98
+
99
+ Job.new(self, *read_job('RESERVED'))
100
+ end
101
+
102
+ def delete(id)
103
+ interact("delete #{id}\r\n", %w(DELETED))
104
+ :ok
105
+ end
106
+
107
+ def release(id, pri, delay)
108
+ id = id.to_i
109
+ pri = pri.to_i
110
+ delay = delay.to_i
111
+ interact("release #{id} #{pri} #{delay}\r\n", %w(RELEASED))
112
+ :ok
113
+ end
114
+
115
+ def bury(id, pri)
116
+ interact("bury #{id} #{pri}\r\n", %w(BURIED))
117
+ :ok
118
+ end
119
+
120
+ def touch(id)
121
+ interact("touch #{id}\r\n", %w(TOUCHED))
122
+ :ok
123
+ end
124
+
125
+ def kick(n)
126
+ interact("kick #{n}\r\n", %w(KICKED))[0].to_i
127
+ end
128
+
129
+ def use(tube)
130
+ return tube if tube == @last_used
131
+ @last_used = interact("use #{tube}\r\n", %w(USING))[0]
132
+ rescue BadFormatError
133
+ raise InvalidTubeName.new(tube)
134
+ end
135
+
136
+ def watch(tube)
137
+ return @watch_list.size if @watch_list.include?(tube)
138
+ r = interact("watch #{tube}\r\n", %w(WATCHING))[0].to_i
139
+ @watch_list += [tube]
140
+ return r
141
+ rescue BadFormatError
142
+ raise InvalidTubeName.new(tube)
143
+ end
144
+
145
+ def ignore(tube)
146
+ return @watch_list.size if !@watch_list.include?(tube)
147
+ r = interact("ignore #{tube}\r\n", %w(WATCHING))[0].to_i
148
+ @watch_list -= [tube]
149
+ return r
150
+ end
151
+
152
+ def stats()
153
+ interact("stats\r\n", :yaml)
154
+ end
155
+
156
+ def job_stats(id)
157
+ interact("stats-job #{id}\r\n", :yaml)
158
+ end
159
+
160
+ def stats_tube(tube)
161
+ interact("stats-tube #{tube}\r\n", :yaml)
162
+ end
163
+
164
+ def list_tubes()
165
+ interact("list-tubes\r\n", :yaml)
166
+ end
167
+
168
+ def list_tube_used()
169
+ interact("list-tube-used\r\n", %w(USING))[0]
170
+ end
171
+
172
+ def list_tubes_watched(cached=false)
173
+ return @watch_list if cached
174
+ @watch_list = interact("list-tubes-watched\r\n", :yaml)
175
+ end
176
+
177
+ private
178
+
179
+ def interact(cmd, rfmt)
180
+ raise WaitingForJobError if @waiting
181
+ @socket.write(cmd)
182
+ return read_yaml('OK') if rfmt == :yaml
183
+ return found_job if rfmt == :job
184
+ check_resp(*rfmt)
185
+ end
186
+
187
+ def get_resp()
188
+ r = @socket.gets("\r\n")
189
+ raise EOFError if r == nil
190
+ r[0...-2]
191
+ end
192
+
193
+ def check_resp(*words)
194
+ r = get_resp()
195
+ rword, *vals = r.split(/\s+/)
196
+ if (words.size > 0) and !words.include?(rword)
197
+ raise UnexpectedResponse.classify(rword, r)
198
+ end
199
+ vals
200
+ end
201
+
202
+ def found_job()
203
+ Job.new(self, *read_job('FOUND'))
204
+ rescue NotFoundError
205
+ nil
206
+ end
207
+
208
+ def read_job(word)
209
+ id, bytes = check_resp(word).map{|s| s.to_i}
210
+ body = read_bytes(bytes)
211
+ raise 'bad trailer' if read_bytes(2) != "\r\n"
212
+ [id, body, word == 'RESERVED']
213
+ end
214
+
215
+ def read_yaml(word)
216
+ bytes_s, = check_resp(word)
217
+ yaml = read_bytes(bytes_s.to_i)
218
+ raise 'bad trailer' if read_bytes(2) != "\r\n"
219
+ YAML::load(yaml)
220
+ end
221
+
222
+ def read_bytes(n)
223
+ str = @socket.read(n)
224
+ raise EOFError, 'End of file reached' if str == nil
225
+ raise EOFError, 'End of file reached' if str.size < n
226
+ str
227
+ end
228
+ end
229
+
230
+ class Pool
231
+ attr_accessor :last_conn
232
+
233
+ def initialize(addrs, default_tube=nil)
234
+ @addrs = addrs
235
+ @watch_list = ['default']
236
+ @default_tube=default_tube
237
+ @watch_list = [default_tube] if default_tube
238
+ connect()
239
+ end
240
+
241
+ def connect()
242
+ @connections ||= {}
243
+ @addrs.each do |addr|
244
+ begin
245
+ if !@connections.include?(addr)
246
+ @connections[addr] = Connection.new(addr, @default_tube)
247
+ prev_watched = @connections[addr].list_tubes_watched()
248
+ to_ignore = prev_watched - @watch_list
249
+ @watch_list.each{|tube| @connections[addr].watch(tube)}
250
+ to_ignore.each{|tube| @connections[addr].ignore(tube)}
251
+ end
252
+ rescue Exception => ex
253
+ puts "#{ex.class}: #{ex}"
254
+ #puts begin ex.fixed_backtrace rescue ex.backtrace end
255
+ end
256
+ end
257
+ @connections.size
258
+ end
259
+
260
+ def open_connections()
261
+ @connections.values()
262
+ end
263
+
264
+ def last_server
265
+ @last_conn.addr
266
+ end
267
+
268
+ # Put a job on the queue.
269
+ #
270
+ # == Parameters:
271
+ #
272
+ # * <tt>body</tt>: the payload of the job (use Beanstalk::Pool#yput / Beanstalk::Job#ybody to automatically serialize your payload with YAML)
273
+ # * <tt>pri</tt>: priority. Default 65536 (higher numbers are higher priority)
274
+ # * <tt>delay</tt>: how long to wait until making the job available for reservation
275
+ # * <tt>ttr</tt>: time in seconds for the job to reappear on the queue (if beanstalk doesn't hear from a consumer within this time, assume the consumer died and make the job available for someone else to process). Default 120 seconds.
276
+ def put(body, pri=65536, delay=0, ttr=120)
277
+ send_to_rand_conn(:put, body, pri, delay, ttr)
278
+ end
279
+
280
+ # Like put, but serialize the object with YAML.
281
+ def yput(obj, pri=65536, delay=0, ttr=120)
282
+ send_to_rand_conn(:yput, obj, pri, delay, ttr)
283
+ end
284
+
285
+ # Reserve a job from the queue.
286
+ #
287
+ # == Parameters
288
+ #
289
+ # * <tt>timeout</tt> - Time (in seconds) to wait for a job to be available. If nil, wait indefinitely.
290
+ def reserve(timeout=nil)
291
+ send_to_rand_conn(:reserve, timeout)
292
+ end
293
+
294
+ def use(tube)
295
+ send_to_all_conns(:use, tube)
296
+ end
297
+
298
+ def watch(tube)
299
+ r = send_to_all_conns(:watch, tube)
300
+ @watch_list = send_to_rand_conn(:list_tubes_watched, true)
301
+ return r
302
+ end
303
+
304
+ def ignore(tube)
305
+ r = send_to_all_conns(:ignore, tube)
306
+ @watch_list = send_to_rand_conn(:list_tubes_watched, true)
307
+ return r
308
+ end
309
+
310
+ def raw_stats()
311
+ send_to_all_conns(:stats)
312
+ end
313
+
314
+ def stats()
315
+ sum_hashes(raw_stats.values)
316
+ end
317
+
318
+ def raw_stats_tube(tube)
319
+ send_to_all_conns(:stats_tube, tube)
320
+ end
321
+
322
+ def stats_tube(tube)
323
+ sum_hashes(raw_stats_tube(tube).values)
324
+ end
325
+
326
+ def list_tubes()
327
+ send_to_all_conns(:list_tubes)
328
+ end
329
+
330
+ def list_tube_used()
331
+ send_to_all_conns(:list_tube_used)
332
+ end
333
+
334
+ def list_tubes_watched(*args)
335
+ send_to_all_conns(:list_tubes_watched, *args)
336
+ end
337
+
338
+ def remove(conn)
339
+ @connections.delete(conn.addr)
340
+ end
341
+
342
+ # Close all open connections for this pool
343
+ def close
344
+ while @connections.size > 0
345
+ addr = @connections.keys.last
346
+ conn = @connections[addr]
347
+ @connections.delete(addr)
348
+ conn.close
349
+ end
350
+ end
351
+
352
+ def peek_ready()
353
+ send_to_each_conn_first_res(:peek_ready)
354
+ end
355
+
356
+ def peek_delayed()
357
+ send_to_each_conn_first_res(:peek_delayed)
358
+ end
359
+
360
+ def peek_buried()
361
+ send_to_each_conn_first_res(:peek_buried)
362
+ end
363
+
364
+ def peek_job(id)
365
+ make_hash(send_to_all_conns(:peek_job, id))
366
+ end
367
+
368
+ private
369
+
370
+ def call_wrap(c, *args)
371
+ self.last_conn = c
372
+ c.send(*args)
373
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE, UnexpectedResponse => ex
374
+ self.remove(c)
375
+ raise ex
376
+ end
377
+
378
+ def retry_wrap(*args)
379
+ yield
380
+ rescue DrainingError
381
+ # Don't reconnect -- we're not interested in this server
382
+ retry
383
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
384
+ connect()
385
+ retry
386
+ end
387
+
388
+ def send_to_each_conn_first_res(*args)
389
+ connect()
390
+ retry_wrap{open_connections.inject(nil) {|r,c| r or call_wrap(c, *args)}}
391
+ end
392
+
393
+ def send_to_rand_conn(*args)
394
+ connect()
395
+ retry_wrap{call_wrap(pick_connection, *args)}
396
+ end
397
+
398
+ def send_to_all_conns(*args)
399
+ connect()
400
+ retry_wrap{compact_hash(map_hash(@connections){|c| call_wrap(c, *args)})}
401
+ end
402
+
403
+ def pick_connection()
404
+ open_connections[rand(open_connections.size)] or raise NotConnected
405
+ end
406
+
407
+ def make_hash(pairs)
408
+ Hash[*pairs.inject([]){|a,b|a+b}]
409
+ end
410
+
411
+ def map_hash(h)
412
+ make_hash(h.map{|k,v| [k, yield(v)]})
413
+ end
414
+
415
+ def compact_hash(hash)
416
+ hash.reject{|k,v| v == nil}
417
+ end
418
+
419
+ def sum_hashes(hs)
420
+ hs.inject({}){|a,b| a.merge(b) {|k,o,n| combine_stats(k, o, n)}}
421
+ end
422
+
423
+ DONT_ADD = Set['name', 'version', 'pid']
424
+ def combine_stats(k, a, b)
425
+ DONT_ADD.include?(k) ? Set[a] + Set[b] : a + b
426
+ end
427
+ end
428
+ end