kicks 3.2.0 → 3.3.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: 7899bb171d5e5399547c0b02c33753e23bc8cfaec63e614baa84cd473448a45d
4
- data.tar.gz: 3b5b708557aae6c815a2ad23cf86115e5d39ad397cd1e52099ed2e621b26848a
3
+ metadata.gz: a7bbca9eba169dacb7dc06c6536049d59ccf469f7c006918850dff45e7f6248a
4
+ data.tar.gz: a9c635d5d1404a4b090df86217c5ffbd28c7dd2dabf3bed1a7d3403236513528
5
5
  SHA512:
6
- metadata.gz: 57565fbb9e74e4b990f62e482293ff41cdffebb0b75875b4c654a34e4bb6ed037e2cc9305f15c1c6920e789749a484403badbc2f7aad58b811d614b0d2e51080
7
- data.tar.gz: e6516ab5c10e167a1e94b0a80f82b68dbe330be53603d4cc7132104e0827f013c3cc930b1f3fe2ecc5f2f65a6cbf140707ccd3bd709fbae5ae0eae0747e2b8d7
6
+ metadata.gz: 13a7f0407c737d4a263283bd07b44bdba929c23b0e3fe2a724e4c900b9edc5ab60fe1d9695e1fca1b9deba4e6ed435b4a80794ed8f3bf9188335ba1b58c1b373
7
+ data.tar.gz: 8a0bc7b766d90f3d0789cf5948d2046e7096d9b8ffecd6af9f3dff80166dfcaa948b21b91e10e0172b5a311d2b51b16085f0f0a3e34e2412fc944dfd7a2073a8
@@ -8,9 +8,13 @@ jobs:
8
8
  runs-on: ubuntu-latest
9
9
  strategy:
10
10
  matrix:
11
- ruby-version: [3.0, 3.1, 3.2, 3.3]
11
+ ruby-version:
12
+ - "3.4.2"
13
+ - "3.3.7"
14
+ - "3.2.7"
15
+ - "3.1.6"
12
16
  steps:
13
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v6
14
18
 
15
19
  - name: Install Ruby ${{ matrix.ruby-version }}
16
20
  uses: ruby/setup-ruby@v1
