racecar 2.11.0 → 3.0.0.alpha.1

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: 7bdff11cf4a069df69e0a54651daa9c01ba29f1dc6182ec4743da1516ba32d54
4
- data.tar.gz: 76f26192c1fdd82c95f3f0f23b4c64f18c10be1febceb585b3689136544ecb2b
3
+ metadata.gz: 8d48006d6ad7a9f1cb57902edbd8a2785f3e05c629a71c3345238a0841abeda6
4
+ data.tar.gz: 06b557c5757e1c4bda2d1d6cebd2e679ab27053bc02f58636911f8df8d67f217
5
5
  SHA512:
6
- metadata.gz: 10656d95fba4b15e16ef742e6483f3e26de66dc192a50de2f5023134505695f10884bd8cbf5679cf5cc5cc18f76038ec5699fe68813c22ea7e73b8b3387989bf
7
- data.tar.gz: 0ea9a87038a2dd66275910cb9e651d648f8cddf5a311d29aed1af0c4ab3a395ae7812baaa9d673fc3954e18f684f03dbf40e4b202f5c58c0997202426bda777e
6
+ metadata.gz: 9e6cd591d16f776a84140105fd70eb565c70c6001ac03c5f61ea0ef45b40a8fd29e656a8da4b445894c87d9bb6002376364d8de896af481847222e4585b37374
7
+ data.tar.gz: 6f1b2afeabde54e49e61ac8242b5a97daf3425f64bbe74bd3d0b69259a67d53a3113224273882779f6e12fc4bf97d1851169640c32a8ed56dcae6ce0b14a00f8
@@ -0,0 +1,4 @@
1
+ # CODEOWNERS file
2
+ # This file defines who should review code changes in this repository.
3
+
4
+ * @zendesk/core-gem-owners
@@ -2,21 +2,22 @@ name: CI
2
2
 
3
3
  on:
4
4
  pull_request:
5
- branches: ["master"]
5
+ branches: ["main"]
6
6
  push:
7
- branches: ["master"]
7
+ branches: ["main"]
8
8
 
9
9
  jobs:
10
10
  unit-specs:
11
11
  runs-on: ubuntu-latest
12
12
 
13
13
  strategy:
14
+ fail-fast: false
14
15
  matrix:
15
16
  ruby-version:
16
- - "3.0"
17
17
  - "3.1"
18
18
  - "3.2"
19
19
  - "3.3"
20
+ - "3.4"
20
21
 
21
22
  steps:
22
23
  - uses: actions/checkout@v4
@@ -30,15 +31,23 @@ jobs:
30
31
 
31
32
  integration-specs:
32
33
  runs-on: ubuntu-latest
34
+ strategy:
35
+ fail-fast: false
36
+ matrix:
37
+ ruby-version:
38
+ - "3.1"
39
+ - "3.2"
40
+ - "3.3"
41
+ - "3.4"
33
42
  steps:
34
43
  - uses: actions/checkout@v4
35
44
  - name: Set up Ruby
36
45
  uses: ruby/setup-ruby@v1
37
46
  with:
38
- ruby-version: "3.0"
47
+ ruby-version: ${{ matrix.ruby-version }}
39
48
  bundler-cache: true
40
49
  - name: Bring up docker-compose stack
41
- run: docker-compose up -d
50
+ run: docker compose up -d
42
51
  - name: Build and test with RSpec
43
52
  env:
44
53
  RACECAR_BROKERS: localhost:9092
@@ -1,12 +1,26 @@
1
- name: Publish Gem
1
+ name: Publish to RubyGems.org
2
2
 
3
3
  on:
4
4
  push:
5
- tags: v*
5
+ branches: main
6
+ paths: lib/racecar/version.rb
7
+ workflow_dispatch:
6
8
 
7
9
  jobs:
