em-jack 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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,80 @@
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
+
19
+ = Examples
20
+ EM.run {
21
+ jack = EMJack::Connection.new
22
+
23
+ r = jack.use('mytube')
24
+ r.callback { |tube| puts "Using #{tube}" }
25
+
26
+ r = jack.reserve
27
+ r.callback do |job|
28
+ puts job.jobid
29
+ process(job)
30
+
31
+ r2 = jack.delete(job)
32
+ r2.callback { puts "Successfully deleted" }
33
+ end
34
+
35
+ r = jack.put("my message", :ttr => 300)
36
+ r.callback { |jobid| puts "put successful #{jobid}" }
37
+
38
+ r = jack.stats
39
+ r.callback { |stats| puts "Server up for #{stats['uptime']} seconds" "}
40
+
41
+ r = jack.stats(:tube, "mytube")
42
+ r.callback { |stats| puts "Total jobs #{stats['total-jobs']}" }
43
+
44
+ r = jack.list(:tubes)
45
+ r.callback { |tubes| puts "There are #{tubes.length} tubes defined" }
46
+ }
47
+
48
+
49
+ EMJack#each_job is useful for scenarios where the client is a job worker
50
+ and intended to process jobs continuously as they become available. Once
51
+ the queue is empty, the client will block and wait for a new job.
52
+
53
+ If multiple workers connect to the queue Beanstalkd will round-robin between
54
+ the workers.
55
+ EM.run {
56
+ jack = EMJack::Connection.new
57
+
58
+ jack.each_job do |job|
59
+ puts "Got job ##{job.jobid}: #{job}"
60
+
61
+ if process(job)
62
+ r = jack.delete(job)
63
+ r.callback { puts "*Deleted #{job}*" }
64
+ else
65
+ end
66
+ end
67
+
68
+ def process(job)
69
+ # Some kind of job processing
70
+ end
71
+ }
72
+
73
+
74
+ = Contact
75
+ If you've got any questions, comments or bugs, please let me know. You can
76
+ contact me by email at dj2 at everburning dot com.
77
+
78
+
79
+ = Contributors
80
+ Peter Kieltyka (EMJack#each_job)
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,231 @@
1
+ require 'eventmachine'
2
+ require 'yaml'
3
+
4
+ module EMJack
5
+ class Connection
6
+ RETRY_COUNT = 5
7
+
8
+ attr_accessor :host, :port
9
+
10
+ def initialize(opts = {})
11
+ @host = opts[:host] || 'localhost'
12
+ @port = opts[:port] || 11300
13
+ @tube = opts[:tube]
14
+
15
+ @used_tube = 'default'
16
+ @watched_tubes = ['default']
17
+
18
+ @data = ""
19
+ @retries = 0
20
+ @in_reserve = false
21
+ @deferrables = []
22
+
23
+ @conn = EM::connect(host, port, EMJack::BeanstalkConnection) do |conn|
24
+ conn.client = self
25
+ end
26
+
27
+ unless @tube.nil?
28
+ use(@tube)
29
+ watch(@tube)
30
+ end
31
+ end
32
+
33
+ def use(tube)
34
+ return if @used_tube == tube
35
+ @used_tube = tube
36
+ @conn.send(:use, tube)
37
+ add_deferrable
38
+ end
39
+
40
+ def watch(tube)
41
+ return if @watched_tubes.include?(tube)
42
+ @watched_tubes.push(tube)
43
+ @conn.send(:watch, tube)
44
+ add_deferrable
45
+ end
46
+
47
+ def ignore(tube)
48
+ return if not @watched_tubes.include?(tube)
49
+ @watched_tubes.delete(tube)
50
+ @conn.send(:ignore, tube)
51
+ add_deferrable
52
+ end
53
+
54
+ def reserve(timeout = nil)
55
+ if timeout
56
+ @conn.send(:'reserve-with-timeout', timeout)
57
+ else
58
+ @conn.send(:reserve)
59
+ end
60
+ add_deferrable
61
+ end
62
+
63
+ def each_job(&block)
64
+ work = Proc.new do
65
+ r = reserve
66
+ r.callback do |job|
67
+ block.call(job)
68
+ EM.next_tick { work.call }
69
+ end
70
+ end
71
+ work.call
72
+ end
73
+
74
+ def stats(type = nil, val = nil)
75
+ case(type)
76
+ when nil then @conn.send(:stats)
77
+ when :tube then @conn.send(:'stats-tube', val)
78
+ when :job then @conn.send(:'stats-job', val.jobid)
79
+ else raise EMJack::InvalidCommand.new
80
+ end
81
+ add_deferrable
82
+ end
83
+
84
+ def list(type = nil)
85
+ case(type)
86
+ when nil then @conn.send(:'list-tubes')
87
+ when :used then @conn.send(:'list-tube-used')
88
+ when :watched then @conn.send(:'list-tubes-watched')
89
+ else raise EMJack::InvalidCommand.new
90
+ end
91
+ add_deferrable
92
+ end
93
+
94
+ def delete(job)
95
+ return if job.nil?
96
+ @conn.send(:delete, job.jobid)
97
+ add_deferrable
98
+ end
99
+
100
+ def release(job)
101
+ return if job.nil?
102
+ @conn.send(:release, job.jobid, 0, 0)
103
+ add_deferrable
104
+ end
105
+
106
+ def put(msg, opts = {})
107
+ pri = (opts[:priority] || 65536).to_i
108
+ if pri< 0
109
+ pri = 65536
110
+ elsif pri > (2 ** 32)
111
+ pri = 2 ** 32
112
+ end
113
+
114
+ delay = (opts[:delay] || 0).to_i
115
+ delay = 0 if delay < 0
116
+
117
+ ttr = (opts[:ttr] || 300).to_i
118
+ ttr = 300 if ttr < 0
119
+
120
+ m = msg.to_s
121
+
122
+ @conn.send_with_data(:put, m, pri, delay, ttr, m.length)
123
+ add_deferrable
124
+ end
125
+
126
+ def connected
127
+ @retries = 0
128
+ end
129
+
130
+ def disconnected
131
+ # XXX I think I need to run out the deferrables as failed here
132
+ # since the connection was dropped
133
+
134
+ raise EMJack::Disconnected if @retries >= RETRY_COUNT
135
+ @retries += 1
136
+ EM.add_timer(1) { @conn.reconnect(@host, @port) }
137
+ end
138
+
139
+ def add_deferrable
140
+ df = EM::DefaultDeferrable.new
141
+ df.errback { |err| puts "ERROR: #{err}" }
142
+
143
+ @deferrables.push(df)
144
+ df
145
+ end
146
+
147
+ def received(data)
148
+ @data << data
149
+
150
+ until @data.empty?
151
+ idx = @data.index(/\r\n/)
152
+ break if idx.nil?
153
+
154
+ first = @data[0..(idx + 1)]
155
+
156
+ handled = false
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
+ next unless first =~ /^#{cmd}\r\n/i
161
+ df = @deferrables.shift
162
+ df.fail(cmd.downcase.to_sym)
163
+
164
+ @data = @data[(cmd.length + 2)..-1]
165
+ handled = true
166
+ break
167
+ end
168
+ next if handled
169
+
170
+ case (first)
171
+ when /^DELETED\r\n/ then
172
+ df = @deferrables.shift
173
+ df.succeed
174
+
175
+ when /^INSERTED\s+(\d+)\r\n/ then
176
+ df = @deferrables.shift
177
+ df.succeed($1.to_i)
178
+
179
+ when /^BURIED\s+(\d+)\r\n/ then
180
+ df = @deferrables.shift
181
+ df.fail(:buried, $1.to_i)
182
+
183
+ when /^USING\s+(.*)\r\n/ then
184
+ df = @deferrables.shift
185
+ df.succeed($1)
186
+
187
+ when /^WATCHING\s+(\d+)\r\n/ then
188
+ df = @deferrables.shift
189
+ df.succeed($1.to_i)
190
+
191
+ when /^OK\s+(\d+)\r\n/ then
192
+ bytes = $1.to_i
193
+
194
+ body, @data = extract_body(bytes, @data)
195
+ break if body.nil?
196
+
197
+ df = @deferrables.shift
198
+ df.succeed(YAML.load(body))
199
+ next
200
+
201
+ when /^RESERVED\s+(\d+)\s+(\d+)\r\n/ then
202
+ id = $1.to_i
203
+ bytes = $2.to_i
204
+
205
+ body, @data = extract_body(bytes, @data)
206
+ break if body.nil?
207
+
208
+ df = @deferrables.shift
209
+ job = EMJack::Job.new(self, id, body)
210
+ df.succeed(job)
211
+ next
212
+
213
+ else
214
+ break
215
+ end
216
+
217
+ @data = @data[(@data.index(/\r\n/) + 2)..-1]
218
+ @data = "" if @data.nil?
219
+ end
220
+ end
221
+
222
+ def extract_body(bytes, data)
223
+ rem = data[(data.index(/\r\n/) + 2)..-1]
224
+ return [nil, data] if rem.length < bytes
225
+ body = rem[0..(bytes - 1)]
226
+ data = rem[(bytes + 2)..-1]
227
+ data = "" if data.nil?
228
+ [body, data]
229
+ end
230
+ end
231
+ 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,23 @@
1
+ module EMJack
2
+ class Job
3
+ attr_accessor :jobid, :body, :ttr, :conn
4
+
5
+ def initialize(conn, jobid, body)
6
+ @conn = conn
7
+ @jobid = jobid.to_i
8
+ @body = body
9
+ end
10
+
11
+ def delete
12
+ @conn.delete(self)
13
+ end
14
+
15
+ def stats
16
+ @conn.stats(:job, self)
17
+ end
18
+
19
+ def to_s
20
+ "#{@jobid} -- #{body.inspect}"
21
+ end
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: em-jack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - dan sinclair
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-11 00:00:00 -06: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: An evented Beanstalk client.
26
+ email: dj2@everburning.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.rdoc
33
+ files:
34
+ - README.rdoc
35
+ - COPYING
36
+ - lib/em-jack.rb
37
+ - lib/em-jack/beanstalk_connection.rb
38
+ - lib/em-jack/connection.rb
39
+ - lib/em-jack/errors.rb
40
+ - lib/em-jack/job.rb
41
+ has_rdoc: true
42
+ homepage: http://github.com/dj2/em-jack/
43
+ licenses: []
44
+
45
+ post_install_message:
46
+ rdoc_options:
47
+ - --title
48
+ - EM-Jack Documentation
49
+ - --main
50
+ - README.rdoc
51
+ - --line-numbers
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.3.5
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: An evented Beanstalk client.
73
+ test_files: []
74
+