beanstalk-client 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ == 0.1 2007-12-12
2
+
3
+ * Initial release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 FIXME full name
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -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/bag.rb
10
+ lib/beanstalk-client/connection.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,71 @@
1
+ require 'beanstalk-client/version'
2
+
3
+ AUTHOR = 'Keith Rarick' # can also be an array of Authors
4
+ EMAIL = 'kr@essembly.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
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
71
+ 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,29 @@
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
+
25
+ class UnexpectedResponse < RuntimeError
26
+ end
27
+ end
28
+
29
+ require 'beanstalk-client/connection'
@@ -0,0 +1,36 @@
1
+ # beanstalk-client/bag.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
+ class Beanstalk::Bag
19
+ def initialize(initial_size=0, &default)
20
+ @default = default
21
+ @items = []
22
+ initial_size.times{give(default.call())}
23
+ end
24
+
25
+ def give(x)
26
+ (@items << x)[-1]
27
+ end
28
+
29
+ def take()
30
+ @items.pop or @default.call()
31
+ end
32
+
33
+ def size()
34
+ @items.size
35
+ end
36
+ end
@@ -0,0 +1,265 @@
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 'beanstalk-client/bag'
22
+ require 'beanstalk-client/job'
23
+
24
+ module Beanstalk
25
+ class RawConnection
26
+ attr_reader :addr
27
+
28
+ def initialize(addr, jptr=self)
29
+ @addr = addr
30
+ @jptr = jptr
31
+ host, port = addr.split(':')
32
+ @socket = TCPSocket.new(host, port.to_i)
33
+
34
+ # Don't leak fds when we exec.
35
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
36
+ end
37
+
38
+ def put(body, pri=65536, delay=0)
39
+ @socket.write("put #{pri} #{delay} #{body.size}\r\n#{body}\r\n")
40
+ check_resp('INSERTED')
41
+ :ok
42
+ end
43
+
44
+ def yput(obj, pri=65536, delay=0)
45
+ put(YAML.dump(obj), pri, delay)
46
+ end
47
+
48
+ def peek()
49
+ @socket.write("peek\r\n")
50
+ begin
51
+ Job.new(@jptr, *read_job('FOUND'))
52
+ rescue UnexpectedResponse
53
+ nil
54
+ end
55
+ end
56
+
57
+ def peek_job(id)
58
+ @socket.write("peek #{id}\r\n")
59
+ Job.new(@jptr, *read_job('FOUND'))
60
+ end
61
+
62
+ def reserve()
63
+ @socket.write("reserve\r\n")
64
+ Job.new(@jptr, *read_job('RESERVED'))
65
+ end
66
+
67
+ def delete(id)
68
+ @socket.write("delete #{id}\r\n")
69
+ check_resp('DELETED')
70
+ :ok
71
+ end
72
+
73
+ def release(id, pri, delay)
74
+ @socket.write("release #{id} #{pri} #{delay}\r\n")
75
+ check_resp('RELEASED')
76
+ :ok
77
+ end
78
+
79
+ def bury(id, pri)
80
+ @socket.write("bury #{id} #{pri}\r\n")
81
+ check_resp('BURIED')
82
+ :ok
83
+ end
84
+
85
+ def stats()
86
+ @socket.write("stats\r\n")
87
+ read_yaml('OK')
88
+ end
89
+
90
+ def job_stats(id)
91
+ @socket.write("stats #{id}\r\n")
92
+ read_yaml('OK')
93
+ end
94
+
95
+ private
96
+
97
+ def get_resp()
98
+ r = @socket.gets("\r\n")
99
+ raise EOFError if r == nil
100
+ r[0...-2]
101
+ end
102
+
103
+ def check_resp(word)
104
+ r = get_resp()
105
+ rword, *vals = r.split(/\s+/)
106
+ raise UnexpectedResponse.new(r) if word and rword != word
107
+ vals
108
+ end
109
+
110
+ def read_job(word)
111
+ # Give the user a chance to select on multiple fds.
112
+ Beanstalk.select.call([@socket]) if Beanstalk.select
113
+
114
+ id, pri, bytes = check_resp(word).map{|s| s.to_i}
115
+ body = read_bytes(bytes)
116
+ raise 'bad trailer' if read_bytes(2) != "\r\n"
117
+ [id, pri, body]
118
+ end
119
+
120
+ def read_yaml(word)
121
+ bytes_s, = check_resp(word)
122
+ yaml = read_bytes(bytes_s.to_i)
123
+ raise 'bad trailer' if read_bytes(2) != "\r\n"
124
+ YAML::load(yaml)
125
+ end
126
+
127
+ def read_bytes(n)
128
+ str = @socket.read(n)
129
+ raise EOFError, 'End of file reached' if str == nil
130
+ raise EOFError, 'End of file reached' if str.size < n
131
+ str
132
+ end
133
+ end
134
+
135
+ # Same interface as RawConnection.
136
+ # With this you can reserve more than one job at a time.
137
+ class Connection
138
+ attr_reader :addr
139
+
140
+ def initialize(addr, jptr=self)
141
+ @addr = addr
142
+ @misc = RawConnection.new(addr, jptr)
143
+ @free = Bag.new{RawConnection.new(addr, jptr)}
144
+ @used = {}
145
+ end
146
+
147
+ def reserve()
148
+ c = @free.take()
149
+ j = c.reserve()
150
+ @used[j.id] = c
151
+ j
152
+ ensure
153
+ @free.give(c) if c and not j
154
+ end
155
+
156
+ def delete(id)
157
+ @used[id].delete(id)
158
+ @free.give(@used.delete(id))
159
+ end
160
+
161
+ def release(id, pri, delay)
162
+ @used[id].release(id, pri, delay)
163
+ @free.give(@used.delete(id))
164
+ end
165
+
166
+ def bury(id, pri)
167
+ @used[id].bury(id, pri)
168
+ @free.give(@used.delete(id))
169
+ end
170
+
171
+ def method_missing(selector, *args, &block)
172
+ @misc.send(selector, *args, &block)
173
+ end
174
+ end
175
+
176
+ class CleanupWrapper
177
+ def initialize(addr, multi)
178
+ @conn = Connection.new(addr, self)
179
+ @multi = multi
180
+ end
181
+
182
+ def method_missing(selector, *args, &block)
183
+ begin
184
+ @conn.send(selector, *args, &block)
185
+ rescue EOFError, Errno::ECONNRESET => ex
186
+ @multi.remove(@conn)
187
+ raise ex
188
+ end
189
+ end
190
+ end
191
+
192
+ class Pool
193
+ def initialize(addrs)
194
+ @addrs = addrs
195
+ connect()
196
+ end
197
+
198
+ def connect()
199
+ @connections ||= {}
200
+ @addrs.each do |addr|
201
+ begin
202
+ if !@connections.include?(addr)
203
+ puts "connecting to beanstalk at #{addr}"
204
+ @connections[addr] = CleanupWrapper.new(addr, self)
205
+ end
206
+ rescue Exception => ex
207
+ puts "#{ex.class}: #{ex}"
208
+ #puts begin ex.fixed_backtrace rescue ex.backtrace end
209
+ end
210
+ end
211
+ @connections.size
212
+ end
213
+
214
+ def open_connections()
215
+ @connections.values()
216
+ end
217
+
218
+ def put(body, pri=65536)
219
+ pick_connection.put(body, pri)
220
+ rescue EOFError, Errno::ECONNRESET
221
+ connect()
222
+ retry
223
+ end
224
+
225
+ def yput(obj, pri=65536)
226
+ pick_connection.yput(obj, pri)
227
+ rescue EOFError, Errno::ECONNRESET
228
+ connect()
229
+ retry
230
+ end
231
+
232
+ def reserve()
233
+ pick_connection.reserve()
234
+ rescue EOFError, Errno::ECONNRESET
235
+ connect()
236
+ retry
237
+ end
238
+
239
+ def raw_stats()
240
+ Hash[*@connections.map{|a,c| [a, c.stats()]}.inject([]){|a,b|a+b}]
241
+ end
242
+
243
+ def stats()
244
+ raw_stats.values.inject({}){|sums,h| sums.merge(h) {|k,o,n| o + n}}
245
+ end
246
+
247
+ def remove(conn)
248
+ @connections.delete(conn.addr)
249
+ end
250
+
251
+ def peek()
252
+ open_connections.each do |c|
253
+ job = c.peek
254
+ return job if job
255
+ end
256
+ nil
257
+ end
258
+
259
+ private
260
+
261
+ def pick_connection()
262
+ open_connections[rand(open_connections.size)] or raise 'not connected'
263
+ end
264
+ end
265
+ end