isaacfeliu-beanstalk-client 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1 @@
1
+ README
@@ -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 }
@@ -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,393 @@
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
+ interact("put #{pri} #{delay} #{ttr} #{body.size}\r\n#{body}\r\n",
54
+ %w(INSERTED BURIED))[0].to_i
55
+ end
56
+
57
+ def yput(obj, pri=65536, delay=0, ttr=120)
58
+ put(YAML.dump(obj), pri, delay, ttr)
59
+ end
60
+
61
+ def peek_job(id)
62
+ interact("peek #{id}\r\n", :job)
63
+ end
64
+
65
+ def peek_ready()
66
+ interact("peek-ready\r\n", :job)
67
+ end
68
+
69
+ def peek_delayed()
70
+ interact("peek-delayed\r\n", :job)
71
+ end
72
+
73
+ def peek_buried()
74
+ interact("peek-buried\r\n", :job)
75
+ end
76
+
77
+ def reserve()
78
+ raise WaitingForJobError if @waiting
79
+ @socket.write("reserve\r\n")
80
+
81
+ begin
82
+ @waiting = true
83
+ # Give the user a chance to select on multiple fds.
84
+ Beanstalk.select.call([@socket]) if Beanstalk.select
85
+ rescue WaitingForJobError
86
+ # just continue
87
+ ensure
88
+ @waiting = false
89
+ end
90
+
91
+ Job.new(self, *read_job('RESERVED'))
92
+ end
93
+
94
+ def delete(id)
95
+ interact("delete #{id}\r\n", %w(DELETED))
96
+ :ok
97
+ end
98
+
99
+ def release(id, pri, delay)
100
+ interact("release #{id} #{pri} #{delay}\r\n", %w(RELEASED))
101
+ :ok
102
+ end
103
+
104
+ def bury(id, pri)
105
+ interact("bury #{id} #{pri}\r\n", %w(BURIED))
106
+ :ok
107
+ end
108
+
109
+ def use(tube)
110
+ return tube if tube == @last_used
111
+ @last_used = interact("use #{tube}\r\n", %w(USING))[0]
112
+ rescue BadFormatError
113
+ raise InvalidTubeName.new(tube)
114
+ end
115
+
116
+ def watch(tube)
117
+ return @watch_list.size if @watch_list.include?(tube)
118
+ r = interact("watch #{tube}\r\n", %w(WATCHING))[0].to_i
119
+ @watch_list += [tube]
120
+ return r
121
+ rescue BadFormatError
122
+ raise InvalidTubeName.new(tube)
123
+ end
124
+
125
+ def ignore(tube)
126
+ return @watch_list.size if !@watch_list.include?(tube)
127
+ r = interact("ignore #{tube}\r\n", %w(WATCHING))[0].to_i
128
+ @watch_list -= [tube]
129
+ return r
130
+ end
131
+
132
+ def stats()
133
+ interact("stats\r\n", :yaml)
134
+ end
135
+
136
+ def job_stats(id)
137
+ interact("stats-job #{id}\r\n", :yaml)
138
+ end
139
+
140
+ def stats_tube(tube)
141
+ interact("stats-tube #{tube}\r\n", :yaml)
142
+ end
143
+
144
+ def list_tubes()
145
+ interact("list-tubes\r\n", :yaml)
146
+ end
147
+
148
+ def list_tube_used()
149
+ interact("list-tube-used\r\n", %w(USING))[0]
150
+ end
151
+
152
+ def list_tubes_watched(cached=false)
153
+ return @watch_list if cached
154
+ @watch_list = interact("list-tubes-watched\r\n", :yaml)
155
+ end
156
+
157
+ private
158
+
159
+ def interact(cmd, rfmt)
160
+ raise WaitingForJobError if @waiting
161
+ @socket.write(cmd)
162
+ return read_yaml('OK') if rfmt == :yaml
163
+ return found_job if rfmt == :job
164
+ check_resp(*rfmt)
165
+ end
166
+
167
+ def get_resp()
168
+ r = @socket.gets("\r\n")
169
+ raise EOFError if r == nil
170
+ r[0...-2]
171
+ end
172
+
173
+ def check_resp(*words)
174
+ r = get_resp()
175
+ rword, *vals = r.split(/\s+/)
176
+ if (words.size > 0) and !words.include?(rword)
177
+ raise UnexpectedResponse.classify(rword, r)
178
+ end
179
+ vals
180
+ end
181
+
182
+ def found_job()
183
+ Job.new(self, *read_job('FOUND'))
184
+ rescue NotFoundError
185
+ nil
186
+ end
187
+
188
+ def read_job(word)
189
+ id, bytes = check_resp(word).map{|s| s.to_i}
190
+ body = read_bytes(bytes)
191
+ raise 'bad trailer' if read_bytes(2) != "\r\n"
192
+ [id, body]
193
+ end
194
+
195
+ def read_yaml(word)
196
+ bytes_s, = check_resp(word)
197
+ yaml = read_bytes(bytes_s.to_i)
198
+ raise 'bad trailer' if read_bytes(2) != "\r\n"
199
+ YAML::load(yaml)
200
+ end
201
+
202
+ def read_bytes(n)
203
+ str = @socket.read(n)
204
+ raise EOFError, 'End of file reached' if str == nil
205
+ raise EOFError, 'End of file reached' if str.size < n
206
+ str
207
+ end
208
+ end
209
+
210
+ class Pool
211
+ attr_accessor :last_conn
212
+
213
+ def initialize(addrs, default_tube=nil)
214
+ @addrs = addrs
215
+ @watch_list = ['default']
216
+ @default_tube=default_tube
217
+ @watch_list = [default_tube] if default_tube
218
+ connect()
219
+ end
220
+
221
+ def connect()
222
+ @connections ||= {}
223
+ @addrs.each do |addr|
224
+ begin
225
+ if !@connections.include?(addr)
226
+ @connections[addr] = Connection.new(addr, @default_tube)
227
+ prev_watched = @connections[addr].list_tubes_watched()
228
+ to_ignore = prev_watched - @watch_list
229
+ @watch_list.each{|tube| @connections[addr].watch(tube)}
230
+ to_ignore.each{|tube| @connections[addr].ignore(tube)}
231
+ end
232
+ rescue Exception => ex
233
+ puts "#{ex.class}: #{ex}"
234
+ #puts begin ex.fixed_backtrace rescue ex.backtrace end
235
+ end
236
+ end
237
+ @connections.size
238
+ end
239
+
240
+ def open_connections()
241
+ @connections.values()
242
+ end
243
+
244
+ def last_server
245
+ @last_conn.addr
246
+ end
247
+
248
+ def put(body, pri=65536, delay=0, ttr=120)
249
+ send_to_rand_conn(:put, body, pri, delay, ttr)
250
+ end
251
+
252
+ def yput(obj, pri=65536, delay=0, ttr=120)
253
+ send_to_rand_conn(:yput, obj, pri, delay, ttr)
254
+ end
255
+
256
+ def reserve()
257
+ send_to_rand_conn(:reserve)
258
+ end
259
+
260
+ def use(tube)
261
+ send_to_all_conns(:use, tube)
262
+ end
263
+
264
+ def watch(tube)
265
+ r = send_to_all_conns(:watch, tube)
266
+ @watch_list = send_to_rand_conn(:list_tubes_watched, true)
267
+ return r
268
+ end
269
+
270
+ def ignore(tube)
271
+ r = send_to_all_conns(:ignore, tube)
272
+ @watch_list = send_to_rand_conn(:list_tubes_watched, true)
273
+ return r
274
+ end
275
+
276
+ def raw_stats()
277
+ send_to_all_conns(:stats)
278
+ end
279
+
280
+ def stats()
281
+ sum_hashes(raw_stats.values)
282
+ end
283
+
284
+ def raw_stats_tube(tube)
285
+ send_to_all_conns(:stats_tube, tube)
286
+ end
287
+
288
+ def stats_tube(tube)
289
+ sum_hashes(raw_stats_tube(tube).values)
290
+ end
291
+
292
+ def list_tubes()
293
+ send_to_all_conns(:list_tubes)
294
+ end
295
+
296
+ def list_tube_used()
297
+ send_to_all_conns(:list_tube_used)
298
+ end
299
+
300
+ def list_tubes_watched(*args)
301
+ send_to_all_conns(:list_tubes_watched, *args)
302
+ end
303
+
304
+ def remove(conn)
305
+ @connections.delete(conn.addr)
306
+ end
307
+
308
+ def close
309
+ while @connections.size > 0
310
+ addr = @connections.keys.last
311
+ conn = @connections[addr]
312
+ @connections.delete(addr)
313
+ conn.close
314
+ end
315
+ end
316
+
317
+ def peek_ready()
318
+ send_to_each_conn_first_res(:peek_ready)
319
+ end
320
+
321
+ def peek_delayed()
322
+ send_to_each_conn_first_res(:peek_delayed)
323
+ end
324
+
325
+ def peek_buried()
326
+ send_to_each_conn_first_res(:peek_buried)
327
+ end
328
+
329
+ def peek_job(id)
330
+ make_hash(send_to_all_conns(:peek_job, id))
331
+ end
332
+
333
+ private
334
+
335
+ def call_wrap(c, *args)
336
+ self.last_conn = c
337
+ c.send(*args)
338
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE, UnexpectedResponse => ex
339
+ self.remove(c)
340
+ raise ex
341
+ end
342
+
343
+ def retry_wrap(*args)
344
+ yield
345
+ rescue DrainingError
346
+ # Don't reconnect -- we're not interested in this server
347
+ retry
348
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
349
+ connect()
350
+ retry
351
+ end
352
+
353
+ def send_to_each_conn_first_res(*args)
354
+ connect()
355
+ retry_wrap{open_connections.inject(nil) {|r,c| r or call_wrap(c, *args)}}
356
+ end
357
+
358
+ def send_to_rand_conn(*args)
359
+ connect()
360
+ retry_wrap{call_wrap(pick_connection, *args)}
361
+ end
362
+
363
+ def send_to_all_conns(*args)
364
+ connect()
365
+ retry_wrap{compact_hash(map_hash(@connections){|c| call_wrap(c, *args)})}
366
+ end
367
+
368
+ def pick_connection()
369
+ open_connections[rand(open_connections.size)] or raise NotConnected
370
+ end
371
+
372
+ def make_hash(pairs)
373
+ Hash[*pairs.inject([]){|a,b|a+b}]
374
+ end
375
+
376
+ def map_hash(h)
377
+ make_hash(h.map{|k,v| [k, yield(v)]})
378
+ end
379
+
380
+ def compact_hash(hash)
381
+ hash.reject{|k,v| v == nil}
382
+ end
383
+
384
+ def sum_hashes(hs)
385
+ hs.inject({}){|a,b| a.merge(b) {|k,o,n| combine_stats(k, o, n)}}
386
+ end
387
+
388
+ DONT_ADD = Set['name', 'version', 'pid']
389
+ def combine_stats(k, a, b)
390
+ DONT_ADD.include?(k) ? Set[a] + Set[b] : a + b
391
+ end
392
+ end
393
+ end