em-beanstalk 0.0.3

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/COPYING ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2009, dan sinclair. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are
5
+ met:
6
+ * Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ * Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY dan sinclair ''AS IS'' AND ANY
13
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
15
+ DISCLAIMED. IN NO EVENT SHALL dan sinclair BE LIABLE FOR ANY
16
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
17
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
18
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
19
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.rdoc ADDED
@@ -0,0 +1,73 @@
1
+ = EMJack
2
+ An attempt to wrap portions of the Beanstalk protocol with EventMachine. Every command
3
+ will return a deferrable object. That object will succeed or fail depending on the
4
+ reply returned from Beanstalk.
5
+
6
+ The current, default, errback is to print the error message received.
7
+
8
+ One thing to keep in mind. The Beanstalk protocol executes all commands serially.
9
+ So, if you send a reserve command and there are no jobs Beanstalk _won't_ process
10
+ any of the commands that come after the reserve until the reserve completes.
11
+
12
+ This is a bit of a gotcha when sending a series of reserve, delete, etc and there
13
+ are no available jobs.
14
+
15
+ = Dependencies
16
+ - EventMachine
17
+ - RSpec (to run the tests)
18
+ - EM::Spec
19
+
20
+ = Examples
21
+ EM.run {
22
+ jack = EMJack::Connection.new
23
+
24
+ jack.use('mytube') { |tube| puts "Using #{tube}" }
25
+
26
+ jack.reserve do |job|
27
+ puts job.id
28
+ process(job)
29
+
30
+ jack.delete(job) { puts "Successfully deleted" }
31
+ end
32
+
33
+ jack.put("my message", :ttr => 300) { |id| puts "put successful #{id}" }
34
+
35
+ jack.stats { |stats| puts "Server up for #{stats['uptime']} seconds" "}
36
+
37
+ jack.stats(:tube, "mytube") { |stats| puts "Total jobs #{stats['total-jobs']}" }
38
+
39
+ jack.list(:tubes) { |tubes| puts "There are #{tubes.length} tubes defined" }
40
+ }
41
+
42
+
43
+ EMJack#each_job is useful for scenarios where the client is a job worker
44
+ and intended to process jobs continuously as they become available. Once
45
+ the queue is empty, the client will block and wait for a new job.
46
+
47
+ If multiple workers connect to the queue Beanstalkd will round-robin between
48
+ the workers.
49
+ EM.run {
50
+ jack = EMJack::Connection.new
51
+
52
+ jack.each_job do |job|
53
+ puts "Got job ##{job.id}: #{job}"
54
+
55
+ if process(job)
56
+ jack.delete(job) { puts "*Deleted #{job}*" }
57
+ else
58
+ # something went horribly wrong!
59
+ end
60
+ end
61
+
62
+ def process(job)
63
+ # Some kind of job processing
64
+ end
65
+ }
66
+
67
+
68
+ = Contact
69
+ If you've got any questions, comments or bugs, please let me know. You can
70
+ contact me by email at dj2 at everburning dot com.
71
+
72
+ = Contributors
73
+ Peter Kieltyka (EMJack#each_job)
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ libdir = File.expand_path("lib")
2
+ $:.unshift(libdir) unless $:.include?(libdir)
3
+
4
+ require 'em-jack'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |s|
9
+ s.name = "em-beanstalk"
10
+ s.description = s.summary = "EventMachine client for Beanstalkd"
11
+ s.email = "dan@postrank.com"
12
+ s.homepage = "http://github.com/joshbuddy/em-jack"
13
+ s.authors = ["Dan"]
14
+ s.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
15
+ s.add_dependency 'eventmachine'
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ require 'spec'
23
+ require 'spec/rake/spectask'
24
+ task :spec => 'spec:all'
25
+ namespace(:spec) do
26
+ Spec::Rake::SpecTask.new(:all) do |t|
27
+ t.spec_opts ||= []
28
+ t.spec_opts << "-rubygems"
29
+ t.spec_opts << "--options" << "spec/spec.opts"
30
+ t.spec_files = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ end
34
+
35
+ desc "Run all examples with RCov"
36
+ Spec::Rake::SpecTask.new('spec_with_rcov') do |t|
37
+ t.spec_files = FileList['spec/**/*.rb']
38
+ t.rcov = true
39
+ t.rcov_opts = ['--exclude', 'spec']
40
+ end
41
+
42
+ require 'rake/rdoctask'
43
+ desc "Generate documentation"
44
+ Rake::RDocTask.new do |rd|
45
+ rd.main = "README.rdoc"
46
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
47
+ rd.rdoc_dir = 'rdoc'
48
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
data/lib/em-jack.rb ADDED
@@ -0,0 +1,12 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'em-jack/job'
4
+ require 'em-jack/errors'
5
+ require 'em-jack/beanstalk_connection'
6
+ require 'em-jack/connection'
7
+
8
+ module EMJack
9
+ module VERSION
10
+ STRING = '0.0.3'
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ require 'eventmachine'
2
+
3
+ module EMJack
4
+ class BeanstalkConnection < EM::Connection
5
+ attr_accessor :client
6
+
7
+ def connection_completed
8
+ @client.connected
9
+ end
10
+
11
+ def receive_data(data)
12
+ @client.received(data)
13
+ end
14
+
15
+ def send(command, *args)
16
+ cmd = command.to_s
17
+ cmd << " #{args.join(" ")}" unless args.length == 0
18
+ cmd << "\r\n"
19
+ send_data(cmd)
20
+ end
21
+
22
+ def send_with_data(command, data, *args)
23
+ send_data("#{command.to_s} #{args.join(" ")}\r\n#{data}\r\n")
24
+ end
25
+
26
+ def unbind
27
+ @client.disconnected
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,281 @@
1
+ require 'eventmachine'
2
+ require 'yaml'
3
+
4
+ module EMJack
5
+ class Connection
6
+
7
+ attr_accessor :host, :port
8
+ attr_reader :default_priority, :default_delay, :default_ttr
9
+
10
+ def initialize(opts = nil)
11
+ @host = opts && opts[:host] || 'localhost'
12
+ @port = opts && opts[:port] || 11300
13
+ @tube = opts && opts[:tube]
14
+ @retry_count = opts && opts[:retry_count] || 5
15
+ @default_priority = opts && opts[:default_priority] || 65536
16
+ @default_delay = opts && opts[:default_delay] || 0
17
+ @default_ttr = opts && opts[:default_ttr] || 300
18
+ @default_timeout = opts && opts[:timeout] || 5
19
+
20
+ @used_tube = 'default'
21
+ @watched_tubes = [@used_tube]
22
+
23
+ @data = ""
24
+ @retries = 0
25
+ @in_reserve = false
26
+ @deferrables = []
27
+
28
+ @conn = EM::connect(host, port, EMJack::BeanstalkConnection) do |conn|
29
+ conn.client = self
30
+ conn.comm_inactivity_timeout = 0
31
+ conn.pending_connect_timeout = @default_timeout
32
+ end
33
+
34
+ unless @tube.nil?
35
+ use(@tube)
36
+ watch(@tube)
37
+ end
38
+ end
39
+
40
+ def close
41
+ @disconnect_manually = true
42
+ @conn.close_connection
43
+ end
44
+
45
+ def drain!(&block)
46
+ stats do |stats|
47
+ stats['current-jobs-ready'].zero? ?
48
+ EM.next_tick(&block) :
49
+ reserve{|job| job.delete{ drain!(&block) }}
50
+ end
51
+ end
52
+
53
+ def use(tube, &block)
54
+ return if @used_tube == tube
55
+ @used_tube = tube
56
+ @conn.send(:use, tube)
57
+ add_deferrable(&block)
58
+
59
+ end
60
+
61
+ def watch(tube, &block)
62
+ return if @watched_tubes.include?(tube)
63
+ @watched_tubes.push(tube)
64
+ @conn.send(:watch, tube)
65
+ add_deferrable(&block)
66
+ end
67
+
68
+ def ignore(tube, &block)
69
+ return if not @watched_tubes.include?(tube)
70
+ @watched_tubes.delete(tube)
71
+ @conn.send(:ignore, tube)
72
+ add_deferrable(&block)
73
+ end
74
+
75
+ def reserve(timeout = nil, &block)
76
+ if timeout
77
+ @conn.send(:'reserve-with-timeout', timeout)
78
+ else
79
+ @conn.send(:reserve)
80
+ end
81
+ add_deferrable(&block)
82
+ end
83
+
84
+ def each_job(timeout = nil, &block)
85
+ work = Proc.new do
86
+ r = reserve(timeout)
87
+ r.callback do |job|
88
+ block.call(job)
89
+ EM.next_tick { work.call }
90
+ end
91
+ end
92
+ work.call
93
+ end
94
+
95
+ def stats(type = nil, val = nil, &block)
96
+ case(type)
97
+ when nil then @conn.send(:stats)
98
+ when :tube then @conn.send(:'stats-tube', val)
99
+ when :job then @conn.send(:'stats-job', job_id(val))
100
+ else raise EMJack::InvalidCommand.new
101
+ end
102
+ add_deferrable(&block)
103
+ end
104
+
105
+ def job_id(val)
106
+ case val
107
+ when Job
108
+ val.id
109
+ else
110
+ val
111
+ end
112
+ end
113
+
114
+ def list(type = nil, &block)
115
+ case(type)
116
+ when nil then @conn.send(:'list-tubes')
117
+ when :use, :used then @conn.send(:'list-tube-used')
118
+ when :watch, :watched then @conn.send(:'list-tubes-watched')
119
+ else raise EMJack::InvalidCommand.new
120
+ end
121
+ add_deferrable(&block)
122
+ end
123
+
124
+ def delete(val, &block)
125
+ return unless val
126
+ @conn.send(:delete, job_id(val))
127
+ add_deferrable(&block)
128
+ end
129
+
130
+ def put(msg, opts = nil, &block)
131
+ case msg
132
+ when Job
133
+ priority = opts && opts[:priority] || msg.priority
134
+ delay = opts && opts[:delay] || msg.delay
135
+ ttr = opts && opts[:ttr] || msg.ttr
136
+ body = msg.body
137
+ else
138
+ priority = opts && opts[:priority] || default_priority
139
+ delay = opts && opts[:delay] || default_delay
140
+ ttr = opts && opts[:ttr] || default_ttr
141
+ body = msg.to_s
142
+ end
143
+
144
+ priority = default_priority if priority < 0
145
+ priority = 2 ** 32 if priority > (2 ** 32)
146
+ delay = default_delay if delay < 0
147
+ ttr = default_ttr if ttr < 0
148
+
149
+ @conn.send_with_data(:put, body, priority, delay, ttr, body.size)
150
+ add_deferrable(&block)
151
+ end
152
+
153
+ def release(job, &block)
154
+ return if job.nil?
155
+ @conn.send(:release, job.jobid, 0, 0)
156
+ add_deferrable(&block)
157
+ end
158
+
159
+ def connected
160
+ @retries = 0
161
+ end
162
+
163
+ def disconnected
164
+ @deferrables.each {|d| d.fail }
165
+ unless @disconnect_manually
166
+ raise EMJack::Disconnected if @retries >= @retry_count
167
+ @retries += 1
168
+ EM.add_timer(1) { reconnect }
169
+ end
170
+ end
171
+
172
+ def reconnect
173
+ @disconnect_manually = false
174
+ @conn.reconnect(host, port)
175
+ end
176
+
177
+ def add_deferrable(&block)
178
+ df = EM::DefaultDeferrable.new
179
+ df.errback do
180
+ if @error_callback
181
+ @error_callback.call
182
+ else
183
+ puts "ERROR"
184
+ end
185
+ end
186
+
187
+ @deferrables.push(df)
188
+ df.callback(&block) if block
189
+ df
190
+ end
191
+
192
+ def on_error(&block)
193
+ @error_callback = block
194
+ end
195
+
196
+ def received(data)
197
+ @data << data
198
+
199
+ until @data.empty?
200
+ idx = @data.index(/(.*?\r\n)/)
201
+ break if idx.nil?
202
+
203
+ first = $1
204
+
205
+ handled = false
206
+ %w(OUT_OF_MEMORY INTERNAL_ERROR DRAINING BAD_FORMAT
207
+ UNKNOWN_COMMAND EXPECTED_CRLF JOB_TOO_BIG DEADLINE_SOON
208
+ TIMED_OUT NOT_FOUND).each do |cmd|
209
+ next unless first =~ /^#{cmd}\r\n/i
210
+ df = @deferrables.shift
211
+ df.fail(cmd.downcase.to_sym)
212
+
213
+ @data = @data[(cmd.length + 2)..-1]
214
+ handled = true
215
+ break
216
+ end
217
+ next if handled
218
+
219
+ case (first)
220
+ when /^DELETED\r\n/ then
221
+ df = @deferrables.shift
222
+ df.succeed
223
+
224
+ when /^INSERTED\s+(\d+)\r\n/ then
225
+ df = @deferrables.shift
226
+ df.succeed($1.to_i)
227
+
228
+ when /^RELEASED\r\n/ then
229
+ df = @deferrables.shift
230
+ df.succeed
231
+
232
+ when /^BURIED\s+(\d+)\r\n/ then
233
+ df = @deferrables.shift
234
+ df.fail(:buried, $1.to_i)
235
+
236
+ when /^USING\s+(.*)\r\n/ then
237
+ df = @deferrables.shift
238
+ df.succeed($1)
239
+
240
+ when /^WATCHING\s+(\d+)\r\n/ then
241
+ df = @deferrables.shift
242
+ df.succeed($1.to_i)
243
+
244
+ when /^OK\s+(\d+)\r\n/ then
245
+ bytes = $1.to_i
246
+
247
+ body, @data = extract_body(bytes, @data)
248
+ break if body.nil?
249
+
250
+ df = @deferrables.shift
251
+ df.succeed(YAML.load(body))
252
+ next
253
+
254
+ when /^RESERVED\s+(\d+)\s+(\d+)\r\n/ then
255
+ id = $1.to_i
256
+ bytes = $2.to_i
257
+
258
+ body, @data = extract_body(bytes, @data)
259
+ break if body.nil?
260
+
261
+ df = @deferrables.shift
262
+ job = EMJack::Job.new(self, id, body)
263
+ df.succeed(job)
264
+ next
265
+ else
266
+ break
267
+ end
268
+ @data.slice!(0, first.size)
269
+ end
270
+ end
271
+
272
+ def extract_body(bytes, data)
273
+ rem = data[(data.index(/\r\n/) + 2)..-1]
274
+ return [nil, data] if rem.length < bytes
275
+ body = rem[0..(bytes - 1)]
276
+ data = rem[(bytes + 2)..-1]
277
+ data = "" if data.nil?
278
+ [body, data]
279
+ end
280
+ end
281
+ end
@@ -0,0 +1,7 @@
1
+ module EMJack
2
+ class Disconnected < RuntimeError
3
+ end
4
+
5
+ class InvalidCommand < RuntimeError
6
+ end
7
+ end
@@ -0,0 +1,30 @@
1
+ module EMJack
2
+ class Job
3
+
4
+ attr_reader :id, :conn
5
+ attr_accessor :body, :ttr, :priority, :delay
6
+
7
+ def initialize(conn, id, body)
8
+ @conn = conn
9
+ @id = id && Integer(id)
10
+ @body = body
11
+ @priority = conn && conn.default_priority
12
+ @delay = conn && conn.default_delay
13
+ @ttr = conn && conn.default_ttr
14
+ end
15
+
16
+ alias_method :jobid, :id
17
+
18
+ def delete(&block)
19
+ conn.delete(self, &block)
20
+ end
21
+
22
+ def stats(&block)
23
+ conn.stats(:job, self, &block)
24
+ end
25
+
26
+ def to_s
27
+ "#{id} -- #{body.inspect}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,374 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe EMJack::Connection do
4
+ before(:each) do
5
+ @connection_mock = mock(:conn)
6
+ EM.should_receive(:connect).and_return(@connection_mock)
7
+ end
8
+
9
+ it 'should use a default host of "localhost"' do
10
+ conn = EMJack::Connection.new
11
+ conn.host.should == 'localhost'
12
+ end
13
+
14
+ it 'should use a default port of 11300' do
15
+ conn = EMJack::Connection.new
16
+ conn.port.should == 11300
17
+ end
18
+
19
+ it 'should watch and use a provided tube on connect' do
20
+ @connection_mock.should_receive(:send).once.with(:use, "mytube")
21
+ @connection_mock.should_receive(:send).once.with(:watch, "mytube")
22
+ conn = EMJack::Connection.new(:tube => "mytube")
23
+ end
24
+
25
+ it 'should send the "use" command' do
26
+ @connection_mock.should_receive(:send).once.with(:use, "mytube")
27
+ conn = EMJack::Connection.new
28
+ conn.use("mytube")
29
+ end
30
+
31
+ it 'should not send the use command to the currently used tube' do
32
+ @connection_mock.should_receive(:send).once.with(:use, "mytube")
33
+ conn = EMJack::Connection.new
34
+ conn.use("mytube")
35
+ conn.use("mytube")
36
+ end
37
+
38
+ it 'should send the "watch" command' do
39
+ @connection_mock.should_receive(:send).once.with(:watch, "mytube")
40
+ conn = EMJack::Connection.new
41
+ conn.watch("mytube")
42
+ end
43
+
44
+ it 'should not send the watch command for a tube currently watched' do
45
+ @connection_mock.should_receive(:send).once.with(:watch, "mytube")
46
+ conn = EMJack::Connection.new
47
+ conn.watch("mytube")
48
+ conn.watch("mytube")
49
+ end
50
+
51
+ it 'should send the "put" command' do
52
+ msg = "my message"
53
+ @connection_mock.should_receive(:send_with_data).once.
54
+ with(:put, msg, anything, anything, anything, msg.length)
55
+ conn = EMJack::Connection.new
56
+ conn.put(msg)
57
+ end
58
+
59
+ it 'should default the delay, priority and ttr settings' do
60
+ @connection_mock.should_receive(:send_with_data).once.
61
+ with(:put, anything, 65536, 0, 300, anything)
62
+ conn = EMJack::Connection.new
63
+ conn.put("msg")
64
+ end
65
+
66
+ it 'should accept a delay setting' do
67
+ @connection_mock.should_receive(:send_with_data).once.
68
+ with(:put, anything, anything, 42, anything, anything)
69
+ conn = EMJack::Connection.new
70
+ conn.put("msg", :delay => 42)
71
+ end
72
+
73
+ it 'should accept a ttr setting' do
74
+ @connection_mock.should_receive(:send_with_data).once.
75
+ with(:put, anything, anything, anything, 999, anything)
76
+ conn = EMJack::Connection.new
77
+ conn.put("msg", :ttr => 999)
78
+ end
79
+
80
+ it 'should accept a priority setting' do
81
+ @connection_mock.should_receive(:send_with_data).once.
82
+ with(:put, anything, 233, anything, anything, anything)
83
+ conn = EMJack::Connection.new
84
+ conn.put("msg", :priority => 233)
85
+ end
86
+
87
+ it 'shoudl accept a priority, delay and ttr setting' do
88
+ @connection_mock.should_receive(:send_with_data).once.
89
+ with(:put, anything, 99, 42, 2000, anything)
90
+ conn = EMJack::Connection.new
91
+ conn.put("msg", :priority => 99, :delay => 42, :ttr => 2000)
92
+ end
93
+
94
+ it 'should force delay to be >= 0' do
95
+ @connection_mock.should_receive(:send_with_data).once.
96
+ with(:put, anything, anything, 0, anything, anything)
97
+ conn = EMJack::Connection.new
98
+ conn.put("msg", :delay => -42)
99
+ end
100
+
101
+ it 'should force ttr to be >= 0' do
102
+ @connection_mock.should_receive(:send_with_data).once.
103
+ with(:put, anything, anything, anything, 300, anything)
104
+ conn = EMJack::Connection.new
105
+ conn.put("msg", :ttr => -42)
106
+ end
107
+
108
+ it 'should force priority to be >= 0' do
109
+ @connection_mock.should_receive(:send_with_data).once.
110
+ with(:put, anything, 65536, anything, anything, anything)
111
+ conn = EMJack::Connection.new
112
+ conn.put("msg", :priority => -42)
113
+ end
114
+
115
+ it 'should force priority to be < 2**32' do
116
+ @connection_mock.should_receive(:send_with_data).once.
117
+ with(:put, anything, (2 ** 32), anything, anything, anything)
118
+ conn = EMJack::Connection.new
119
+ conn.put("msg", :priority => (2 ** 32 + 1))
120
+ end
121
+
122
+ it 'should handle a non-string provided as the put message' do
123
+ msg = 22
124
+ @connection_mock.should_receive(:send_with_data).once.
125
+ with(:put, msg.to_s, anything, anything, anything, msg.to_s.length)
126
+ conn = EMJack::Connection.new
127
+ conn.put(msg)
128
+ end
129
+
130
+ it 'should send the "delete" command' do
131
+ @connection_mock.should_receive(:send).once.with(:delete, 1)
132
+ job = EMJack::Job.new(nil, 1, "body")
133
+ conn = EMJack::Connection.new
134
+ conn.delete(job)
135
+ end
136
+
137
+ it 'should handle a nil job sent to the "delete" command' do
138
+ @connection_mock.should_not_receive(:send).with(:delete, nil)
139
+ conn = EMJack::Connection.new
140
+ conn.delete(nil)
141
+ end
142
+
143
+ it 'should send the "reserve" command' do
144
+ @connection_mock.should_receive(:send).with(:reserve)
145
+ conn = EMJack::Connection.new
146
+ conn.reserve
147
+ end
148
+
149
+ it 'should raise exception if reconnect fails more then RETRY_COUNT times' do
150
+ EM.should_receive(:add_timer).exactly(5).times
151
+
152
+ conn = EMJack::Connection.new
153
+ 5.times { conn.disconnected }
154
+ lambda { conn.disconnected }.should raise_error(EMJack::Disconnected)
155
+ end
156
+
157
+ it 'should reset the retry count on connection' do
158
+ EM.should_receive(:add_timer).at_least(1).times
159
+
160
+ conn = EMJack::Connection.new
161
+ 5.times { conn.disconnected }
162
+ conn.connected
163
+ lambda { conn.disconnected }.should_not raise_error(EMJack::Disconnected)
164
+ end
165
+
166
+ %w(OUT_OF_MEMORY INTERNAL_ERROR DRAINING BAD_FORMAT
167
+ UNKNOWN_COMMAND EXPECTED_CRLF JOB_TOO_BIG DEADLINE_SOON
168
+ TIMED_OUT NOT_FOUND).each do |cmd|
169
+ it "should handle #{cmd} messages" do
170
+ conn = EMJack::Connection.new
171
+
172
+ df = conn.add_deferrable
173
+ df.should_receive(:fail).with(cmd.downcase.to_sym)
174
+
175
+ conn.received("#{cmd}\r\n")
176
+ end
177
+ end
178
+
179
+ it 'should handle deleted messages' do
180
+ conn = EMJack::Connection.new
181
+
182
+ df = conn.add_deferrable
183
+ df.should_receive(:succeed)
184
+
185
+ conn.received("DELETED\r\n")
186
+ end
187
+
188
+ it 'should handle inserted messages' do
189
+ conn = EMJack::Connection.new
190
+
191
+ df = conn.add_deferrable
192
+ df.should_receive(:succeed).with(40)
193
+
194
+ conn.received("INSERTED 40\r\n")
195
+ end
196
+
197
+ it 'should handle buried messages' do
198
+ conn = EMJack::Connection.new
199
+
200
+ df = conn.add_deferrable
201
+ df.should_receive(:fail).with(:buried, 40)
202
+
203
+ conn.received("BURIED 40\r\n")
204
+ end
205
+
206
+ it 'should handle using messages' do
207
+ conn = EMJack::Connection.new
208
+
209
+ df = conn.add_deferrable
210
+ df.should_receive(:succeed).with("mytube")
211
+
212
+ conn.received("USING mytube\r\n")
213
+ end
214
+
215
+ it 'should handle watching messages' do
216
+ conn = EMJack::Connection.new
217
+
218
+ df = conn.add_deferrable
219
+ df.should_receive(:succeed).with(24)
220
+
221
+ conn.received("WATCHING 24\r\n")
222
+ end
223
+
224
+ it 'should handle reserved messages' do
225
+ conn = EMJack::Connection.new
226
+
227
+ msg = "This is my message"
228
+
229
+ df = conn.add_deferrable
230
+ df.should_receive(:succeed).with do |job|
231
+ job.class.should == EMJack::Job
232
+ job.jobid.should == 42
233
+ job.body.should == msg
234
+ end
235
+
236
+ conn.received("RESERVED 42 #{msg.length}\r\n#{msg}\r\n")
237
+ end
238
+
239
+ it 'should handle receiving multiple replies in one packet' do
240
+ conn = EMJack::Connection.new
241
+
242
+ df = conn.add_deferrable
243
+ df.should_receive(:succeed).with(24)
244
+
245
+ df2 = conn.add_deferrable
246
+ df2.should_receive(:succeed).with("mytube")
247
+
248
+ conn.received("WATCHING 24\r\nUSING mytube\r\n")
249
+ end
250
+
251
+ it 'should handle receiving data in chunks' do
252
+ conn = EMJack::Connection.new
253
+
254
+ msg1 = "First half of the message\r\n"
255
+ msg2 = "Last half of the message"
256
+
257
+ df = conn.add_deferrable
258
+ df.should_receive(:succeed).with do |job|
259
+ job.body.should == "#{msg1}#{msg2}"
260
+ end
261
+
262
+ conn.received("RESERVED 9 #{(msg1 + msg2).length}\r\n#{msg1}")
263
+ conn.received("#{msg2}\r\n")
264
+ end
265
+
266
+ it 'should send the stat command' do
267
+ @connection_mock.should_receive(:send).once.with(:stats)
268
+ conn = EMJack::Connection.new
269
+ conn.stats
270
+ end
271
+
272
+ it 'should handle receiving the OK command' do
273
+ conn = EMJack::Connection.new
274
+
275
+ msg =<<-HERE
276
+ ---
277
+ current-jobs-urgent: 42
278
+ current-jobs-ready: 92
279
+ current-jobs-reserved: 18
280
+ current-jobs-delayed: 7
281
+ current-jobs-buried: 0
282
+ pid: 416
283
+ version: dev
284
+ HERE
285
+
286
+ df = conn.add_deferrable
287
+ df.should_receive(:succeed).with do |stats|
288
+ stats['current-jobs-urgent'].should == 42
289
+ stats['current-jobs-ready'].should == 92
290
+ stats['current-jobs-reserved'].should == 18
291
+ stats['current-jobs-delayed'].should == 7
292
+ stats['current-jobs-buried'].should == 0
293
+ stats['pid'].should == 416
294
+ stats['version'].should == 'dev'
295
+ end
296
+
297
+ conn.received("OK #{msg.length}\r\n#{msg}\r\n")
298
+ end
299
+
300
+ it 'should support job stats' do
301
+ job = EMJack::Job.new(nil, 42, "blah")
302
+
303
+ @connection_mock.should_receive(:send).once.with(:'stats-job', 42)
304
+ conn = EMJack::Connection.new
305
+ conn.stats(:job, job)
306
+ end
307
+
308
+ it 'should support tube stats' do
309
+ @connection_mock.should_receive(:send).once.with(:'stats-tube', "mytube")
310
+ conn = EMJack::Connection.new
311
+ conn.stats(:tube, "mytube")
312
+ end
313
+
314
+ it 'should throw exception on invalid stats command' do
315
+ @connection_mock.should_not_receive(:send)
316
+ conn = EMJack::Connection.new
317
+ lambda { conn.stats(:blah) }.should raise_error(EMJack::InvalidCommand)
318
+ end
319
+
320
+ it 'should support listing tubes' do
321
+ @connection_mock.should_receive(:send).once.with(:'list-tubes')
322
+ conn = EMJack::Connection.new
323
+ conn.list
324
+ end
325
+
326
+ it 'should support listing tube used' do
327
+ @connection_mock.should_receive(:send).once.with(:'list-tube-used')
328
+ conn = EMJack::Connection.new
329
+ conn.list(:used)
330
+ end
331
+
332
+ it 'should support listing tubes watched' do
333
+ @connection_mock.should_receive(:send).once.with(:'list-tubes-watched')
334
+ conn = EMJack::Connection.new
335
+ conn.list(:watched)
336
+ end
337
+
338
+ it 'should throw exception on invalid list command' do
339
+ @connection_mock.should_not_receive(:send)
340
+ conn = EMJack::Connection.new
341
+ lambda { conn.list(:blah) }.should raise_error(EMJack::InvalidCommand)
342
+ end
343
+
344
+ it 'should accept a response broken over multiple packets' do
345
+ conn = EMJack::Connection.new
346
+
347
+ msg1 = "First half of the message\r\n"
348
+ msg2 = "Last half of the message"
349
+
350
+ df = conn.add_deferrable
351
+ df.should_receive(:succeed).with do |job|
352
+ job.body.should == "#{msg1}#{msg2}"
353
+ end
354
+
355
+ conn.received("RESERVED 9 ")
356
+ conn.received("#{(msg1 + msg2).length}")
357
+ conn.received("\r\n#{msg1}#{msg2}\r\n")
358
+ end
359
+
360
+ it 'should accept a response broken over multiple packets' do
361
+ conn = EMJack::Connection.new
362
+
363
+ msg1 = "First half of the message\r\n"
364
+ msg2 = "Last half of the message"
365
+
366
+ df = conn.add_deferrable
367
+ df.should_receive(:succeed).with do |job|
368
+ job.body.should == "#{msg1}#{msg2}"
369
+ end
370
+
371
+ conn.received("RESERVED 9 #{(msg1 + msg2).length}\r\n#{msg1}#{msg2}")
372
+ conn.received("\r\n")
373
+ end
374
+ end
@@ -0,0 +1,365 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe EMJack::Connection, "integration" do
4
+ include EM::Spec
5
+
6
+ it 'should use a default host of "localhost" and port of 11300' do
7
+ conn = EMJack::Connection.new
8
+ conn.host.should == 'localhost'
9
+ conn.port.should == 11300
10
+ done
11
+ end
12
+
13
+ it 'should watch and use a provided tube on connect' do
14
+ conn = EMJack::Connection.new
15
+ conn.watch('my-lovely-tube') do
16
+ conn.list(:watched) do |watched|
17
+ watched.should include("my-lovely-tube")
18
+ done
19
+ end
20
+ end
21
+ end
22
+
23
+ it 'should send the "use" command' do
24
+ conn = EMJack::Connection.new
25
+ conn.use('my-lovely-tube') do
26
+ conn.list(:used) do |used|
27
+ used.should include("my-lovely-tube")
28
+ done
29
+ end
30
+ end
31
+ end
32
+
33
+ it "should put a job" do
34
+ conn = EMJack::Connection.new
35
+ conn.put('myjob') do
36
+ conn.reserve do |job|
37
+ job.body.should == 'myjob'
38
+ job.delete { done }
39
+ end
40
+ end
41
+ end
42
+
43
+ it "should drain the queue" do
44
+ conn = EMJack::Connection.new
45
+ conn.put('myjob')
46
+ conn.put('myjob')
47
+ conn.put('myjob')
48
+ conn.put('myjob')
49
+ conn.put('myjob')
50
+ conn.drain!{
51
+ conn.stats do |stats|
52
+ stats['current-jobs-ready'].should == 0
53
+ done
54
+ end
55
+ }
56
+ end
57
+
58
+ it 'should default the delay, priority and ttr settings' do
59
+ conn = EMJack::Connection.new
60
+ conn.put('myjob') do
61
+ conn.reserve do |job|
62
+ job.delay.should == conn.default_delay
63
+ job.ttr.should == conn.default_ttr
64
+ job.priority.should == conn.default_priority
65
+ job.delete { done }
66
+ end
67
+ end
68
+ end
69
+
70
+ it 'should accept a delay setting' do
71
+ conn = EMJack::Connection.new
72
+ start = Time.new.to_f
73
+ conn.put('mydelayedjob', :delay => 3) do |id|
74
+ conn.reserve do |job|
75
+ (Time.new.to_f - start).should be_close(3, 0.1)
76
+ job.id.should == id
77
+ job.delete { done }
78
+ end
79
+ end
80
+ end
81
+
82
+ it 'should accept a ttr setting' do
83
+ conn = EMJack::Connection.new
84
+ conn.put('mydelayedjob', :ttr => 1) do |id|
85
+
86
+ conn.reserve do |job|
87
+ job.id.should == id
88
+ EM.add_timer(3) do
89
+ conn2 = EMJack::Connection.new
90
+ conn2.reserve do |job2|
91
+ job2.id.should == id
92
+ job2.delete { done }
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ it 'should accept a priority setting' do
100
+ conn = EMJack::Connection.new
101
+ conn.put('myhighpriorityjob', :priority => 800) do |low_id|
102
+ conn.put('myhighpriorityjob', :priority => 300) do |high_id|
103
+ conn.reserve do |job|
104
+ job.id.should == high_id
105
+ job.delete { conn.delete(low_id){done}}
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ it 'should handle a non-string provided as the put message' do
112
+ conn = EMJack::Connection.new
113
+ conn.put(22) do |id|
114
+ conn.reserve do |job|
115
+ job.body.should == '22'
116
+ job.delete {done}
117
+ end
118
+ end
119
+ end
120
+ #
121
+ #it 'should send the "delete" command' do
122
+ # @connection_mock.should_receive(:send).once.with(:delete, 1)
123
+ # job = EMJack::Job.new(nil, 1, "body")
124
+ # conn = EMJack::Connection.new
125
+ # conn.delete(job)
126
+ #end
127
+ #
128
+ #it 'should handle a nil job sent to the "delete" command' do
129
+ # @connection_mock.should_not_receive(:send).with(:delete, nil)
130
+ # conn = EMJack::Connection.new
131
+ # conn.delete(nil)
132
+ #end
133
+ #
134
+ #it 'should send the "reserve" command' do
135
+ # @connection_mock.should_receive(:send).with(:reserve)
136
+ # conn = EMJack::Connection.new
137
+ # conn.reserve
138
+ #end
139
+ #
140
+ #it 'should raise exception if reconnect fails more then RETRY_COUNT times' do
141
+ # EM.should_receive(:add_timer).exactly(5).times
142
+ #
143
+ # conn = EMJack::Connection.new
144
+ # 5.times { conn.disconnected }
145
+ # lambda { conn.disconnected }.should raise_error(EMJack::Disconnected)
146
+ #end
147
+ #
148
+ #it 'should reset the retry count on connection' do
149
+ # EM.should_receive(:add_timer).at_least(1).times
150
+ #
151
+ # conn = EMJack::Connection.new
152
+ # 5.times { conn.disconnected }
153
+ # conn.connected
154
+ # lambda { conn.disconnected }.should_not raise_error(EMJack::Disconnected)
155
+ #end
156
+ #
157
+ #%w(OUT_OF_MEMORY INTERNAL_ERROR DRAINING BAD_FORMAT
158
+ # UNKNOWN_COMMAND EXPECTED_CRLF JOB_TOO_BIG DEADLINE_SOON
159
+ # TIMED_OUT NOT_FOUND).each do |cmd|
160
+ # it "should handle #{cmd} messages" do
161
+ # conn = EMJack::Connection.new
162
+ #
163
+ # df = conn.add_deferrable
164
+ # df.should_receive(:fail).with(cmd.downcase.to_sym)
165
+ #
166
+ # conn.received("#{cmd}\r\n")
167
+ # end
168
+ #end
169
+ #
170
+ #it 'should handle deleted messages' do
171
+ # conn = EMJack::Connection.new
172
+ #
173
+ # df = conn.add_deferrable
174
+ # df.should_receive(:succeed)
175
+ #
176
+ # conn.received("DELETED\r\n")
177
+ #end
178
+ #
179
+ #it 'should handle inserted messages' do
180
+ # conn = EMJack::Connection.new
181
+ #
182
+ # df = conn.add_deferrable
183
+ # df.should_receive(:succeed).with(40)
184
+ #
185
+ # conn.received("INSERTED 40\r\n")
186
+ #end
187
+ #
188
+ #it 'should handle buried messages' do
189
+ # conn = EMJack::Connection.new
190
+ #
191
+ # df = conn.add_deferrable
192
+ # df.should_receive(:fail).with(:buried, 40)
193
+ #
194
+ # conn.received("BURIED 40\r\n")
195
+ #end
196
+ #
197
+ #it 'should handle using messages' do
198
+ # conn = EMJack::Connection.new
199
+ #
200
+ # df = conn.add_deferrable
201
+ # df.should_receive(:succeed).with("mytube")
202
+ #
203
+ # conn.received("USING mytube\r\n")
204
+ #end
205
+ #
206
+ #it 'should handle watching messages' do
207
+ # conn = EMJack::Connection.new
208
+ #
209
+ # df = conn.add_deferrable
210
+ # df.should_receive(:succeed).with(24)
211
+ #
212
+ # conn.received("WATCHING 24\r\n")
213
+ #end
214
+ #
215
+ #it 'should handle reserved messages' do
216
+ # conn = EMJack::Connection.new
217
+ #
218
+ # msg = "This is my message"
219
+ #
220
+ # df = conn.add_deferrable
221
+ # df.should_receive(:succeed).with do |job|
222
+ # job.class.should == EMJack::Job
223
+ # job.id.should == 42
224
+ # job.body.should == msg
225
+ # end
226
+ #
227
+ # conn.received("RESERVED 42 #{msg.length}\r\n#{msg}\r\n")
228
+ #end
229
+ #
230
+ #it 'should handle receiving multiple replies in one packet' do
231
+ # conn = EMJack::Connection.new
232
+ #
233
+ # df = conn.add_deferrable
234
+ # df.should_receive(:succeed).with(24)
235
+ #
236
+ # df2 = conn.add_deferrable
237
+ # df2.should_receive(:succeed).with("mytube")
238
+ #
239
+ # conn.received("WATCHING 24\r\nUSING mytube\r\n")
240
+ #end
241
+ #
242
+ #it 'should handle receiving data in chunks' do
243
+ # conn = EMJack::Connection.new
244
+ #
245
+ # msg1 = "First half of the message\r\n"
246
+ # msg2 = "Last half of the message"
247
+ #
248
+ # df = conn.add_deferrable
249
+ # df.should_receive(:succeed).with do |job|
250
+ # job.body.should == "#{msg1}#{msg2}"
251
+ # end
252
+ #
253
+ # conn.received("RESERVED 9 #{(msg1 + msg2).length}\r\n#{msg1}")
254
+ # conn.received("#{msg2}\r\n")
255
+ #end
256
+ #
257
+ #it 'should send the stat command' do
258
+ # @connection_mock.should_receive(:send).once.with(:stats)
259
+ # conn = EMJack::Connection.new
260
+ # conn.stats
261
+ #end
262
+ #
263
+ #it 'should handle receiving the OK command' do
264
+ # conn = EMJack::Connection.new
265
+ #
266
+ # msg =<<-HERE
267
+ #---
268
+ #current-jobs-urgent: 42
269
+ #current-jobs-ready: 92
270
+ #current-jobs-reserved: 18
271
+ #current-jobs-delayed: 7
272
+ #current-jobs-buried: 0
273
+ #pid: 416
274
+ #version: dev
275
+ #HERE
276
+ #
277
+ # df = conn.add_deferrable
278
+ # df.should_receive(:succeed).with do |stats|
279
+ # stats['current-jobs-urgent'].should == 42
280
+ # stats['current-jobs-ready'].should == 92
281
+ # stats['current-jobs-reserved'].should == 18
282
+ # stats['current-jobs-delayed'].should == 7
283
+ # stats['current-jobs-buried'].should == 0
284
+ # stats['pid'].should == 416
285
+ # stats['version'].should == 'dev'
286
+ # end
287
+ #
288
+ # conn.received("OK #{msg.length}\r\n#{msg}\r\n")
289
+ #end
290
+ #
291
+ #it 'should support job stats' do
292
+ # job = EMJack::Job.new(nil, 42, "blah")
293
+ #
294
+ # @connection_mock.should_receive(:send).once.with(:'stats-job', 42)
295
+ # conn = EMJack::Connection.new
296
+ # conn.stats(:job, job)
297
+ #end
298
+ #
299
+ #it 'should support tube stats' do
300
+ # @connection_mock.should_receive(:send).once.with(:'stats-tube', "mytube")
301
+ # conn = EMJack::Connection.new
302
+ # conn.stats(:tube, "mytube")
303
+ #end
304
+ #
305
+ #it 'should throw exception on invalid stats command' do
306
+ # @connection_mock.should_not_receive(:send)
307
+ # conn = EMJack::Connection.new
308
+ # lambda { conn.stats(:blah) }.should raise_error(EMJack::InvalidCommand)
309
+ #end
310
+ #
311
+ #it 'should support listing tubes' do
312
+ # @connection_mock.should_receive(:send).once.with(:'list-tubes')
313
+ # conn = EMJack::Connection.new
314
+ # conn.list
315
+ #end
316
+ #
317
+ #it 'should support listing tube used' do
318
+ # @connection_mock.should_receive(:send).once.with(:'list-tube-used')
319
+ # conn = EMJack::Connection.new
320
+ # conn.list(:used)
321
+ #end
322
+ #
323
+ #it 'should support listing tubes watched' do
324
+ # @connection_mock.should_receive(:send).once.with(:'list-tubes-watched')
325
+ # conn = EMJack::Connection.new
326
+ # conn.list(:watched)
327
+ #end
328
+ #
329
+ #it 'should throw exception on invalid list command' do
330
+ # @connection_mock.should_not_receive(:send)
331
+ # conn = EMJack::Connection.new
332
+ # lambda { conn.list(:blah) }.should raise_error(EMJack::InvalidCommand)
333
+ #end
334
+ #
335
+ #it 'should accept a response broken over multiple packets' do
336
+ # conn = EMJack::Connection.new
337
+ #
338
+ # msg1 = "First half of the message\r\n"
339
+ # msg2 = "Last half of the message"
340
+ #
341
+ # df = conn.add_deferrable
342
+ # df.should_receive(:succeed).with do |job|
343
+ # job.body.should == "#{msg1}#{msg2}"
344
+ # end
345
+ #
346
+ # conn.received("RESERVED 9 ")
347
+ # conn.received("#{(msg1 + msg2).length}")
348
+ # conn.received("\r\n#{msg1}#{msg2}\r\n")
349
+ #end
350
+ #
351
+ #it 'should accept a response broken over multiple packets' do
352
+ # conn = EMJack::Connection.new
353
+ #
354
+ # msg1 = "First half of the message\r\n"
355
+ # msg2 = "Last half of the message"
356
+ #
357
+ # df = conn.add_deferrable
358
+ # df.should_receive(:succeed).with do |job|
359
+ # job.body.should == "#{msg1}#{msg2}"
360
+ # end
361
+ #
362
+ # conn.received("RESERVED 9 #{(msg1 + msg2).length}\r\n#{msg1}#{msg2}")
363
+ # conn.received("\r\n")
364
+ #end
365
+ end
data/spec/job_spec.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe EMJack::Job do
4
+ it 'should convert id to an integer' do
5
+ j = EMJack::Job.new(nil, "123", "body")
6
+ j.id.should == 123
7
+ end
8
+
9
+ it 'should fail if id is not an integer' do
10
+ proc {
11
+ j = EMJack::Job.new(nil, "asd", "body")
12
+ }.should raise_error
13
+ end
14
+
15
+ it 'should send a delete command to the connection' do
16
+ conn = mock(:conn)
17
+ conn.should_receive(:default_priority)
18
+ conn.should_receive(:default_delay)
19
+ conn.should_receive(:default_ttr)
20
+
21
+ j = EMJack::Job.new(conn, 1, "body")
22
+
23
+ conn.should_receive(:delete).with(j)
24
+
25
+ j.delete
26
+ end
27
+
28
+ it 'should send a stats command to the connection' do
29
+ conn = mock(:conn)
30
+ conn.should_receive(:default_priority)
31
+ conn.should_receive(:default_delay)
32
+ conn.should_receive(:default_ttr)
33
+
34
+ j = EMJack::Job.new(conn, 2, 'body')
35
+ conn.should_receive(:stats).with(:job, j)
36
+
37
+ j.stats
38
+ end
39
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format
3
+ specdoc
4
+ --loadby
5
+ mtime
6
+ --reverse
7
+ --backtrace
@@ -0,0 +1,14 @@
1
+ libdir = File.expand_path("lib")
2
+ $:.unshift(libdir) unless $:.include?(libdir)
3
+
4
+ require 'em-jack'
5
+ #require 'rubygems'
6
+ require "em-spec/rspec"
7
+
8
+ #def with_connection(*args)
9
+ # EM.run do
10
+ # yield EMJack::Connection.new(*args)
11
+ # EM.stop
12
+ # end
13
+
14
+ #end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-beanstalk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Dan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-20 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: EventMachine client for Beanstalkd
26
+ email: dan@postrank.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.rdoc
33
+ files:
34
+ - COPYING
35
+ - README.rdoc
36
+ - Rakefile
37
+ - VERSION
38
+ - lib/em-jack.rb
39
+ - lib/em-jack/beanstalk_connection.rb
40
+ - lib/em-jack/connection.rb
41
+ - lib/em-jack/errors.rb
42
+ - lib/em-jack/job.rb
43
+ - spec/connection_spec.rb
44
+ - spec/integration_spec.rb
45
+ - spec/job_spec.rb
46
+ - spec/spec.opts
47
+ - spec/spec_helper.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/joshbuddy/em-jack
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: EventMachine client for Beanstalkd
76
+ test_files:
77
+ - spec/connection_spec.rb
78
+ - spec/integration_spec.rb
79
+ - spec/job_spec.rb
80
+ - spec/spec_helper.rb