data/AGENTS.md ADDED
@@ -0,0 +1,74 @@
1
+ # Instructions for AI Agents
2
+
3
+ ## Overview
4
+
5
+ Kicks is a high-performance RabbitMQ background processing framework for Ruby,
6
+ a community-driven continuation of the original [Sneakers](https://github.com/jondot/sneakers) project.
7
+ The internal module name is still `Sneakers`.
8
+
9
+ Its key dependencies are [Bunny](https://github.com/ruby-amqp/bunny) (a RabbitMQ client that uses AMQP 0-9-1)
10
+ and [ServerEngine](https://github.com/treasure-data/serverengine) for process management.
11
+
12
+ ## Target Ruby Version
13
+
14
+ This library targets Ruby 3.0 and later versions.
15
+
16
+ ## Build and Test
17
+
18
+ ```bash
19
+ bundle install
20
+
21
+ bundle exec rake
22
+ ```
23
+
24
+ Tests use Minitest. The default Rake task runs `spec/**/*_spec.rb`.
25
+
26
+ ## Key Files
27
+
28
+ * `lib/sneakers/worker.rb`: the core `Sneakers::Worker` module (work processing, [delivery acknowledgements](https://www.rabbitmq.com/docs/confirms))
29
+ * `lib/sneakers/configuration.rb`: configuration and defaults
30
+ * `lib/sneakers/queue.rb`: queue declaration and consumer registration
31
+ * `lib/sneakers/runner.rb`: the main runner that spawns and manages workers
32
+ * `lib/sneakers/publisher.rb`: message publishing
33
+ * `lib/sneakers/cli.rb`: CLI, based on Thor
34
+ * `lib/sneakers/handlers/maxretry.rb`: retry handler with error queue support
35
+ * `lib/sneakers/handlers/oneshot.rb`: the default handler
36
+ * `lib/sneakers/version.rb`: `Sneakers::VERSION` constant
37
+ * `lib/active_job/queue_adapters/sneakers_adapter.rb`: an `ActiveJob` adapter for Ruby on Rails
38
+
39
+ ## Comments
40
+
41
+ * Only add important comments that express the non-obvious intent, both in tests and in the implementation
42
+ * Keep the comments short
43
+ * Pay attention to the grammar of your comments, including punctuation, full stops, articles, and so on
44
+
45
+ ## Change Log
46
+
47
+ If asked to perform change log updates, consult and modify `ChangeLog.md` and stick to its
48
+ existing writing style.
49
+
50
+ ## Releases
51
+
52
+ ### How to Roll (Produce) a New Release
53
+
54
+ Suppose the current development version in `ChangeLog.md` has
55
+ a `## Changes Between Kicks X.Y.0 and X.(Y+1).0 (in development)` section at the top.
56
+
57
+ To produce a new release:
58
+
59
+ 1. Update `ChangeLog.md`: replace `(in development)` with today's date, e.g. `(Mar 30, 2026)`. Make sure all notable changes since the previous release are listed
60
+ 2. Update the version in `lib/sneakers/version.rb` to match (remove the `.pre` suffix)
61
+ 3. Commit with the message `X.(Y+1).0` (just the version number, nothing else)
62
+ 4. Tag the commit: `git tag X.(Y+1).0`
63
+ 5. Bump the dev version: add a new `## Changes Between Kicks X.(Y+1).0 and X.(Y+2).0 (in development)` section to `ChangeLog.md` with `No changes yet.` underneath, and update `lib/sneakers/version.rb` to the next dev version with a `.pre` suffix
64
+ 6. Commit with the message `Bump dev version`
65
+ 7. Push: `git push && git push --tags`
66
+
67
+ ## Git Instructions
68
+
69
+ * Never add yourself to the list of commit co-authors
70
+ * Never mention yourself in commit messages in any way (no "Generated by", no AI tool links, etc)
71
+
72
+ ## Style Guide
73
+
74
+ * Never add full stops to Markdown list items
data/ChangeLog.md CHANGED
@@ -1,16 +1,49 @@
1
1
  # Change Log
2
2
 
3
- ## Changes Between 3.2.0 and 3.3.0 (in development)
3
+ ## Changes Between 3.2.0 and 3.3.0 (Mar 31, 2026)
4
4
 
5
- No (documented) changes yet.
5
+ ### Queue Type Inheritance for Retry and Error Queues
6
+
7
+ The `maxretry` handler now inherits the queue type from the worker queue's
8
+ arguments when declaring retry and error queues.
9
+
10
+ This can be overridden with the `:retry_queue_arguments` option.
11
+
12
+ GitHub issue: [#38](https://github.com/ruby-amqp/kicks/pull/38)
13
+
14
+ Contributed by @chris72205.
15
+
16
+ ### Improved Bunny Exception Handling for Consumers
17
+
18
+ Contributed by @shashankmehra.
19
+
20
+ GitHub issue: [#35](https://github.com/ruby-amqp/kicks/pull/35)
21
+
22
+ ### Improved Logger Configuration
23
+
24
+ Contributed by @cdhagmann.
25
+
26
+ GitHub issue: [#34](https://github.com/ruby-amqp/kicks/pull/34)
27
+
28
+ ### Bunny Version Bump
29
+
30
+ Kicks now requires the latest (at the time of writing) Bunny `2.24.x`.
31
+
32
+ ### Support Rails 7.2
33
+
34
+ Contributed by @sekrett.
35
+
36
+ GitHub issue: [#32](https://github.com/ruby-amqp/kicks/pull/32)
6
37
 
7
38
 
8
39
  ## Changes Between 3.1.0 and 3.2.0 (Jan 26, 2025)
9
40
 
10
41
  ### Improved Support for Bring-Your-Own-Connection (BYOC)
11
42
 
12
- Kicks now supports a callable (e.g. a proc) to be passed for an externally-initialized
13
- and managed Bunny connection. In this case, it is entirely up to the caller
43
+ Kicks now supports passing in a callable (e.g. a proc) instead of an externally-initialized
44
+ and managed Bunny connection.
45
+
46
+ In this case, it is entirely up to the caller
14
47
  to configure the connection and call `Bunny::Session#start` on it
15
48
  at the right moment.
16
49
 
data/kicks.gemspec CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |gem|
25
25
  gem.require_paths = ['lib']
26
26
 
27
27
  gem.add_dependency 'serverengine', '~> 2.1'
28
- gem.add_dependency 'bunny', '~> 2.19'
28
+ gem.add_dependency 'bunny', '~> 2.24'
29
29
  gem.add_dependency 'concurrent-ruby', '~> 1.0'
30
30
  gem.add_dependency 'thor'
31
31
  gem.add_dependency 'rake', '>= 12.3', '< 14.0'
@@ -8,7 +8,7 @@ module ActiveJob
8
8
  # To use Sneakers set the queue_adapter config to +:sneakers+.
9
9
  #
10
10
  # Rails.application.config.active_job.queue_adapter = :sneakers
11
- class SneakersAdapter
11
+ class SneakersAdapter < (const_defined?(:AbstractAdapter) ? AbstractAdapter : Object)
12
12
  def initialize
13
13
  @monitor = Monitor.new
14
14
  end
@@ -5,7 +5,7 @@ module Sneakers
5
5
  class Configuration
6
6
 
7
7
  extend Forwardable
8
- def_delegators :@hash, :to_hash, :[], :[]=, :==, :fetch, :delete, :has_key?, :dig
8
+ def_delegators :@hash, :to_hash, :[], :[]=, :==, :fetch, :delete, :has_key?, :dig, :slice
9
9
 
10
10
  EXCHANGE_OPTION_DEFAULTS = {
11
11
  :type => :direct,
@@ -35,6 +35,11 @@ module Sneakers
35
35
  :log => STDOUT,
36
36
  :pid_path => 'sneakers.pid',
37
37
  :amqp_heartbeat => 30,
38
+
39
+ # Default values from serverengine
40
+ :log_rotate_age => 5,
41
+ :log_rotate_size => 1048576,
42
+ :log_level => 'debug',
38
43
 
39
44
  # workers
40
45
  :prefetch => 10,
@@ -60,19 +60,24 @@ module Sneakers
60
60
  Sneakers.logger.debug do
61
61
  "#{log_prefix} creating queue=#{retry_name} x-dead-letter-exchange=#{requeue_name}"
62
62
  end
63
+ retry_args = retry_queue_arguments.merge(
64
+ :'x-dead-letter-exchange' => requeue_name,
65
+ :'x-message-ttl' => @opts[:retry_timeout] || 60000
66
+ )
63
67
  @retry_queue = @channel.queue(retry_name,
64
68
  :durable => queue_durable?,
65
- :arguments => {
66
- :'x-dead-letter-exchange' => requeue_name,
67
- :'x-message-ttl' => @opts[:retry_timeout] || 60000
68
- })
69
+ :arguments => retry_args)
69
70
  @retry_queue.bind(@retry_exchange, :routing_key => '#')
70
71
 
71
72
  Sneakers.logger.debug do
72
73
  "#{log_prefix} creating queue=#{error_name}"
73
74
  end
74
- @error_queue = @channel.queue(error_name,
75
- :durable => queue_durable?)
75
+ error_args = retry_queue_arguments
76
+ if error_args.empty?
77
+ @error_queue = @channel.queue(error_name, :durable => queue_durable?)
78
+ else
79
+ @error_queue = @channel.queue(error_name, :durable => queue_durable?, :arguments => error_args)
80
+ end
76
81
  @error_queue.bind(@error_exchange, :routing_key => '#')
77
82
 
78
83
  # Finally, bind the worker queue to our requeue exchange
@@ -218,6 +223,17 @@ module Sneakers
218
223
  def exchange_durable?
219
224
  queue_durable?
220
225
  end
226
+
227
+ def retry_queue_arguments
228
+ if @opts[:retry_queue_arguments]
229
+ @opts[:retry_queue_arguments].transform_keys(&:to_sym)
230
+ elsif (queue_type = @opts.dig(:queue_options, :arguments, :'x-queue-type') ||
231
+ @opts.dig(:queue_options, :arguments, 'x-queue-type'))
232
+ { :'x-queue-type' => queue_type }
233
+ else
234
+ {}
235
+ end
236
+ end
221
237
  end
222
238
  end
223
239
  end
@@ -1,3 +1,3 @@
1
1
  module Sneakers
2
- VERSION = "3.2.0"
2
+ VERSION = "3.3.0"
3
3
  end
@@ -76,7 +76,7 @@ module Sneakers
76
76
  end
77
77
  res = block_to_call.call(deserialized_msg, delivery_info, metadata, handler)
78
78
  end
79
- rescue SignalException, SystemExit
79
+ rescue SignalException, SystemExit, Bunny::Exception
80
80
  # ServerEngine handles these exceptions, so they are not expected to be raised within the worker.
81
81
  # Nevertheless, they are listed here to ensure that they are not caught by the rescue block below.
82
82
  raise
data/lib/sneakers.rb CHANGED
@@ -46,11 +46,11 @@ module Sneakers
46
46
  @configured = false
47
47
  end
48
48
 
49
- def daemonize!(loglevel=Logger::INFO)
49
+ def daemonize!(loglevel=nil)
50
50
  CONFIG[:log] = 'sneakers.log'
51
+ CONFIG[:log_level] = loglevel || Logger::INFO
51
52
  CONFIG[:daemonize] = true
52
53
  setup_general_logger!
53
- logger.level = loglevel
54
54
  end
55
55
 
56
56
  def rake_worker_classes=(worker_classes)
@@ -109,7 +109,7 @@ module Sneakers
109
109
  if [:info, :debug, :error, :warn].all?{ |meth| CONFIG[:log].respond_to?(meth) }
110
110
  @logger = CONFIG[:log]
111
111
  else
112
- @logger = ServerEngine::DaemonLogger.new(CONFIG[:log])
112
+ @logger = ServerEngine::DaemonLogger.new(CONFIG[:log], CONFIG.slice(:log_level, :log_rotate_age, :log_rotate_size))
113
113
  @logger.formatter = Sneakers::Support::ProductionFormatter
114
114
  end
115
115
  end
@@ -124,4 +124,3 @@ module Sneakers
124
124
  @publisher = Sneakers::Publisher.new
125
125
  end
126
126
  end
127
-
@@ -466,4 +466,118 @@ describe 'Handlers' do
466
466
  end
467
467
  end
468
468
  end
469
+
470
+ describe 'Maxretry queue arguments inheritance' do
471
+ let(:channel) { Object.new }
472
+ let(:queue) { Object.new }
473
+
474
+ before(:each) do
475
+ Sneakers.configure(:daemonize => true, :log => 'sneakers.log')
476
+ Sneakers::Worker.configure_logger(Logger.new('/dev/null'))
477
+ Sneakers::Worker.configure_metrics
478
+ end
479
+
480
+ describe 'with x-queue-type in queue_options' do
481
+ before do
482
+ @opts = {
483
+ :exchange => 'sneakers',
484
+ :queue_options => {
485
+ :durable => 'true',
486
+ :arguments => { :'x-queue-type' => 'quorum' }
487
+ }
488
+ }
489
+
490
+ mock(queue).name { 'downloads' }
491
+
492
+ @retry_exchange = Object.new
493
+ @error_exchange = Object.new
494
+ @requeue_exchange = Object.new
495
+ @retry_queue = Object.new
496
+ @error_queue = Object.new
497
+
498
+ mock(channel).exchange('downloads-retry',
499
+ :type => 'topic',
500
+ :durable => 'true').once { @retry_exchange }
501
+ mock(channel).exchange('downloads-error',
502
+ :type => 'topic',
503
+ :durable => 'true').once { @error_exchange }
504
+ mock(channel).exchange('downloads-retry-requeue',
505
+ :type => 'topic',
506
+ :durable => 'true').once { @requeue_exchange }
507
+
508
+ mock(channel).queue('downloads-retry',
509
+ :durable => 'true',
510
+ :arguments => {
511
+ :'x-dead-letter-exchange' => 'downloads-retry-requeue',
512
+ :'x-message-ttl' => 60000,
513
+ :'x-queue-type' => 'quorum'
514
+ }).once { @retry_queue }
515
+ mock(@retry_queue).bind(@retry_exchange, :routing_key => '#')
516
+
517
+ mock(channel).queue('downloads-error',
518
+ :durable => 'true',
519
+ :arguments => { :'x-queue-type' => 'quorum' }).once { @error_queue }
520
+ mock(@error_queue).bind(@error_exchange, :routing_key => '#')
521
+
522
+ mock(queue).bind(@requeue_exchange, :routing_key => '#')
523
+ end
524
+
525
+ it 'inherits queue type for retry and error queues' do
526
+ handler = Sneakers::Handlers::Maxretry.new(channel, queue, @opts)
527
+ _(handler).wont_be_nil
528
+ end
529
+ end
530
+
531
+ describe 'with explicit retry_queue_arguments override' do
532
+ before do
533
+ @opts = {
534
+ :exchange => 'sneakers',
535
+ :queue_options => {
536
+ :durable => 'true',
537
+ :arguments => { :'x-queue-type' => 'quorum' }
538
+ },
539
+ :retry_queue_arguments => { :'x-queue-type' => 'classic' }
540
+ }
541
+
542
+ mock(queue).name { 'downloads' }
543
+
544
+ @retry_exchange = Object.new
545
+ @error_exchange = Object.new
546
+ @requeue_exchange = Object.new
547
+ @retry_queue = Object.new
548
+ @error_queue = Object.new
549
+
550
+ mock(channel).exchange('downloads-retry',
551
+ :type => 'topic',
552
+ :durable => 'true').once { @retry_exchange }
553
+ mock(channel).exchange('downloads-error',
554
+ :type => 'topic',
555
+ :durable => 'true').once { @error_exchange }
556
+ mock(channel).exchange('downloads-retry-requeue',
557
+ :type => 'topic',
558
+ :durable => 'true').once { @requeue_exchange }
559
+
560
+ mock(channel).queue('downloads-retry',
561
+ :durable => 'true',
562
+ :arguments => {
563
+ :'x-dead-letter-exchange' => 'downloads-retry-requeue',
564
+ :'x-message-ttl' => 60000,
565
+ :'x-queue-type' => 'classic'
566
+ }).once { @retry_queue }
567
+ mock(@retry_queue).bind(@retry_exchange, :routing_key => '#')
568
+
569
+ mock(channel).queue('downloads-error',
570
+ :durable => 'true',
571
+ :arguments => { :'x-queue-type' => 'classic' }).once { @error_queue }
572
+ mock(@error_queue).bind(@error_exchange, :routing_key => '#')
573
+
574
+ mock(queue).bind(@requeue_exchange, :routing_key => '#')
575
+ end
576
+
577
+ it 'uses explicit retry_queue_arguments over inherited' do
578
+ handler = Sneakers::Handlers::Maxretry.new(channel, queue, @opts)
579
+ _(handler).wont_be_nil
580
+ end
581
+ end
582
+ end
469
583
  end
@@ -218,7 +218,10 @@ describe Sneakers::Worker do
218
218
  :hooks => {},
219
219
  :handler => Sneakers::Handlers::Oneshot,
220
220
  :heartbeat => 30,
221
- :amqp_heartbeat => 30
221
+ :amqp_heartbeat => 30,
222
+ :log_rotate_age => 5,
223
+ :log_rotate_size => 1048576,
224
+ :log_level => "debug"
222
225
  )
223
226
  end
224
227
 
@@ -256,7 +259,10 @@ describe Sneakers::Worker do
256
259
  :hooks => {},
257
260
  :handler => Sneakers::Handlers::Oneshot,
258
261
  :heartbeat => 5,
259
- :amqp_heartbeat => 30
262
+ :amqp_heartbeat => 30,
263
+ :log_rotate_age => 5,
264
+ :log_rotate_size => 1048576,
265
+ :log_level => "debug"
260
266
  )
261
267
  end
262
268
 
@@ -294,7 +300,10 @@ describe Sneakers::Worker do
294
300
  :hooks => {},
295
301
  :handler => Sneakers::Handlers::Oneshot,
296
302
  :heartbeat => 30,
297
- :amqp_heartbeat => 30
303
+ :amqp_heartbeat => 30,
304
+ :log_rotate_age => 5,
305
+ :log_rotate_size => 1048576,
306
+ :log_level => "debug"
298
307
  )
299
308
  end
300
309
  end
@@ -437,6 +446,14 @@ describe Sneakers::Worker do
437
446
  w.do_work(header, nil, "msg", handler)
438
447
  end
439
448
 
449
+ it "should not catch bunny exceptions" do
450
+ w = DummyWorker.new(@queue, TestPool.new)
451
+ mock(w).work("msg").once{ raise Bunny::Exception }
452
+ assert_raises(Bunny::Exception) do
453
+ w.do_work(nil, nil, "msg", nil)
454
+ end
455
+ end
456
+
440
457
  it "should log exceptions from workers" do
441
458
  handler = Object.new
442
459
  header = Object.new
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kicks
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dotan Nahum
8
8
  - Michael Klishin
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2025-01-26 00:00:00.000000000 Z
12
+ date: 2026-04-01 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: serverengine
@@ -30,14 +31,14 @@ dependencies:
30
31
  requirements:
31
32
  - - "~>"
32
33
  - !ruby/object:Gem::Version
33
- version: '2.19'
34
+ version: '2.24'
34
35
  type: :runtime
35
36
  prerelease: false
36
37
  version_requirements: !ruby/object:Gem::Requirement
37
38
  requirements:
38
39
  - - "~>"
39
40
  - !ruby/object:Gem::Version
40
- version: '2.19'
41
+ version: '2.24'
41
42
  - !ruby/object:Gem::Dependency
42
43
  name: concurrent-ruby
43
44
  requirement: !ruby/object:Gem::Requirement
@@ -265,6 +266,7 @@ files:
265
266
  - ".github/dependabot.yml"
266
267
  - ".github/workflows/ci.yml"
267
268
  - ".gitignore"
269
+ - AGENTS.md
268
270
  - ChangeLog.md
269
271
  - Dockerfile
270
272
  - Dockerfile.slim
@@ -342,6 +344,7 @@ licenses:
342
344
  - MIT
343
345
  metadata:
344
346
  source_code_uri: https://github.com/ruby-amqp/kicks
347
+ post_install_message:
345
348
  rdoc_options: []
346
349
  require_paths:
347
350
  - lib
@@ -356,7 +359,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
356
359
  - !ruby/object:Gem::Version
357
360
  version: '0'
358
361
  requirements: []
359
- rubygems_version: 3.6.2
362
+ rubygems_version: 3.4.19
363
+ signing_key:
360
364
  specification_version: 4
361
365
  summary: Fast background processing framework for Ruby and RabbitMQ
362
366
  test_files: