beaneater 1.1.3 → 1.2.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
  SHA256:
3
- metadata.gz: ffe398358517da9e09fd22e17ddd4421e7a0f2b7fa2ae816e8ad0a308466f2d8
4
- data.tar.gz: 7f9d831b5318de310462d8d4405acc0dbedb82463d5b252dec9b63fbcb39677b
3
+ metadata.gz: 7397bf5f83a1ba3ff93d90fb1ee7718d8dcaf90ac45ecdedd236fe4fe666c5eb
4
+ data.tar.gz: e3f0776719d39a2eb235a921b505bddb3af25be0c915718be3b24a46dd0afd0b
5
5
  SHA512:
6
- metadata.gz: 0fd5812759fa6e28a830cecacdd7a22447ce94705b044d9c28062bb868a082ea3679a29b18e3c4f421a6abf90fb3ba28551a2715eb5e8b38782a6e842548dc08
7
- data.tar.gz: 919952687b5f715aca936c4c40af28a0db741e6c7539dfdcb78c6e5834a855fb91800e5a2e5fc2cb0e201e6422b5522f7c944be787fa0ca7b145f6b3edf83091
6
+ metadata.gz: 9e6b026dbb985f693b2fb0c494084223f86100850210cef83af8d6c7a951e6f81b2dd620262d5f28fa4a18ec8c26542409c45fd9404199dba766470af8265af1
7
+ data.tar.gz: a73f631654a1147afcaed139b24b8ec1748fde51b02ff9e84492e8ccdcae81cc84177951b5faebf5d8c06189188b7448e21c346f2b2650219467bcbe1f62b1c8
@@ -0,0 +1,44 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Build Status
9
+
10
+ on:
11
+ push:
12
+ branches: [ "master" ]
13
+ pull_request:
14
+ branches: [ "master" ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ matrix:
25
+ ruby-version: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
26
+
27
+ steps:
28
+ - uses: actions/checkout@v4
29
+ - name: Download & Extract beanstalkd
30
+ run: curl -L https://github.com/beanstalkd/beanstalkd/archive/refs/tags/v1.13.tar.gz | tar xz
31
+ - name: Make beanstalkd
32
+ run: make
33
+ working-directory: beanstalkd-1.13
34
+ - name: Set up Ruby
35
+ uses: ruby/setup-ruby@v1
36
+ with:
37
+ ruby-version: ${{ matrix.ruby-version }}
38
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
39
+ - name: Daemonize beanstalkd
40
+ run: ./beanstalkd-1.13/beanstalkd &
41
+ - name: Run tests
42
+ run: bundle exec rake test:full
43
+ env:
44
+ BEANSTALKD_BIN: ${{ github.workspace }}/beanstalkd-1.13/beanstalkd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # CHANGELOG for Beaneater
2
2
 
3
+ ## 1.2.0 (October 31 2026)
4
+
5
+ * `Jobs#process!` buries a job whose body the `job_parser` rejects (`JSON.parse` on a non-JSON job, say); it used to stay reserved until its TTR ran out and then fail again, for ever. A parser that raises `AbortProcessingError` has its job buried and stops the loop, as a handler's abort does. The job is not buried when the parser raises `NotConnected` while this client's connection is gone (`process!` raises it, and beanstalkd releases the job), an exception that isn't a `StandardError` (`process!` raises it, and the job stays reserved until its TTR runs out or the connection closes), or one that can't be copied (raised untagged, so `process!` can't tell which job it came from: the job stays reserved and `process!` keeps failing on it; make parser exceptions copyable). A plain `tubes.reserve` raises a copy of the parser's error with the same class, message, cause and backtrace (a frozen exception, for which Ruby records no backtrace, gets one that starts in beaneater), so `rescue JSON::ParserError` keeps working; the copy is also a `Beaneater::JobParseFailure`, whose `#job_id` names the job, still reserved by the connection (@nesquena)
6
+ * `close` while a command is waiting on the connection (a `reserve`, say), whether from a signal handler or from another thread, now makes that command raise `NotConnected`; 1.1.x raised `IOError`, and on Ruby 3.3 and 3.4 a `close` from a signal handler could crash the process. A `close` from a `job_parser` lets its command finish, then closes the socket (@nesquena)
7
+ * Drop the connection when a command is interrupted mid-reply (for example by `Timeout.timeout`), so the next command no longer reads the stale reply (reported by @tdegrunt)
8
+ * Honour `stop!` while `process!` is waiting for a job with `reserve_timeout` set (reported by @martint17r)
9
+ * Keep `process!` running when a handler deletes or releases its job, or overruns its TTR, and then raises; it used to stop with `NotFoundError` or `JobNotReserved`. A handler that raises one of those (or `TimedOutError`) while its job is still reserved now has the job buried like any other failure, instead of leaving it reserved and forgotten, which made `process!` reserve in a tight loop of `DEADLINE_SOON` replies near the job's TTR. To have such a job released for another try instead, add the error to the handler's `retry_on` (`jobs.register(tube, :retry_on => [Beaneater::NotFoundError])`); it is released up to `max_retries` times, then buried (reported by @pagojo)
10
+ * Reconnect on the next command after the connection was lost, restoring the used and watched tubes (while beanstalkd is down each command now retries for about 2 seconds before raising `NotConnected`, and gives up after 3 reconnects when every new connection is dropped at once; set `connect_timeout` in web apps that enqueue during requests), and make `process!` raise `NotConnected` instead of looping while its beanstalkd is down (a handler that raises `NotConnected` for another server still has its job buried, as before) (reported by @b-sridhar and @Meekohi)
11
+ * Test on Ruby 2.7 to 3.4 and 4.0 in CI (dropping 2.6), and update `actions/checkout` to v4 (@nesquena)
12
+ * Require Ruby 2.7 or newer, declared in the gemspec, so older Rubies get a clear install-time error instead of failing at runtime (the code needs at least 2.5 for `Hash#slice`), #83 (@nesquena; reported by @hkmaly)
13
+ * Make `read_timeout` and `write_timeout` take effect: they set `SO_RCVTIMEO`/`SO_SNDTIMEO`, which have no effect on Ruby 3 (Ruby waits on the socket itself), so a server that stopped answering still blocked forever. Beaneater now enforces both itself, adds `read_timeout` to `reserve(timeout)`'s own wait, never cuts off a plain `reserve`, and on timeout raises the new `Beaneater::SocketTimeout` (a `NotConnected`) and drops the connection, so the next command reconnects. `read_timeout` and `write_timeout` may be numeric strings. A `SocketTimeout` on `put` is ambiguous (beanstalkd may have stored the job), so retrying it can enqueue the job twice (@nesquena)
14
+ * Fix a raising `job_parser` (e.g. `JSON.parse` on a non-JSON job) leaving the connection one reply out of step, so later commands returned the wrong replies (@nesquena)
15
+ * Fix stats and tube lists mangling values since 1.1.2: `-`, `:` and `~` were rewritten to `_`, so `job.tube` for tube `email-jobs` was `"email_jobs"` and `Jobs#process!` buried its jobs; tube names that look like dates or numbers (`2026-10-30`, `1.10`) are now returned as strings instead of raising or being converted (@nesquena)
16
+ * Add `connect_timeout` (Ruby 3+), `resolv_timeout` (Ruby 3+), `read_timeout`, and `write_timeout` configuration options (@rveznaver)
17
+ * Fix `StatStruct#[]` raising a `SyntaxError` when looking up hyphenated beanstalkd keys (e.g. `stats['current-jobs-ready']`); keys are now underscored before lookup to match how they are stored (@dkam)
18
+
19
+ ## 1.1.4 (March 6 2026)
20
+
21
+ * Add frozen string literal comments for Ruby 3.4 compatibility (@nearapogee)
22
+ * Update tests for Ruby 3.4.x (@nearapogee)
23
+ * Add GitHub Actions CI workflow (@Ifiht)
24
+ * Update README.md (@Ifiht)
25
+
3
26
  ## 1.1.3 (Oct 14 2022)
4
27
 
5
28
  * Fixes issue introduced in 1.1.2 re YML parsing (@pond)
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in beaneater.gemspec
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Beaneater
2
- [![Build Status](https://secure.travis-ci.org/beanstalkd/beaneater.png)](http://travis-ci.org/beanstalkd/beaneater)
2
+ ![Build Status](https://github.com/beanstalkd/beaneater/actions/workflows/ruby.yml/badge.svg)
3
3
  [![Coverage Status](https://coveralls.io/repos/beanstalkd/beaneater/badge.png?branch=master)](https://coveralls.io/r/beanstalkd/beaneater)
4
4
 
5
5
  Beaneater is the best way to interact with beanstalkd from within Ruby.
@@ -133,11 +133,21 @@ Beaneater.configure do |config|
133
133
  # config.job_parser = lambda { |body| body }
134
134
  # config.job_serializer = lambda { |body| body }
135
135
  # config.beanstalkd_url = 'localhost:11300'
136
+ # config.connect_timeout = nil
137
+ # config.resolv_timeout = nil
138
+ # config.read_timeout = nil
139
+ # config.write_timeout = nil
136
140
  end
137
141
  ```
138
142
 
139
143
  The above options are all defaults, so only include a configuration block if you need to make changes.
140
144
 
145
+ `connect_timeout` and `resolv_timeout` are passed through to `TCPSocket.new` on Ruby 3.0 and newer (ignored on Ruby < 3.0 for compatibility). `read_timeout` bounds how long beaneater waits for beanstalkd's reply to a command; for `reserve(timeout)` it is added to the reserve's own timeout, and a plain `reserve` (no timeout) still waits as long as it takes for a job. `write_timeout` bounds how long sending a command may take. When either runs out, `Beaneater::SocketTimeout` (a `Beaneater::NotConnected`) is raised and the connection is dropped; the next command reconnects. Timeouts are in seconds; `read_timeout` and `write_timeout` may also be numeric strings (from `ENV`, say).
146
+
147
+ A `SocketTimeout` on `put` does not tell you whether beanstalkd stored the job: the command may have reached the server and only the reply been lost. Retrying the `put` can therefore enqueue the job twice, so retry a timed-out `put` only if running the job twice is harmless.
148
+
149
+ While beanstalkd is unreachable, every command tries to reconnect three times, one second apart, before raising `Beaneater::NotConnected`, so each command takes about 2 seconds to fail (longer per attempt when connecting itself hangs, as with an unreachable host). Threads sharing a client pause between their retries side by side, but the connection attempts themselves still run one at a time. Apps that enqueue jobs inside web requests should set `connect_timeout` and expect this delay while beanstalkd is down.
150
+
141
151
  ### Connection
142
152
 
143
153
  To interact with a beanstalk queue, first establish a connection by providing an address:
@@ -412,6 +422,23 @@ The `process!` command is ideally suited for a beanstalk job processing daemon.
412
422
  Even though `process!` is intended to be a long-running process, you can stop the loop at any time
413
423
  by raising `AbortProcessingError` while processing is running.
414
424
 
425
+ If the `job_parser` raises for a job (a body that isn't JSON, say), `process!` buries that job and
426
+ goes on; if it raises `AbortProcessingError`, `process!` buries the job and stops, as for a
427
+ handler's abort. The job is not buried when the parser raises `NotConnected` while this client's
428
+ connection is gone (`process!` raises it, and beanstalkd releases the job), an exception that isn't
429
+ a `StandardError` (`process!` raises it, and the job stays reserved until its TTR runs out or the
430
+ connection closes), or one that can't be copied (raised untagged, so `process!` can't tell which job
431
+ it came from: the job stays reserved and `process!` keeps failing on it; make parser exceptions
432
+ copyable). A direct `tubes.reserve` raises a copy of
433
+ the parser's error (same class, message, cause and backtrace; a frozen exception, for which Ruby
434
+ records no backtrace, gets one that starts in beaneater) that is also a
435
+ `Beaneater::JobParseFailure`: its `job_id` names the job, still reserved by your connection, so
436
+ you can bury, release or delete it.
437
+
438
+ If beanstalkd goes away and reconnecting fails, `process!` raises `Beaneater::NotConnected`.
439
+ The client reconnects on its next command, so calling `process!` again (for example from a
440
+ supervisor loop) resumes once the server is back.
441
+
415
442
  ### Handling Errors
416
443
 
417
444
  While using Beaneater, certain errors may be encountered. Errors are encountered when
@@ -424,6 +451,7 @@ are listed below:
424
451
  | Beaneater::InvalidTubeName | Specified tube name for use or watch is not valid. |
425
452
  | Beaneater::NotFoundError | Specified job or tube could not be found. |
426
453
  | Beaneater::TimedOutError | Job could not be reserved within time specified. |
454
+ | Beaneater::SocketTimeout | No reply within `read_timeout` (or the write exceeded `write_timeout`); the connection is dropped and the next command reconnects. A subclass of `NotConnected`, not to be confused with `TimedOutError`. |
427
455
  | Beaneater::JobNotReserved | Job has not been reserved and action cannot be taken. |
428
456
 
429
457
  There are other exceptions that are less common such as `OutOfMemoryError`, `DrainingError`,
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require 'rake/testtask'
3
5
  require 'yard'
data/beaneater.gemspec CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # -*- encoding: utf-8 -*-
2
3
  lib = File.expand_path('../lib', __FILE__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
@@ -17,6 +18,7 @@ Gem::Specification.new do |gem|
17
18
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
20
  gem.require_paths = ["lib"]
21
+ gem.required_ruby_version = '>= 2.7'
20
22
  gem.add_development_dependency 'minitest', "~> 4.1.0"
21
23
  gem.add_development_dependency 'rake'
22
24
  gem.add_development_dependency 'mocha'
data/examples/demo.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'term/ansicolor'
2
4
  class String; include Term::ANSIColor; end
3
5
  def step(msg); "\n[STEP] #{msg}...".yellow; end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Beaneater
2
4
  class Configuration
3
5
  attr_accessor :default_put_delay # default delay value to put a job
@@ -6,6 +8,10 @@ class Beaneater
6
8
  attr_accessor :job_parser # default job_parser to parse job body
7
9
  attr_accessor :job_serializer # default serializer for job body
8
10
  attr_accessor :beanstalkd_url # default beanstalkd url
11
+ attr_accessor :connect_timeout # TCP connect timeout in seconds
12
+ attr_accessor :resolv_timeout # DNS resolve timeout in seconds
13
+ attr_accessor :read_timeout # seconds to wait for a reply (added to reserve-with-timeout's own timeout; plain reserve is never cut off)
14
+ attr_accessor :write_timeout # seconds to wait for a command to be written
9
15
 
10
16
  def initialize
11
17
  @default_put_delay = 0
@@ -14,6 +20,10 @@ class Beaneater
14
20
  @job_parser = lambda { |body| body }
15
21
  @job_serializer = lambda { |body| body }
16
22
  @beanstalkd_url = nil
23
+ @connect_timeout = nil
24
+ @resolv_timeout = nil
25
+ @read_timeout = nil
26
+ @write_timeout = nil
17
27
  end
18
28
  end # Configuration
19
29
  end # Beaneater