8
- call-workflow:
9
- uses: zendesk/gw/.github/workflows/ruby-gem-publication.yml@main
10
- secrets:
11
- RUBY_GEMS_API_KEY: ${{ secrets.RUBY_GEMS_API_KEY }}
12
- RUBY_GEMS_TOTP_DEVICE: ${{ secrets.RUBY_GEMS_TOTP_DEVICE }}
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ environment: rubygems-publish
13
+ if: github.repository_owner == 'zendesk'
14
+ permissions:
15
+ id-token: write
16
+ contents: write
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ bundler-cache: false
23
+ ruby-version: "3.4"
24
+ - name: Install dependencies
25
+ run: bundle install
26
+ - uses: rubygems/release-gem@v1
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --format documentation
2
2
  --require spec_helper
3
3
  --color
4
+ --order random
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 2.12.0
6
+
7
+ * Add tests against Ruby 3.4
8
+ * Drop support for Ruby 3.0
9
+ * Allow rdkafka gem versions newer than 0.15.0
10
+
5
11
  ## 2.11.0
6
12
 
7
13
  * Allow the liveness probe command to skip loading config files
data/Gemfile CHANGED
@@ -5,6 +5,4 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in racecar.gemspec
6
6
  gemspec
7
7
 
8
- # We actually support version 7.x (see gemspec); this extra restriction is added just for running the test suite also
9
- # on Ruby 2.6, which activesupport 7.0 does not support.
10
- gem 'activesupport', '~> 6.1.0'
8
+ gem 'activesupport', '~> 7.2.0'
data/Gemfile.lock CHANGED
@@ -1,31 +1,43 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- racecar (2.11.0)
4
+ racecar (3.0.0.alpha.1)
5
5
  king_konf (~> 1.0.0)
