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 +4 -4
- data/.github/workflows/ruby.yml +44 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile +2 -0
- data/README.md +29 -1
- data/Rakefile +2 -0
- data/beaneater.gemspec +2 -0
- data/examples/demo.rb +2 -0
- data/lib/beaneater/configuration.rb +10 -0
- data/lib/beaneater/connection.rb +315 -57
- data/lib/beaneater/errors.rb +16 -0
- data/lib/beaneater/job/collection.rb +44 -6
- data/lib/beaneater/job/record.rb +2 -0
- data/lib/beaneater/job.rb +2 -0
- data/lib/beaneater/stats/fast_struct.rb +2 -0
- data/lib/beaneater/stats/stat_struct.rb +8 -1
- data/lib/beaneater/stats.rb +2 -0
- data/lib/beaneater/tube/collection.rb +2 -0
- data/lib/beaneater/tube/record.rb +2 -0
- data/lib/beaneater/tube.rb +2 -0
- data/lib/beaneater/version.rb +3 -1
- data/lib/beaneater.rb +2 -0
- data/test/beaneater_test.rb +2 -0
- data/test/connection_test.rb +612 -12
- data/test/errors_test.rb +2 -0
- data/test/job_test.rb +14 -0
- data/test/jobs_test.rb +431 -0
- data/test/prompt_regexp_test.rb +2 -0
- data/test/stat_struct_test.rb +7 -0
- data/test/stats_test.rb +2 -0
- data/test/test_helper.rb +47 -0
- data/test/tube_test.rb +125 -0
- data/test/tubes_test.rb +18 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7397bf5f83a1ba3ff93d90fb1ee7718d8dcaf90ac45ecdedd236fe4fe666c5eb
|
|
4
|
+
data.tar.gz: e3f0776719d39a2eb235a921b505bddb3af25be0c915718be3b24a46dd0afd0b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Beaneater
|
|
2
|
-
|
|
2
|
+

|
|
3
3
|
[](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
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
|
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
|