beaneater 0.3.3 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b2177aa90f9a3a7017a9df9295ce6105f9d40e22
4
- data.tar.gz: 5f77980dcbee480728ee7723c2c67d2cd0138f25
3
+ metadata.gz: fe3f3a885f7fd1d205f508d48e2ce588bb56d2e4
4
+ data.tar.gz: 0d5578662d16fa1ad5da7f48e85f1ebf2a13d66d
5
5
  SHA512:
6
- metadata.gz: 8246d9cf93f51481b0d8cdd16129aa44fe6d47c9b0ce702735a44396c364bf5f200e75e4da84857fd9b302fe89dce64f22345e0f0e79b1242c235b73bda7026c
7
- data.tar.gz: ca040b82353afa029e60554d45747dc63a14bce3f8fd0e40a7d455e42873a1bebcd05441959a789c89f7dc615a8826164f300ae1e98bcf83e1fe0d68be9016ef
6
+ metadata.gz: 532de361e18a96f6acbbb152edc7ada7e83c2793fb494e03ea8e9f0064c6d81b20dfd296fbec26731d61a944cbdc43986492ce77f3c5e4fa30a7a3158e27a185
7
+ data.tar.gz: 1838fcddd8e169470a13dac5305ed0dec7b84a5fdd79289626f05a45862b4b7bce4e45cb82f455ea46ec0ed738e9003e6922b751c8f85f57aaa386e2e5b25c0f
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG for Beaneater
2
2
 
3
+ ## 1.0.0 (April 26th 2015)
4
+
5
+ * Beginning from version 1.0.0 the support for `Beaneater::Pool` has been dropped (@alup)
6
+ * `Jobs#find_all` method has been removed, since it is no longer necessary after removing pool (@alup)
7
+ * `Tubes` is now an enumerable allowing `tubes` to be handled as a collection (@Aethelflaed)
8
+
3
9
  ## 0.3.3 (August 16th 2014)
4
10
 
5
11
  * Fix failure when job is not defined and fix exception handling for jobs (@nicholasorenrawlings)
@@ -7,7 +13,6 @@
7
13
  * Add travis-ci badge (@tdg5)
8
14
  * Fix tests to run more reliably (@tdg5)
9
15
 
10
-
11
16
  ## 0.3.2 (Sept 15 2013)
12
17
 
13
18
  * Fix #29 ExpectedCrlfError name and invocation (@tdg5)