6
- rdkafka (~> 0.15.0)
6
+ rdkafka (>= 0.15.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activesupport (6.1.7.3)
12
- concurrent-ruby (~> 1.0, >= 1.0.2)
11
+ activesupport (7.2.2.1)
12
+ base64
13
+ benchmark (>= 0.3)
14
+ bigdecimal
15
+ concurrent-ruby (~> 1.0, >= 1.3.1)
16
+ connection_pool (>= 2.2.5)
17
+ drb
13
18
  i18n (>= 1.6, < 2)
19
+ logger (>= 1.4.2)
14
20
  minitest (>= 5.1)
15
- tzinfo (~> 2.0)
16
- zeitwerk (~> 2.3)
21
+ securerandom (>= 0.3)
22
+ tzinfo (~> 2.0, >= 2.0.5)
23
+ base64 (0.2.0)
24
+ benchmark (0.4.0)
25
+ bigdecimal (3.1.9)
17
26
  byebug (11.1.3)
18
27
  coderay (1.1.3)
19
- concurrent-ruby (1.2.2)
28
+ concurrent-ruby (1.3.4)
29
+ connection_pool (2.4.1)
20
30
  diff-lcs (1.5.0)
21
31
  dogstatsd-ruby (5.5.0)
32
+ drb (2.2.1)
22
33
  ffi (1.16.3)
23
34
  i18n (1.12.0)
24
35
  concurrent-ruby (~> 1.0)
25
36
  king_konf (1.0.1)
37
+ logger (1.6.4)
26
38
  method_source (1.0.0)
27
39
  mini_portile2 (2.8.5)
28
- minitest (5.18.0)
40
+ minitest (5.25.4)
29
41
  pry (0.14.2)
30
42
  coderay (~> 1.1)
31
43
  method_source (~> 1.0)
@@ -33,7 +45,7 @@ GEM
33
45
  byebug (~> 11.0)
34
46
  pry (>= 0.13, < 0.15)
35
47
  rake (13.0.6)
36
- rdkafka (0.15.1)
48
+ rdkafka (0.21.0)
37
49
  ffi (~> 1.15)
38
50
  mini_portile2 (~> 2.6)
39
51
  rake (> 12)
@@ -50,16 +62,16 @@ GEM
50
62
  diff-lcs (>= 1.2.0, < 2.0)
51
63
  rspec-support (~> 3.12.0)
52
64
  rspec-support (3.12.0)
65
+ securerandom (0.4.1)
53
66
  timecop (0.9.6)
54
67
  tzinfo (2.0.6)
55
68
  concurrent-ruby (~> 1.0)
56
- zeitwerk (2.6.7)
57
69
 
58
70
  PLATFORMS
59
71
  ruby
60
72
 
61
73
  DEPENDENCIES
62
- activesupport (~> 6.1.0)
74
+ activesupport (~> 7.2.0)
63
75
  bundler (>= 1.13, < 3)
64
76
  dogstatsd-ruby (>= 4.0.0, < 6.0.0)
65
77
  pry-byebug
@@ -69,4 +81,4 @@ DEPENDENCIES
69
81
  timecop
70
82
 
71
83
  BUNDLED WITH
72
- 2.4.9
84
+ 2.6.2
data/README.md CHANGED
@@ -418,7 +418,7 @@ Racecar supports [Datadog](https://www.datadoghq.com/) monitoring integration. I
418
418
  - `datadog_namespace` – The namespace to use for Datadog metrics.
419
419
  - `datadog_tags` – Tags that should always be set on Datadog metrics.
420
420
 
421
- Furthermore, there's a [standard Datadog dashboard configuration file](https://raw.githubusercontent.com/zendesk/racecar/master/extra/datadog-dashboard.json) that you can import to get started with a Racecar dashboard for all of your consumers.
421
+ Furthermore, there's a [standard Datadog dashboard configuration file](https://raw.githubusercontent.com/zendesk/racecar/main/extra/datadog-dashboard.json) that you can import to get started with a Racecar dashboard for all of your consumers.
422
422
 
423
423
  #### Consumers Without Rails
424
424
 
@@ -716,19 +716,35 @@ apt-get update && apt-get install -y libzstd-dev
716
716
 
717
717
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
718
718
 
719
- The integration tests run against a Kafka instance that is not automatically started from within `rspec`. You can set one up using the provided `docker-compose.yml` by running `docker-compose up`.
719
+ The integration tests run against a Kafka instance that is not automatically started from within `rspec`. You can set one up using the provided `docker-compose.yml` by running `docker compose up`.
720
720
 
721
721
  ### Running RSpec within Docker
722
722
 
723
723
  There can be behavioural inconsistencies between running the specs on your machine, and in the CI pipeline. Due to this, there is now a Dockerfile included in the project, which is based on the CircleCI ruby 2.7.8 image. This could easily be extended with more Dockerfiles to cover different Ruby versions if desired. In order to run the specs via Docker:
724
724
 
725
725
  - Uncomment the `tests` service from the docker-compose.yml
726
- - Bring up the stack with `docker-compose up -d`
727
- - Execute the entire suite with `docker-compose run --rm tests bundle exec rspec`
728
- - Execute a single spec or directory with `docker-compose run --rm tests bundle exec rspec spec/integration/consumer_spec.rb`
726
+ - Bring up the stack with `docker compose up -d`
727
+ - Execute the entire suite with `docker compose run --rm tests bundle exec rspec`
728
+ - Execute a single spec or directory with `docker compose run --rm tests bundle exec rspec spec/integration/consumer_spec.rb`
729
729
 
730
730
  Please note - your code directory is mounted as a volume, so you can make code changes without needing to rebuild
731
731
 
732
+ ### Releasing a new version
733
+
734
+ A new version is published to RubyGems.org every time a change to `version.rb` is pushed to the `main` branch.
735
+ In short, follow these steps:
736
+ 1. Update `version.rb`,
737
+ 2. run `bundle lock` to update `Gemfile.lock`,
738
+ 3. merge this change into `main`, and
739
+ 4. look at [the action](https://github.com/zendesk/racecar/actions/workflows/publish.yml) for output.
740
+
741
+ To create a pre-release from a non-main branch:
742
+ 1. change the version in `version.rb` to something like `2.13.0.pre.1` or `3.0.0.beta.2`,
743
+ 2. push this change to your branch,
744
+ 3. go to [Actions → “Publish to RubyGems.org” on GitHub](https://github.com/zendesk/racecar/actions/workflows/publish.yml),
745
+ 4. click the “Run workflow” button,
746
+ 5. pick your branch from a dropdown.
747
+
732
748
  ## Contributing
733
749
 
734
750
  Bug reports and pull requests are welcome on [GitHub](https://github.com/zendesk/racecar). Feel free to [join our Slack team](https://ruby-kafka-slack.herokuapp.com/) and ask how best to contribute!
data/Rakefile CHANGED
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bundler/setup"
3
4
  require "bundler/gem_tasks"
4
5
  require "rspec/core/rake_task"
5
6
 
6
- # Pushing to rubygems is handled by a github workflow
7
- ENV["gem_push"] = "false"
8
-
9
7
  RSpec::Core::RakeTask.new(:spec)
10
8
 
11
9
  task :default => :spec
data/docker-compose.yml CHANGED
@@ -1,8 +1,6 @@
1
- version: '2.1'
2
-
3
1
  services:
4
2
  zookeeper:
5
- image: confluentinc/cp-zookeeper:5.5.1
3
+ image: confluentinc/cp-zookeeper:7.8.1
6
4
  ports:
7
5
  - "2181:2181"
8
6
  environment:
@@ -13,7 +11,7 @@ services:
13
11
  test: echo ruok | nc 127.0.0.1 2181 | grep imok
14
12
 
15
13
  broker:
16
- image: confluentinc/cp-kafka:5.5.1
14
+ image: confluentinc/cp-kafka:7.8.1
17
15
  depends_on:
18
16
  - zookeeper
19
17
  ports:
@@ -57,7 +55,7 @@ services:
57
55
  # RACECAR_BROKERS: broker:29092
58
56
  # DOCKER_SUDO: 'true'
59
57
  # # When bringing up the stack, we just let the container exit. For running the
60
- # # specs, we'll use commands like `docker-compose run tests rspec`
58
+ # # specs, we'll use commands like `docker compose run tests rspec`
61
59
  # command: ["echo", "ready"]
62
60
  # volumes:
63
61
  # # The line below allows us to run docker commands from the container itself
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'racecar/pause'
4
+ require 'concurrent-ruby'
5
+
6
+ module Racecar
7
+ class AsyncPartitionProcessor
8
+ attr_reader :thread
9
+
10
+ THREAD_KEY_IDENTIFIER = 'racecar_topic_partition_identifier'.freeze
11
+
12
+ def self.thread_key(topic, partition)
13
+ "#{topic}/#{partition}"
14
+ end
15
+
16
+ def initialize(topic:, partition:, logger:, config:, consumer:, consumer_class:, instrumenter:, rdkafka_consumer:)
17
+ @topic = topic
18
+ @partition = partition
19
+ @logger = logger
20
+ @config = config
21
+ @consumer = consumer
22
+ @consumer_class = consumer_class
23
+ @instrumenter = instrumenter
24
+ @rdkafka_consumer = rdkafka_consumer
25
+ @backpressure_paused = Concurrent::AtomicBoolean.new
26
+ @tpl = build_tpl(topic, partition)
27
+ setup_async_processing
28
+ end
29
+
30
+ def process(message)
31
+ push(message)
32
+ end
33
+
34
+ def process_batch(messages)
35
+ push(messages)
36
+ end
37
+
38
+ def rebalance!
39
+ processor.rebalance!
40
+ @queue << nil
41
+ end
42
+
43
+ def shut_down!
44
+ processor.shut_down!
45
+ @queue << nil
46
+ end
47
+
48
+ def rebalancing_or_shutting_down?
49
+ processor.rebalancing_or_shutting_down?
50
+ end
51
+
52
+ def resume_paused_partition
53
+ processor.resume_paused_partition
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :backpressure_paused, :instrumenter, :consumer_class, :consumer, :queue, :config, :processor, :logger
59
+
60
+ def setup_async_processing
61
+ @processor = PartitionProcessor.new(
62
+ config: config,
63
+ logger: logger,
64
+ instrumenter: instrumenter,
65
+ consumer_class_instance: consumer_class.new,
66
+ consumer: consumer,
67
+ topic: @topic,
68
+ partition: @partition,
69
+ pause: Pause.new_from_config(config),
70
+ rdkafka_consumer: @rdkafka_consumer,
71
+ )
72
+ @queue = Queue.new
73
+ @thread = nil
74
+
75
+ use_process_batch = consumer_class.method_defined?(:process_batch)
76
+
77
+ if use_process_batch
78
+ spawn_thread do |msgs|
79
+ processor.process_batch(msgs)
80
+ end
81
+ else
82
+ spawn_thread do |msgs|
83
+ msgs.each do |msg|
84
+ processor.process(msg)
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ def spawn_thread(&block)
91
+ @thread = Thread.new do
92
+ Thread.current.name = "Racecar thread for #{thread_key}"
93
+ Thread.current[AsyncPartitionProcessor::THREAD_KEY_IDENTIFIER] = thread_key
94
+ main_processing_loop(block)
95
+ end
96
+ end
97
+
98
+ def push(messages)
99
+ @queue << Array(messages)
100
+ maybe_apply_backpressure
101
+ end
102
+
103
+ def maybe_apply_backpressure
104
+ if @backpressure_paused.false? && @queue.size >= config.multithreaded_processing_max_queue_size
105
+ @backpressure_paused.make_true
106
+ @rdkafka_consumer.pause(@tpl)
107
+ logger.debug "Paused partition #{@topic}/#{@partition}: queue reached capacity (#{@queue.size}/#{config.multithreaded_processing_max_queue_size})"
108
+ end
109
+ end
110
+
111
+ def maybe_resume_the_partition
112
+ if @backpressure_paused.true? && @queue.size < config.multithreaded_processing_resume_threshold * config.multithreaded_processing_max_queue_size
113
+ @backpressure_paused.make_false
114
+ @rdkafka_consumer.resume(@tpl)
115
+ end
116
+ end
117
+
118
+ def build_tpl(topic, partition)
119
+ Rdkafka::Consumer::TopicPartitionList.new.tap do |tpl|
120
+ tpl.add_topic_and_partitions_with_offsets(topic, partition => -1001)
121
+ end
122
+ end
123
+
124
+ def thread_key
125
+ self.class.thread_key(@topic, @partition)
126
+ end
127
+
128
+ def main_processing_loop(block)
129
+ loop do
130
+ msgs = @queue.pop
131
+ break if msgs.nil?
132
+
133
+ maybe_resume_the_partition
134
+ block.call(msgs)
135
+ rescue => e
136
+ logger.error "Error in processing thread for #{thread_key}: #{e.class} - #{e.full_message}. backtrace: #{e.backtrace&.first(10)&.join("\n")}"
137
+ end
138
+ ensure
139
+ @processor.teardown
140
+ end
141
+ end
142
+ end
data/lib/racecar/cli.rb CHANGED
@@ -65,8 +65,7 @@ module Racecar
65
65
  config.install_liveness_probe
66
66
  end
67
67
 
68
- processor = consumer_class.new
69
- @runner = Racecar.runner(processor)
68
+ @runner = Racecar.runner(consumer_class)
70
69
  @runner.run
71
70
  nil
72
71
  end
@@ -194,6 +194,18 @@ module Racecar
194
194
  desc "Strategy for switching topics when there are multiple subscriptions. `exhaust-topic` will only switch when the consumer poll returns no messages. `round-robin` will switch after each poll regardless.\nWarning: `round-robin` will be the default in Racecar 3.x"
195
195
  string :multi_subscription_strategy, allowed_values: %w(round-robin exhaust-topic), default: "exhaust-topic"
196
196
 
197
+ desc "Whether multithreaded processing is enabled"
198
+ boolean :multithreaded_processing_enabled, default: false
199
+
200
+ desc "Max size of the queue of messages waiting to be processed when multithreaded processing is enabled"
201
+ integer :multithreaded_processing_max_queue_size, default: 1000
202
+
203
+ desc "Timeout in seconds for the main thread to wait for a processing thread to finish when shutting down the consumer with multithreaded processing enabled"
204
+ integer :multithreaded_processing_shutdown_timeout, default: 300
205
+
206
+ desc "Multi threaded queue resume threshold as a percentage of `multithreaded_processing_max_queue_size`. Defaults to 0.5, meaning that the consumer will attempt to resume a paused partition when the queue size drops below 50% of the max queue size."
207
+ float :multithreaded_processing_resume_threshold, default: 0.5
208
+
197
209
  # The error handler must be set directly on the object.
198
210
  attr_reader :error_handler
199
211
 
@@ -68,16 +68,16 @@ module Racecar
68
68
  @instrumenter.instrument('deliver_messages', instrumentation_payload) do
69
69
  @delivery_handles.each do |handle|
70
70
  begin
71
- # rdkafka-ruby checks every wait_timeout seconds if the message was
72
- # successfully delivered, up to max_wait_timeout seconds before raising
73
- # Rdkafka::AbstractHandle::WaitTimeoutError. librdkafka will (re)try to
74
- # deliver all messages in the background, until "config.message_timeout"
71
+ # rdkafka-ruby checks with exponential backoff starting at 0 seconds wait
72
+ # if the message was successfully delivered, up to max_wait_timeout seconds
73
+ # before raising Rdkafka::AbstractHandle::WaitTimeoutError. librdkafka will
74
+ # (re)try to deliver all messages in the background, until "config.message_timeout"
75
75
  # (message.timeout.ms) is exceeded. Phrased differently, rdkafka-ruby's
76
76
  # WaitTimeoutError is just informative.
77
77
  # The raising can be avoided if max_wait_timeout below is greater than
78
78
  # config.message_timeout, but config is not available here (without
79
79
  # changing the interface).
80
- handle.wait(max_wait_timeout: 60, wait_timeout: 0.1)
80
+ handle.wait(max_wait_timeout: 60)
81
81
  rescue Rdkafka::AbstractHandle::WaitTimeoutError => e
82
82
  partition = MessageDeliveryError.partition_from_delivery_handle(handle)
83
83
  # ideally we could use the logger passed to the Runner, but it is not
@@ -1,21 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "racecar/delivery_callback"
4
+
3
5
  module Racecar
4
6
  class ConsumerSet
5
7
  MAX_POLL_TRIES = 10
6
8
 
7
- def initialize(config, logger, instrumenter = NullInstrumenter)
9
+ def initialize(config, logger, partition_processors, instrumenter = NullInstrumenter)
8
10
  @config, @logger = config, logger
9
11
  @instrumenter = instrumenter
12
+ @partition_processors = partition_processors
10
13
  raise ArgumentError, "Subscriptions must not be empty when subscribing" if @config.subscriptions.empty?
11
14
 
12
15
  @consumers = []
13
16
  @consumer_id_iterator = (0...@config.subscriptions.size).cycle
17
+ @producer_mutex = Mutex.new
14
18
 
15
19
  @previous_retries = 0
16
20
 
17
21
  @last_poll_read_nil_message = false
18
- @paused_tpls = Hash.new { |h, k| h[k] = {} }
19
22
  end
20
23
 
21
24
  def poll(max_wait_time_ms = @config.max_wait_time_ms)
@@ -48,8 +51,9 @@ module Racecar
48
51
  messages
49
52
  end
50
53
 
51
- def store_offset(message)
52
- current.store_offset(message)
54
+ def store_offset(message, raw_consumer = nil)
55
+ consumer = raw_consumer || current
56
+ consumer.store_offset(message)
53
57
  rescue Rdkafka::RdkafkaError => e
54
58
  if e.code == :state # -172
55
59
  @logger.warn "Attempted to store_offset, but we're not subscribed to it: #{ErroneousStateError.new(e)}"
@@ -66,13 +70,28 @@ module Racecar
66
70
 
67
71
  def close
68
72
  each_subscribed(&:close)
69
- @paused_tpls.clear
73
+ reset_producer!
74
+ end
75
+
76
+ def producer
77
+ @producer_mutex.synchronize do
78
+ @producer ||= Rdkafka::Config.new(producer_config).producer.tap do |p|
79
+ p.delivery_callback = Racecar::DeliveryCallback.new(instrumenter: @instrumenter)
80
+ end
81
+ end
82
+ end
83
+
84
+ def reset_producer!
85
+ @producer_mutex.synchronize do
86
+ @producer&.close
87
+ @producer = nil
88
+ end
70
89
  end
71
90
 
72
91
  def current
73
92
  @consumers[@consumer_id_iterator.peek] ||= begin
74
93
  consumer_config = Rdkafka::Config.new(rdkafka_config(current_subscription))
75
- listener = RebalanceListener.new(@config.consumer_class, @instrumenter)
94
+ listener = RebalanceListener.new(@config, @instrumenter, @partition_processors)
76
95
  consumer_config.consumer_rebalance_listener = listener
77
96
  consumer = consumer_config.consumer
78
97
  listener.rdkafka_consumer = consumer
@@ -86,44 +105,38 @@ module Racecar
86
105
 
87
106
  def each_subscribed
88
107
  if block_given?
89
- @consumers.each { |c| yield c }
108
+ @consumers.compact.each { |c| yield c }
90
109
  else
91
- @consumers.each
110
+ @consumers.compact.each
92
111
  end
93
112
  end
94
113
 
95
- def pause(topic, partition, offset)
114
+ def pause(topic, partition, offset = nil)
96
115
  consumer, filtered_tpl = find_consumer_by(topic, partition)
97
- if !consumer
116
+ unless consumer
98
117
  @logger.info "Attempted to pause #{topic}/#{partition}, but we're not subscribed to it"
99
118
  return
100
119
  end
101
120
 
102
121
  consumer.pause(filtered_tpl)
103
- fake_msg = OpenStruct.new(topic: topic, partition: partition, offset: offset)
104
- consumer.seek(fake_msg)
105
-
106
- @paused_tpls[topic][partition] = [consumer, filtered_tpl]
122
+ if offset
123
+ fake_msg = OpenStruct.new(topic: topic, partition: partition, offset: offset)
124
+ consumer.seek(fake_msg)
125
+ end
107
126
  end
108
127
 
109
128
  def resume(topic, partition)
110
129
  consumer, filtered_tpl = find_consumer_by(topic, partition)
111
130
 
112
- if !consumer && @paused_tpls[topic][partition]
113
- consumer, filtered_tpl = @paused_tpls[topic][partition]
114
- end
115
-
116
- if !consumer
131
+ unless consumer
117
132
  @logger.info "Attempted to resume #{topic}/#{partition}, but we're not subscribed to it"
118
133
  return
119
134
  end
120
135
 
121
136
  consumer.resume(filtered_tpl)
122
- @paused_tpls[topic].delete(partition)
123
- @paused_tpls.delete(topic) if @paused_tpls[topic].empty?
124
137
  end
125
138
 
126
- alias :each :each_subscribed
139
+ alias :each :each_subscribed
127
140
 
128
141
  # Subscribe to all topics eagerly, even if there's still messages elsewhere. Usually
129
142
  # that's not needed and Kafka might rebalance if topics are not polled frequently
@@ -269,5 +282,18 @@ module Racecar
269
282
  r = limit_ms - ((Time.now - started_at_time)*1000).round
270
283
  r <= 0 ? 0 : r
271
284
  end
285
+
286
+ def producer_config
287
+ cfg = {
288
+ "bootstrap.servers" => @config.brokers.join(","),
289
+ "client.id" => @config.client_id,
290
+ "statistics.interval.ms" => @config.statistics_interval_ms,
291
+ "message.timeout.ms" => @config.message_timeout * 1000,
292
+ "partitioner" => @config.partitioner.to_s,
293
+ }
294
+ cfg["compression.codec"] = @config.producer_compression_codec.to_s unless @config.producer_compression_codec.nil?
295
+ cfg.merge!(@config.rdkafka_producer)
296
+ cfg
297
+ end
272
298
  end
273
299
  end