data/README.md CHANGED
@@ -8,6 +8,7 @@ originally designed for reducing the latency of page views in high-volume web ap
8
8
  running time-consuming tasks asynchronously. Read the [yardocs](http://rdoc.info/github/beanstalkd/beaneater) and/or the
9
9
  [beanstalk protocol](https://github.com/kr/beanstalkd/blob/master/doc/protocol.md) for more details.
10
10
 
11
+ **Important Note**: This README is **for branch 1.0.x which is under development**. Please switch to latest `0.x` branch for stable version.
11
12
 
12
13
  ## Why Beanstalk?
13
14
 
@@ -83,13 +84,25 @@ gem 'beaneater'
83
84
 
84
85
  and run `bundle install` to install the dependency.
85
86
 
87
+ ## Breaking Changes since 1.0!
88
+
89
+ Starting in 1.0, we removed the concept of the `Beaneater::Pool` which introduced considerable complexity into this gem.
90
+
91
+ * Beginning from version 1.0.0 the support for `Beaneater::Pool` has been dropped.
92
+ The specific feature may be supported again in the next versions as separate module
93
+ or through a separate gem. If you want to use the pool feature you should switch to
94
+ 0.x stable branches instead.
95
+ * `Jobs#find_all` method has been removed, since it is no longer necessary.
96
+
97
+ To manage a pool of beanstalkd instances, we'd prefer to leave the handling to the developer or other higher-level libraries.
98
+
86
99
  ## Quick Overview:
87
100
 
88
101
  The concise summary of how to use beaneater:
89
102
 
90
103
  ```ruby
91
104
  # Connect to pool
92
- @beanstalk = Beaneater::Pool.new(['localhost:11300'])
105
+ @beanstalk = Beaneater.new('localhost:11300')
93
106
  # Enqueue jobs to tube
94
107
  @tube = @beanstalk.tubes["my-tube"]
95
108
  @tube.put '{ "key" : "foo" }', :pri => 5
@@ -118,7 +131,8 @@ Beaneater.configure do |config|
118
131
  # config.default_put_pri = 65536
119
132
  # config.default_put_ttr = 120
120
133
  # config.job_parser = lambda { |body| body }
121
- # config.beanstalkd_url = ['localhost:11300']
134
+ # config.job_serializer = lambda { |body| body }
135
+ # config.beanstalkd_url = 'localhost:11300'
122
136
  end
123
137
  ```
124
138
 
@@ -126,18 +140,17 @@ The above options are all defaults, so only include a configuration block if you
126
140
 
127
141
  ### Connection
128
142
 
129
- To interact with a beanstalk queue, first establish a connection by providing a set of addresses:
143
+ To interact with a beanstalk queue, first establish a connection by providing an address:
130
144
 
131
145
  ```ruby
132
- @beanstalk = Beaneater::Pool.new(['10.0.1.5:11300'])
146
+ @beanstalk = Beaneater.new('10.0.1.5:11300')
133
147
 
134
- # Or if ENV['BEANSTALKD_URL] == 'localhost:11300,127.0.0.1:11300'
135
- @beanstalk = Beaneater::Pool.new
136
- @beanstalk.connections.first # => localhost:11300
137
- @beanstalk.connections.last # => 127.0.0.1:11300
148
+ # Or if ENV['BEANSTALKD_URL'] == '127.0.0.1:11300'
149
+ @beanstalk = Beaneater.new
150
+ @beanstalk.connectiont # => localhost:11300
138
151
  ```
139
152
 
140
- You can conversely close and dispose of a pool at any time with:
153
+ You can conversely close and dispose of a connection at any time with:
141
154
 
142
155
  ```ruby
143
156
  @beanstalk.close
@@ -248,6 +261,16 @@ Beanstalkd can only stores strings as job bodies, but you can easily encode your
248
261
  @tube.put({:foo => 'bar'}.to_json)
249
262
  ```
250
263
 
264
+ Moreover, you can provide a default job serializer by setting the corresponding configuration
265
+ option (`job_serializer`), in order to apply the encoding on each job body which
266
+ is going to be send using the `put` command. For example, to encode a ruby object to JSON format:
267
+
268
+ ```ruby
269
+ Beaneater.configure do |config|
270
+ config.job_serializer = lambda { |body| JSON.dump(body) }
271
+ end
272
+ ```
273
+
251
274
  Each job has various metadata associated such as `priority`, `delay`, and `ttr` which can be
252
275
  specified as part of the `put` command:
253
276
 
@@ -266,7 +289,7 @@ In order to process jobs, the client should first specify the intended tubes to
266
289
  this will default to watching just the `default` tube.
267
290
 
268
291
  ```ruby
269
- @beanstalk = Beaneater::Pool.new(['10.0.1.5:11300'])
292
+ @beanstalk = Beaneater.new('10.0.1.5:11300')
270
293
  @beanstalk.tubes.watch!('tube-name', 'other-tube')
271
294
  ```
272
295
 
@@ -330,13 +353,6 @@ inspected using the 'peek' commands. To find and peek at a particular job based
330
353
  # => <Beaneater::Job id=123 body="foo">
331
354
  ```
332
355
 
333
- You can also `find_all` jobs across all connections:
334
-
335
- ```ruby
336
- @beanstalk.jobs.find_all(123)
337
- # => [<Beaneater::Job id=123 body="foo">, <Beaneater::Job id=123 body="bar">]
338
- ```
339
-
340
356
  or you can peek at jobs within a tube:
341
357
 
342
358
  ```ruby
@@ -424,8 +440,9 @@ beanstalk overall:
424
440
  ```ruby
425
441
  # Get overall stats about the job processing that has occurred
426
442
  print @beanstalk.stats
427
- # => { 'current_connections': 1, 'current_jobs_buried': 0, ... }
428
- print @beanstalk.stats.current_connections
443
+ # => #<Beaneater::StatStruct current_jobs_urgent=0, current_jobs_ready=0, current_jobs_reserved=0, current_jobs_delayed=0, current_jobs_buried=0, ...
444
+
445
+ print @beanstalk.stats.current_tubes
429
446
  # => 1
430
447
  ```
431
448
 
@@ -466,3 +483,4 @@ There are other resources helpful when learning about beanstalk:
466
483
  - [Nathan Esquenazi](https://github.com/nesquena) - Contributor and co-maintainer
467
484
  - [Keith Rarick](https://github.com/kr) - Much code inspired and adapted from beanstalk-client
468
485
  - [Vidar Hokstad](https://github.com/vidarh) - Replaced telnet with correct TCP socket handling
486
+ - [Andreas Loupasakis](https://github.com/alup) - Improve test coverage, improve job configuration
@@ -6,8 +6,7 @@ require 'beaneater'
6
6
 
7
7
  # Establish a pool of beanstalks
8
8
  puts step("Connecting to Beanstalk")
9
- bc = Beaneater::Pool.new('localhost')
10
- # bc = Beaneater::Pool.new(['localhost', 'localhost:11301', 'localhost:11302'])
9
+ bc = Beaneater.new('localhost')
11
10
  puts bc
12
11
 
13
12
  # Print out key stats
@@ -1,11 +1,64 @@
1
1
  require 'thread' unless defined?(Mutex)
2
2
 
3
- %w(version configuration errors pool_command pool connection stats tube job).each do |f|
3
+ %w(version configuration errors connection tube job stats).each do |f|
4
4
  require "beaneater/#{f}"
5
5
  end
6
6
 
7
- module Beaneater
8
- # Simple ruby client for beanstalkd.
7
+ class Beaneater
8
+
9
+ # @!attribute connection
10
+ # @return <Beaneater::Connection> returns the associated connection object
11
+ attr_reader :connection
12
+
13
+ # Initialize new instance of Beaneater
14
+ #
15
+ # @param [String] address in the form "host:port"
16
+ # @example
17
+ # Beaneater.new('127.0.0.1:11300')
18
+ #
19
+ # ENV['BEANSTALKD_URL'] = '127.0.0.1:11300'
20
+ # @b = Beaneater.new
21
+ # @b.connection.host # => '127.0.0.1'
22
+ # @b.connection.port # => '11300'
23
+ #
24
+ def initialize(address)
25
+ @connection = Connection.new(address)
26
+ end
27
+
28
+ # Returns Beaneater::Tubes object for accessing tube related functions.
29
+ #
30
+ # @return [Beaneater::Tubes] tubes object
31
+ # @api public
32
+ def tubes
33
+ @tubes ||= Beaneater::Tubes.new(self)
34
+ end
35
+
36
+ # Returns Beaneater::Jobs object for accessing job related functions.
37
+ #
38
+ # @return [Beaneater::Jobs] jobs object
39
+ # @api public
40
+ def jobs
41
+ @jobs ||= Beaneater::Jobs.new(self)
42
+ end
43
+
44
+ # Returns Beaneater::Stats object for accessing beanstalk stats.
45
+ #
46
+ # @return [Beaneater::Stats] stats object
47
+ # @api public
48
+ def stats
49
+ @stats ||= Stats.new(self)
50
+ end
51
+
52
+ # Closes the related connection
53
+ #
54
+ # @example
55
+ # @beaneater_instance.close
56
+ #
57
+ def close
58
+ connection.close if connection
59
+ end
60
+
61
+ protected
9
62
 
10
63
  class << self
11
64
  # Yields a configuration block
@@ -1,9 +1,10 @@
1
- module Beaneater
1
+ class Beaneater
2
2
  class Configuration
3
3
  attr_accessor :default_put_delay # default delay value to put a job
4
4
  attr_accessor :default_put_pri # default priority value to put a job
5
5
  attr_accessor :default_put_ttr # default ttr value to put a job
6
6
  attr_accessor :job_parser # default job_parser to parse job body
7
+ attr_accessor :job_serializer # default serializer for job body
7
8
  attr_accessor :beanstalkd_url # default beanstalkd url
8
9
 
9
10
  def initialize
@@ -11,6 +12,7 @@ module Beaneater
11
12
  @default_put_pri = 65536
12
13
  @default_put_ttr = 120
13
14
  @job_parser = lambda { |body| body }
15
+ @job_serializer = lambda { |body| body }
14
16
  @beanstalkd_url = nil
15
17
  end
16
18
  end # Configuration
@@ -1,10 +1,16 @@
1
1
  require 'yaml'
2
2
  require 'socket'
3
3
 
4
- module Beaneater
4
+ class Beaneater
5
5
  # Represents a connection to a beanstalkd instance.
6
6
  class Connection
7
7
 
8
+ # Default number of retries to send a command to a connection
9
+ MAX_RETRIES = 3
10
+
11
+ # Default retry interval
12
+ DEFAULT_RETRY_INTERVAL = 1
13
+
8
14
  # @!attribute address
9
15
  # @return [String] returns Beanstalkd server address
10
16
  # @example
@@ -21,6 +27,12 @@ module Beaneater
21
27
  # @return [Net::TCPSocket] returns connection object
22
28
  attr_reader :address, :host, :port, :connection
23
29
 
30
+ # @!attribute tubes_watched
31
+ # @returns [Array<String>] returns currently watched tube names
32
+ # @!attribute tube_used
33
+ # @returns [String] returns currently used tube name
34
+ attr_accessor :tubes_watched, :tube_used
35
+
24
36
  # Default port value for beanstalk connection
25
37
  DEFAULT_PORT = 11300
26
38
 
@@ -28,13 +40,23 @@ module Beaneater
28
40
  #
29
41
  # @param [String] address beanstalkd instance address.
30
42
  # @example
31
- # Beaneater::Connection.new('localhost')
32
- # Beaneater::Connection.new('localhost:11300')
43
+ # Beaneater::Connection.new('127.0.0.1')
44
+ # Beaneater::Connection.new('127.0.0.1:11300')
45
+ #
46
+ # ENV['BEANSTALKD_URL'] = '127.0.0.1:11300'
47
+ # @b = Beaneater.new
48
+ # @b.connection.host # => '127.0.0.1'
49
+ # @b.connection.port # => '11300'
33
50
  #
34
51
  def initialize(address)
35
- @address = address
36
- @connection = establish_connection
52
+ @address = address || _host_from_env || Beaneater.configuration.beanstalkd_url
37
53
  @mutex = Mutex.new
54
+ @tube_used = 'default'
55
+ @tubes_watched = ['default']
56
+
57
+ establish_connection
58
+ rescue
59
+ _raise_not_connected!
38
60
  end
39
61
 
40
62
  # Send commands to beanstalkd server via connection.
@@ -43,18 +65,19 @@ module Beaneater
43
65
  # @param [String] command Beanstalkd command
44
66
  # @return [Array<Hash{String => String, Number}>] Beanstalkd command response
45
67
  # @example
68
+ # @conn = Beaneater::Connection.new
46
69
  # @conn.transmit('bury 123')
70
+ # @conn.transmit('stats')
47
71
  #
48
72
  def transmit(command, options={})
49
- @mutex.synchronize do
50
- if connection
73
+ _with_retry(options[:retry_interval], options[:init]) do
74
+ @mutex.synchronize do
75
+ _raise_not_connected! unless connection
76
+
51
77
  command = command.force_encoding('ASCII-8BIT') if command.respond_to?(:force_encoding)
52
78
  connection.write(command.to_s + "\r\n")
53
- res = connection.gets
54
- raise_not_connected! unless res
79
+ res = connection.readline
55
80
  parse_response(command, res)
56
- else # no connection
57
- raise_not_connected!
58
81
  end
59
82
  end
60
83
  end
@@ -65,8 +88,10 @@ module Beaneater
65
88
  # @conn.close
66
89
  #
67
90
  def close
68
- @connection.close
69
- @connection = nil
91
+ if @connection
92
+ @connection.close
93
+ @connection = nil
94
+ end
70
95
  end
71
96
 
72
97
  # Returns string representation of job.
@@ -79,6 +104,15 @@ module Beaneater
79
104
  end
80
105
  alias :inspect :to_s
81
106
 
107
+ def add_to_watched(tube_name)
108
+ @tubes_watched << tube_name
109
+ @tubes_watched.uniq
110
+ end
111
+
112
+ def remove_from_watched(tube_name)
113
+ @tubes_watched.delete(tube_name)
114
+ end
115
+
82
116
  protected
83
117
 
84
118
  # Establish a connection based on beanstalk address.
@@ -89,13 +123,11 @@ module Beaneater
89
123
  # establish_connection('localhost:3005')
90
124
  #
91
125
  def establish_connection
92
- @match = address.split(':')
93
- @host, @port = @match[0], Integer(@match[1] || DEFAULT_PORT)
94
- TCPSocket.new @host, @port
95
- rescue Errno::ECONNREFUSED => e
96
- raise NotConnected, "Could not connect to '#{@host}:#{@port}'"
97
- rescue Exception => ex
98
- raise NotConnected, "#{ex.class}: #{ex}"
126
+ @address = address.first if address.is_a?(Array)
127
+ match = address.split(':')
128
+ @host, @port = match[0], Integer(match[1] || DEFAULT_PORT)
129
+
130
+ @connection = TCPSocket.new @host, @port
99
131
  end
100
132
 
101
133
  # Parses the response and returns the useful beanstalk response.
@@ -103,11 +135,11 @@ module Beaneater
103
135
  #
104
136
  # @param [String] cmd Beanstalk command transmitted
105
137
  # @param [String] res Telnet command response
106
- # @return [Array<Hash{String => String, Number}>] Beanstalk response with `status`, `id`, `body`, and `connection`
138
+ # @return [Array<Hash{String => String, Number}>] Beanstalk response with `status`, `id`, `body`
107
139
  # @raise [Beaneater::UnexpectedResponse] Response from beanstalk command was an error status
108
140
  # @example
109
141
  # parse_response("delete 56", "DELETED 56\nFOO")
110
- # # => { :body => "FOO", :status => "DELETED", :id => 56, :connection => <Connection> }
142
+ # # => { :body => "FOO", :status => "DELETED", :id => 56 }
111
143
  #
112
144
  def parse_response(cmd, res)
113
145
  status = res.chomp
@@ -123,9 +155,9 @@ module Beaneater
123
155
  raise ExpectedCrlfError.new('EXPECTED_CRLF', cmd) if crlf != "\r\n"
124
156
  end
125
157
  id = body_values[1]
126
- response = { :status => status, :body => body }
158
+ response = { :status => status }
127
159
  response[:id] = id if id
128
- response[:connection] = self
160
+ response[:body] = body if body
129
161
  response
130
162
  end
131
163
 
@@ -136,10 +168,75 @@ module Beaneater
136
168
  Beaneater.configuration
137
169
  end
138
170
 
171
+ private
172
+
173
+ def _initialize_tubes
174
+ if @tubes_watched != ['default']
175
+ tubes_watched.each do |t|
176
+ transmit("watch #{t}", init: false)
177
+ end
178
+ end
179
+
180
+ transmit("use #{tube_used}", init: false) if @tube_used != 'default'
181
+ end
182
+
183
+ # Wrapper method for capturing certain failures and retry the payload block
184
+ #
185
+ # @param [Proc] block The command to execute.
186
+ # @param [Integer] retry_interval The time to wait before the next retry
187
+ # @param [Integer] tries The maximum number of tries in draining mode
188
+ # @return [Object] Result of the block passed
189
+ #
190
+ def _with_retry(retry_interval, init=true, tries=MAX_RETRIES, &block)
191
+ yield
192
+ rescue EOFError, Errno::ECONNRESET, Errno::EPIPE,
193
+ Errno::ECONNREFUSED => ex
194
+ _reconnect(ex, retry_interval)
195
+ _initialize_tubes if init
196
+ retry
197
+ rescue Beaneater::DrainingError
198
+ tries -= 1
199
+ if tries.zero?
200
+ close
201
+ raise
202
+ end
203
+ sleep(retry_interval || DEFAULT_RETRY_INTERVAL)
204
+ retry
205
+ end
206
+
207
+ # Tries to re-establish connection to the beanstalkd
208
+ #
209
+ # @param [Exception] original_exception The exception caused the retry
210
+ # @param [Integer] retry_interval The time to wait before the next reconnect
211
+ # @param [Integer] tries The maximum number of attempts to reconnect
212
+ def _reconnect(original_exception, retry_interval, tries=MAX_RETRIES)
213
+ close
214
+ establish_connection
215
+ rescue Errno::ECONNREFUSED
216
+ tries -= 1
217
+ if tries.zero?
218
+ _raise_not_connected!
219
+ end
220
+ sleep(retry_interval || DEFAULT_RETRY_INTERVAL)
221
+ retry
222
+ end
223
+
224
+ # The host provided by BEANSTALKD_URL environment variable, if available.
225
+ #
226
+ # @return [String] A beanstalkd host address
227
+ # @example
228
+ # ENV['BEANSTALKD_URL'] = "localhost:1212"
229
+ # # => 'localhost:1212'
230
+ #
231
+ def _host_from_env
232
+ ENV['BEANSTALKD_URL'].respond_to?(:length) && ENV['BEANSTALKD_URL'].length > 0 && ENV['BEANSTALKD_URL'].strip
233
+ end
234
+
139
235
  # Raises an error to be triggered when the connection has failed
140
236
  # @raise [Beaneater::NotConnected] Beanstalkd is no longer connected
141
- def raise_not_connected!
142
- raise NotConnected, "Connection to beanstalk '#{@host}:#{@port}' is closed!"
237
+ def _raise_not_connected!
238
+ raise Beaneater::NotConnected, "Connection to beanstalk '#{@host}:#{@port}' is closed!"
143
239
  end
240
+
144
241
  end # Connection
145
242
  end # Beaneater