racecar 2.8.2 → 2.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cbf6ca4837bdd3893c8342936f9d5ea115a5b3a585e0c843e62171e00038e72
4
- data.tar.gz: d8603c46cf801d0f41f017d5b27b35e0d2b195eec5be11a1f7b6bf77413726b4
3
+ metadata.gz: b394d31edcb9c83e562811ba748b6c6915388d0227a4e7425aa9aa13f64e3890
4
+ data.tar.gz: 1dfcd7046b6d932f716246d0685589c1635af115a4e126067c9775b13fe05464
5
5
  SHA512:
6
- metadata.gz: 78bdedfe7bd888a3716f5a29f28810f4c4d2f03e43c33b8e47ed08607155211fca1d47f4eab6a4f0def6976bb7ca3263117e52d2e0fa7f0a721c56ec70c7ee3d
7
- data.tar.gz: 2bf6e2b50980add68363b59718f54403caada58abcb404a9018ac4304519bbbb7ed920578d37bcadefad63964812e7374d367298f403577725eef79af269c604
6
+ metadata.gz: 59043be21e411e680c11815b583236556413a539f5aa9508a460eefe82cee6199f56a30b9fa07a3f206886d5dc811b210cea6130813a7073a68fe6612343ca0d
7
+ data.tar.gz: d6692b9bb7cdc27efe5272a10fa1d9061920084a7b517699e5133f5e31e9fd50415e72ee40550e07e5a2fed028aeabb741422fef47372c7a53e0a96bdfc1b090
@@ -12,30 +12,34 @@ jobs:
12
12
 
13
13
  strategy:
14
14
  matrix:
15
- ruby-version: ["2.6", "3.0"]
15
+ ruby-version:
16
+ - "2.7"
17
+ - "3.0"
18
+ - "3.1"
19
+ - "3.2"
16
20
 
17
21
  steps:
18
- - uses: zendesk/checkout@v2
22
+ - uses: zendesk/checkout@v3
19
23
  - name: Set up Ruby
20
- uses: zendesk/setup-ruby@v1.64.1
24
+ uses: zendesk/setup-ruby@v1
21
25
  with:
22
26
  ruby-version: ${{ matrix.ruby-version }}
23
27
  bundler-cache: true
24
28
  - name: Build and test with RSpec
25
- run: bundle exec rspec --format documentation --require spec_helper --color --exclude-pattern='spec/integration/*_spec.rb'
29
+ run: bundle exec rspec --exclude-pattern='spec/integration/*_spec.rb'
26
30
 
27
31
  integration-specs:
28
32
  runs-on: ubuntu-latest
29
33
  steps:
30
- - uses: zendesk/checkout@v2
34
+ - uses: zendesk/checkout@v3
31
35
  - name: Set up Ruby
32
- uses: zendesk/setup-ruby@v1.64.1
36
+ uses: zendesk/setup-ruby@v1
33
37
  with:
34
- ruby-version: 2.7
38
+ ruby-version: "2.7"
35
39
  bundler-cache: true
36
40
  - name: Bring up docker-compose stack
37
41
  run: docker-compose up -d
38
42
  - name: Build and test with RSpec
39
43
  env:
40
44
  RACECAR_BROKERS: localhost:9092
41
- run: timeout --kill-after 180 150 bundle exec rspec --format documentation --require spec_helper --color spec/integration/*_spec.rb
45
+ run: timeout --kill-after 180 150 bundle exec rspec spec/integration/*_spec.rb
@@ -0,0 +1,12 @@
1
+ name: Publish Gem
2
+
3
+ on:
4
+ push:
5
+ tags: v*
6
+
7
+ 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 }}
data/CHANGELOG.md CHANGED
@@ -2,14 +2,23 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ * Test with Ruby 3.2
6
+
7
+ ## v2.9.0, v2.9.0.beta1
8
+
9
+ * Add `partitioner` producer config option to allow changing the strategy to
10
+ determine which topic partition a message is written to when racecar
11
+ produces a kafka message
12
+ * Add built-in liveness probe for Kubernetes deployments.
13
+
5
14
  ## v2.8.2
6
- * Handles ErroneousStateError, in previous versions the consumer would do several unecessary group leave/joins. The log level is also changed to WARN instead of ERROR. ([#285](https://github.com/zendesk/racecar/pull/295))
15
+ * Handles ErroneousStateError, in previous versions the consumer would do several unecessary group leave/joins. The log level is also changed to WARN instead of ERROR. ([#295](https://github.com/zendesk/racecar/pull/295))
7
16
 
8
17
  ## v2.8.1
9
18
  * Adds new ErroneousStateError to racecar in order to give more information on this new possible exception.
10
19
 
11
20
  ## v2.8.0
12
- * Update librdkafka version from 1.8.2 to 1.9.0 by upgrading from rdkafka 0.10.0 to 0.12.0. ([#283](https://github.com/zendesk/racecar/pull/293))
21
+ * Update librdkafka version from 1.8.2 to 1.9.0 by upgrading from rdkafka 0.10.0 to 0.12.0. ([#293](https://github.com/zendesk/racecar/pull/293))
13
22
 
14
23
  ## v2.7.0
15
24
 
data/Gemfile CHANGED
@@ -5,6 +5,6 @@ source 'https://rubygems.org'
5
5
  # Specify your gem's dependencies in racecar.gemspec
6
6
  gemspec
7
7
 
8
- # We actually support version 6.0 (see gemspec); this extra restriction is added just for running the test suite also
9
- # on Ruby 2.4, which activesupport 6.0 no longer supports
10
- gem 'activesupport', '< 6.0'
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'
data/Gemfile.lock CHANGED
@@ -1,67 +1,72 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- racecar (2.8.2.beta)
4
+ racecar (2.9.0.beta1)
5
5
  king_konf (~> 1.0.0)
6
6
  rdkafka (~> 0.12.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activesupport (5.2.6)
11
+ activesupport (6.1.7.3)
12
12
  concurrent-ruby (~> 1.0, >= 1.0.2)
13
- i18n (>= 0.7, < 2)
14
- minitest (~> 5.1)
15
- tzinfo (~> 1.1)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ zeitwerk (~> 2.3)
17
+ byebug (11.1.3)
16
18
  coderay (1.1.3)
17
- concurrent-ruby (1.1.9)
18
- diff-lcs (1.4.4)
19
- dogstatsd-ruby (5.2.0)
19
+ concurrent-ruby (1.2.2)
20
+ diff-lcs (1.5.0)
21
+ dogstatsd-ruby (5.5.0)
20
22
  ffi (1.15.5)
21
- i18n (1.8.10)
23
+ i18n (1.12.0)
22
24
  concurrent-ruby (~> 1.0)
23
- king_konf (1.0.0)
25
+ king_konf (1.0.1)
24
26
  method_source (1.0.0)
25
- mini_portile2 (2.8.0)
26
- minitest (5.14.4)
27
- pry (0.13.1)
27
+ mini_portile2 (2.8.1)
28
+ minitest (5.18.0)
29
+ pry (0.14.2)
28
30
  coderay (~> 1.1)
29
31
  method_source (~> 1.0)
30
- rake (13.0.1)
32
+ pry-byebug (3.10.1)
33
+ byebug (~> 11.0)
34
+ pry (>= 0.13, < 0.15)
35
+ rake (13.0.6)
31
36
  rdkafka (0.12.0)
32
37
  ffi (~> 1.15)
33
38
  mini_portile2 (~> 2.6)
34
39
  rake (> 12)
35
- rspec (3.10.0)
36
- rspec-core (~> 3.10.0)
37
- rspec-expectations (~> 3.10.0)
38
- rspec-mocks (~> 3.10.0)
39
- rspec-core (3.10.1)
40
- rspec-support (~> 3.10.0)
41
- rspec-expectations (3.10.1)
40
+ rspec (3.12.0)
41
+ rspec-core (~> 3.12.0)
42
+ rspec-expectations (~> 3.12.0)
43
+ rspec-mocks (~> 3.12.0)
44
+ rspec-core (3.12.1)
45
+ rspec-support (~> 3.12.0)
46
+ rspec-expectations (3.12.2)
42
47
  diff-lcs (>= 1.2.0, < 2.0)
43
- rspec-support (~> 3.10.0)
44
- rspec-mocks (3.10.2)
48
+ rspec-support (~> 3.12.0)
49
+ rspec-mocks (3.12.4)
45
50
  diff-lcs (>= 1.2.0, < 2.0)
46
- rspec-support (~> 3.10.0)
47
- rspec-support (3.10.2)
48
- thread_safe (0.3.6)
49
- timecop (0.9.2)
50
- tzinfo (1.2.9)
51
- thread_safe (~> 0.1)
51
+ rspec-support (~> 3.12.0)
52
+ rspec-support (3.12.0)
53
+ timecop (0.9.6)
54
+ tzinfo (2.0.6)
55
+ concurrent-ruby (~> 1.0)
56
+ zeitwerk (2.6.7)
52
57
 
53
58
  PLATFORMS
54
59
  ruby
55
60
 
56
61
  DEPENDENCIES
57
- activesupport (< 6.0)
62
+ activesupport (~> 6.1.0)
58
63
  bundler (>= 1.13, < 3)
59
64
  dogstatsd-ruby (>= 4.0.0, < 6.0.0)
60
- pry
65
+ pry-byebug
61
66
  racecar!
62
67
  rake (> 10.0)
63
68
  rspec (~> 3.0)
64
69
  timecop
65
70
 
66
71
  BUNDLED WITH
67
- 2.3.7
72
+ 2.4.9
data/README.md CHANGED
@@ -20,6 +20,7 @@ The framework is based on [rdkafka-ruby](https://github.com/appsignal/rdkafka-ru
20
20
  8. [Logging](#logging)
21
21
  9. [Operations](#operations)
22
22
  10. [Upgrading from v1 to v2](#upgrading-from-v1-to-v2)
23
+ 11. [Compression](#compression)
23
24
  3. [Development](#development)
24
25
  4. [Contributing](#contributing)
25
26
  5. [Support and Discussion](#support-and-discussion)
@@ -245,6 +246,71 @@ The `deliver!` method can be used to block until the broker received all queued
245
246
 
246
247
  You can set message headers by passing a `headers:` option with a Hash of headers.
247
248
 
249
+ ### Standalone Producer
250
+
251
+ Racecar provides a standalone producer to publish messages to Kafka directly from your Rails application:
252
+
253
+ ```ruby
254
+ # app/controllers/comments_controller.rb
255
+ class CommentsController < ApplicationController
256
+ def create
257
+ @comment = Comment.create!(params)
258
+
259
+ # This will publish a JSON representation of the comment to the `comments` topic
260
+ # in Kafka. Make sure to create the topic first, or this may fail.
261
+ Racecar.produce_sync(value:comment.to_json, topic: "comments")
262
+ end
263
+ end
264
+ ```
265
+
266
+ The above example will block the server process until the message has been delivered. If you want deliveries to happen in the background in order to free up your server processes more quickly, call #deliver_async instead:
267
+
268
+ ```ruby
269
+ # app/controllers/comments_controller.rb
270
+ class CommentsController < ApplicationController
271
+ def show
272
+ @comment = Comment.find(params[:id])
273
+
274
+ event = {
275
+ name: "comment_viewed",
276
+ data: {
277
+ comment_id: @comment.id,
278
+ user_id: current_user.id
279
+ }
280
+ }
281
+
282
+ # By delivering messages asynchronously you free up your server processes faster.
283
+ Racecar.produce_async(value: event.to_json, topic: "activity")
284
+ end
285
+ end
286
+ ```
287
+ In addition to improving response time, delivering messages asynchronously also protects your application against Kafka availability issues -- if messages cannot be delivered, they'll be buffered for later and retried automatically.
288
+
289
+ A third method is to produce messages first (without delivering the messages to Kafka yet), and deliver them synchronously later:
290
+
291
+ ```ruby
292
+ # app/controllers/comments_controller.rb
293
+ class CommentsController < ApplicationController
294
+ def create
295
+ @comment = Comment.create!(params)
296
+
297
+ event = {
298
+ name: "comment_created",
299
+ data: {
300
+ comment_id: @comment.id
301
+ user_id: current_user.id
302
+ }
303
+ }
304
+
305
+ # This will queue the two messages in the internal buffer and block server process until they are delivered.
306
+ Racecar.wait_for_delivery do
307
+ Racecar.produce_async(comment.to_json, topic: "comments")
308
+ Racecar.produce_async(event.to_json, topic: "activity")
309
+ end
310
+ end
311
+ end
312
+ ```
313
+
248
314
  ### Configuration
249
315
 
250
316
  Racecar provides a flexible way to configure your consumer in a way that feels at home in a Rails application. If you haven't already, run `bundle exec rails generate racecar:install` in order to generate a config file. You'll get a separate section for each Rails environment, with the common configuration values in a shared `common` section.
@@ -338,6 +404,7 @@ Racecar has support for using SASL to authenticate clients using either the GSSA
338
404
 
339
405
  These settings are related to consumers that _produce messages to Kafka_.
340
406
 
407
+ - `partitioner` – The strategy used to determine which topic partition a message is written to when Racecar produces a value to Kafka. The codec needs to be one of `consistent`, `consistent_random` `murmur2` `murmur2_random` `fnv1a` `fnv1a_random` either as a Symbol or a String, defaults to `consistent_random`
341
408
  - `producer_compression_codec` – If defined, Racecar will compress messages before writing them to Kafka. The codec needs to be one of `gzip`, `lz4`, or `snappy`, either as a Symbol or a String.
342
409
 
343
410
  #### Datadog monitoring
@@ -350,7 +417,7 @@ Racecar supports [Datadog](https://www.datadoghq.com/) monitoring integration. I
350
417
  - `datadog_namespace` – The namespace to use for Datadog metrics.
351
418
  - `datadog_tags` – Tags that should always be set on Datadog metrics.
352
419
 
353
- Furthermore, there's a [standard Datadog dashboard configution 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.
420
+ 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.
354
421
 
355
422
  #### Consumers Without Rails
356
423
 
@@ -443,6 +510,64 @@ The important part is the `strategy.type` value, which tells Kubernetes how to u
443
510
 
444
511
  Instead, the `Recreate` update strategy should be used. It completely tears down the existing containers before starting all of the new containers simultaneously, allowing for a single synchronization stage and a much faster, more stable deployment update.
445
512
 
513
+ #### Liveness Probe
514
+
515
+ Racecar comes with a built-in liveness probe, primarily for use with Kubernetes, but useful for any deployment environment where you can periodically run a process to check the health of your consumer.
516
+
517
+ To use this feature:
518
+ - set the `liveness_probe_enabled` config option to true.
519
+ - configure your Kubernetes deployment to run `$ racecarctl liveness_probe`
520
+
521
+
522
+ When enabled (see config) Racecar will touch the file at `liveness_probe_file_path` each time it finishes polling Kafka and processing the messages in the batch (if any).
523
+
524
+ The modified time of this file can be observed to determine when the consumer last exhibited 'liveness'.
525
+
526
+ Running `racecarctl liveness_probe` will return a successful exit status if the last 'liveness' event happened within an acceptable time, `liveness_probe_max_interval`.
527
+
528
+ `liveness_probe_max_interval` should be long enough to account for both the Kafka polling time of `max_wait_time` and the processing time of a full message batch.
529
+
530
+ On receiving `SIGTERM`, Racecar will gracefully shut down and delete this file, causing the probe to fail immediately after exit.
531
+
532
+ You may wish to tolerate more than one failed probe run to accommodate for environmental variance and clock changes.
533
+
534
+ See the [Configuration section](https://github.com/zendesk/racecar#configuration) for the various ways the liveness probe can be configured, environment variables being one option.
535
+
536
+ Here is an example Kubernetes liveness probe configuration:
537
+
538
+ ```yaml
539
+ apiVersion: apps/v1
540
+ kind: Deployment
541
+ spec:
542
+ template:
543
+ spec:
544
+ containers:
545
+ - name: consumer
546
+
547
+ args:
548
+ - racecar
549
+ - SomeConsumer
550
+
551
+ env:
552
+ - name: RACECAR_LIVENESS_PROBE_ENABLED
553
+ value: "true"
554
+
555
+ livenessProbe:
556
+ exec:
557
+ command:
558
+ - racecarctl
559
+ - liveness_probe
560
+
561
+ # Allow up to 10 consecutive failures before terminating Pod:
562
+ failureThreshold: 10
563
+
564
+ # Wait 30 seconds before starting the probes:
565
+ initialDelaySeconds: 30
566
+
567
+ # Perform the check every 10 seconds:
568
+ periodSeconds: 10
569
+ ```
570
+
446
571
  #### Deploying to Heroku
447
572
 
448
573
  If you run your applications in Heroku and/or use the Heroku Kafka add-on, you application will be provided with 4 ENV variables that allow connecting to the cluster: `KAFKA_URL`, `KAFKA_TRUSTED_CERT`, `KAFKA_CLIENT_CERT`, and `KAFKA_CLIENT_CERT_KEY`.
@@ -479,7 +604,7 @@ Again, the recommended approach is to manage the processes using process manager
479
604
 
480
605
  ### Handling errors
481
606
 
482
- When processing messages from a Kafka topic, your code may encounter an error and raise an exception. The cause is typically one of two things:
607
+ #### When processing messages from a Kafka topic, your code may encounter an error and raise an exception. The cause is typically one of two things:
483
608
 
484
609
  1. The message being processed is somehow malformed or doesn't conform with the assumptions made by the processing code.
485
610
  2. You're using some external resource such as a database or a network API that is temporarily unavailable.
@@ -514,6 +639,16 @@ end
514
639
 
515
640
  It is highly recommended that you set up an error handler. Please note that the `info` object contains different keys and values depending on whether you are using `process` or `process_batch`. See the `instrumentation_payload` object in the `process` and `process_batch` methods in the `Runner` class for the complete list.
516
641
 
642
+ #### Errors related to Compression
643
+
644
+ A sample error might look like this:
645
+
646
+ ```
647
+ E, [2022-10-09T11:28:29.976548 #15] ERROR -- : (try 5/10): Error for topic subscription #<struct Racecar::Consumer::Subscription topic="support.entity_incremental.views.view_ticket_ids", start_from_beginning=false, max_bytes_per_partition=104857, additional_config={}>: Local: Not implemented (not_implemented)
648
+ ```
649
+
650
+ Please see [Compression](#compression)
651
+
517
652
  ### Logging
518
653
 
519
654
  By default, Racecar will log to `STDOUT`. If you're using Rails, your application code will use whatever logger you've configured there.
@@ -528,7 +663,15 @@ In order to introspect the configuration of a consumer process, send it the `SIG
528
663
 
529
664
  ### Upgrading from v1 to v2
530
665
 
531
- In order to safely upgrade from Racecar v1 to v2, you need to completely shut down your consumer group before starting it up again with the v2 Racecar dependency. In general, you should avoid rolling deploys for consumers groups, so it is likely the case that this will just work for you, but it's a good idea to check first.
666
+ In order to safely upgrade from Racecar v1 to v2, you need to completely shut down your consumer group before starting it up again with the v2 Racecar dependency. In general, you should avoid rolling deploys for consumers groups, so it is likely the case that this will just work for you, but it's a good idea to check first.
667
+
668
+ ### Compression
669
+
670
+ Racecar v2 requires a C library (zlib) to compress the messages before producing to the topic. If not already installed on you consumer docker container, please install using following command in Dockerfile of consumer
671
+
672
+ ```
673
+ apt-get update && apt-get install -y libzstd-dev
674
+ ```
532
675
 
533
676
  ## Development
534
677
 
data/Rakefile CHANGED
@@ -3,6 +3,9 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
5
 
6
+ # Pushing to rubygems is handled by a github workflow
7
+ ENV["gem_push"] = "false"
8
+
6
9
  RSpec::Core::RakeTask.new(:spec)
7
10
 
8
11
  task :default => :spec
@@ -3,8 +3,8 @@
3
3
  class BatchConsumer < Racecar::Consumer
4
4
  subscribes_to "messages", start_from_beginning: false
5
5
 
6
- def process_batch(batch)
7
- batch.messages.each do |message|
6
+ def process_batch(messages)
7
+ messages.each do |message|
8
8
  puts message.value
9
9
  end
10
10
  end
data/lib/racecar/cli.rb CHANGED
@@ -5,6 +5,7 @@ require "logger"
5
5
  require "fileutils"
6
6
  require "racecar/rails_config_file_loader"
7
7
  require "racecar/daemon"
8
+ require "racecar/liveness_probe"
8
9
 
9
10
  module Racecar
10
11
  class Cli
@@ -18,6 +19,7 @@ module Racecar
18
19
  @parser = build_parser
19
20
  @parser.parse!(args)
20
21
  @consumer_name = args.first or raise Racecar::Error, "no consumer specified"
22
+ @runner = nil
21
23
  end
22
24
 
23
25
  def run
@@ -58,11 +60,21 @@ module Racecar
58
60
  $stderr.puts "=> Ctrl-C to shutdown consumer"
59
61
  end
60
62
 
63
+ if config.liveness_probe_enabled
64
+ $stderr.puts "=> Liveness probe enabled"
65
+ config.install_liveness_probe
66
+ end
67
+
61
68
  processor = consumer_class.new
62
- Racecar.run(processor)
69
+ @runner = Racecar.runner(processor)
70
+ @runner.run
63
71
  nil
64
72
  end
65
73
 
74
+ def stop
75
+ @runner.stop
76
+ end
77
+
66
78
  private
67
79
 
68
80
  attr_reader :consumer_name
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tmpdir"
4
+
3
5
  require "king_konf"
4
6
 
7
+ require "racecar/liveness_probe"
8
+ require "racecar/instrumenter"
9
+ require "racecar/rebalance_listener"
10
+
5
11
  module Racecar
6
12
  class Config < KingKonf::Config
7
13
  env_prefix :racecar
@@ -77,6 +83,9 @@ module Racecar
77
83
  desc "The log level for the Racecar logs"
78
84
  string :log_level, default: "info"
79
85
 
86
+ desc "The strategy used to determine which topic partition a message is written to when Racecar produces a value to Kafka; defaults to `consistent_random`"
87
+ symbol :partitioner, allowed_values: %i{consistent consistent_random murmur2 murmur2_random fnv1a fnv1a_random}, default: :consistent_random
88
+
80
89
  desc "Protocol used to communicate with brokers"
81
90
  symbol :security_protocol, allowed_values: %i{plaintext ssl sasl_plaintext sasl_ssl}
82
91
 
@@ -164,6 +173,15 @@ module Racecar
164
173
  for backward compatibility, however this can be quite memory intensive"
165
174
  integer :statistics_interval, default: 1
166
175
 
176
+ desc "Whether to enable liveness probe behavior (touch the file)"
177
+ boolean :liveness_probe_enabled, default: false
178
+
179
+ desc "Path to a file Racecar will touch to show liveness"
180
+ string :liveness_probe_file_path, default: "#{Dir.tmpdir}/racecar-liveness"
181
+
182
+ desc "Used only by the liveness probe: Max time (in seconds) between liveness events before the process is considered not healthy"
183
+ integer :liveness_probe_max_interval, default: 5
184
+
167
185
  # The error handler must be set directly on the object.
168
186
  attr_reader :error_handler
169
187
 
@@ -210,6 +228,7 @@ module Racecar
210
228
  end
211
229
 
212
230
  def load_consumer_class(consumer_class)
231
+ self.consumer_class = consumer_class
213
232
  self.group_id = consumer_class.group_id || self.group_id
214
233
 
215
234
  self.group_id ||= [
@@ -226,6 +245,7 @@ module Racecar
226
245
  self.fetch_messages = consumer_class.fetch_messages || self.fetch_messages
227
246
  self.pidfile ||= "#{group_id}.pid"
228
247
  end
248
+ attr_accessor :consumer_class
229
249
 
230
250
  def on_error(&handler)
231
251
  @error_handler = handler
@@ -247,6 +267,39 @@ module Racecar
247
267
  producer_config
248
268
  end
249
269
 
270
+ def instrumenter
271
+ @instrumenter ||= begin
272
+ default_payload = { client_id: client_id, group_id: group_id }
273
+
274
+ if defined?(ActiveSupport::Notifications)
275
+ # ActiveSupport needs `concurrent-ruby` but doesn't `require` it.
276
+ require 'concurrent/utility/monotonic_time'
277
+ Instrumenter.new(backend: ActiveSupport::Notifications, default_payload: default_payload)
278
+ else
279
+ logger.warn "ActiveSupport::Notifications not available, instrumentation is disabled"
280
+ NullInstrumenter
281
+ end
282
+ end
283
+ end
284
+ attr_writer :instrumenter
285
+
286
+ def install_liveness_probe
287
+ liveness_probe.tap(&:install)
288
+ end
289
+
290
+ def liveness_probe
291
+ require "active_support/notifications"
292
+ @liveness_probe ||= LivenessProbe.new(
293
+ ActiveSupport::Notifications,
294
+ liveness_probe_file_path,
295
+ liveness_probe_max_interval
296
+ )
297
+ end
298
+
299
+ def rebalance_listener
300
+ RebalanceListener.new(self)
301
+ end
302
+
250
303
  private
251
304
 
252
305
  def rdkafka_security_config
@@ -69,7 +69,10 @@ module Racecar
69
69
 
70
70
  def current
71
71
  @consumers[@consumer_id_iterator.peek] ||= begin
72
- consumer = Rdkafka::Config.new(rdkafka_config(current_subscription)).consumer
72
+ consumer_config = Rdkafka::Config.new(rdkafka_config(current_subscription))
73
+ consumer_config.consumer_rebalance_listener = @config.rebalance_listener
74
+
75
+ consumer = consumer_config.consumer
73
76
  @instrumenter.instrument('join_group') do
74
77
  consumer.subscribe current_subscription.topic
75
78
  end
@@ -140,7 +143,7 @@ module Racecar
140
143
  @logger.debug "No time remains for polling messages. Will try on next call."
141
144
  return nil
142
145
  elsif wait_ms >= remain_ms
143
- @logger.error "Only #{remain_ms}ms left, but want to wait for #{wait_ms}ms before poll. Will retry on next call."
146
+ @logger.warn "Only #{remain_ms}ms left, but want to wait for #{wait_ms}ms before poll. Will retry on next call."
144
147
  @previous_retries = try
145
148
  return nil
146
149
  elsif wait_ms > 0
data/lib/racecar/ctl.rb CHANGED
@@ -32,6 +32,17 @@ module Racecar
32
32
  @command = command
33
33
  end
34
34
 
35
+ def liveness_probe(args)
36
+ require "racecar/liveness_probe"
37
+ parse_options!(args)
38
+
39
+ if ENV["RAILS_ENV"]
40
+ Racecar.config.load_file("config/racecar.yml", ENV["RAILS_ENV"])
41
+ end
42
+
43
+ Racecar.config.liveness_probe.check_liveness_within_interval!
44
+ end
45
+
35
46
  def status(args)
36
47
  parse_options!(args)
37
48
 
@@ -54,7 +54,7 @@ module Racecar
54
54
  end
55
55
 
56
56
  def pid
57
- if File.exists?(pidfile)
57
+ if File.exist?(pidfile)
58
58
  File.read(pidfile).to_i
59
59
  else
60
60
  nil
@@ -89,7 +89,7 @@ module Racecar
89
89
  end
90
90
 
91
91
  at_exit do
92
- File.delete(pidfile) if File.exists?(pidfile)
92
+ File.delete(pidfile) if File.exist?(pidfile)
93
93
  end
94
94
  rescue Errno::EEXIST
95
95
  check_pid
@@ -211,6 +211,10 @@ module Racecar
211
211
  topic: topic,
212
212
  }
213
213
 
214
+ if event.payload.key?(:exception)
215
+ increment("producer.produce.errors", tags: tags)
216
+ end
217
+
214
218
  # This gets us the write rate.
215
219
  increment("producer.produce.messages", tags: tags.merge(topic: topic))
216
220
 
@@ -245,6 +249,67 @@ module Racecar
245
249
  increment("producer.ack.messages", tags: tags)
246
250
  end
247
251
 
252
+ def produce_delivery_error(event)
253
+ tags = {
254
+ client: event.payload.fetch(:client_id),
255
+ }
256
+
257
+ increment("producer.produce.delivery.errors", tags: tags)
258
+ end
259
+
260
+ def produce_async(event)
261
+ client = event.payload.fetch(:client_id)
262
+ topic = event.payload.fetch(:topic)
263
+ message_size = event.payload.fetch(:message_size)
264
+ buffer_size = event.payload.fetch(:buffer_size)
265
+
266
+ tags = {
267
+ client: client,
268
+ topic: topic,
269
+ }
270
+
271
+ if event.payload.key?(:exception)
272
+ increment("producer.produce.errors", tags: tags)
273
+ end
274
+
275
+ # This gets us the write rate.
276
+ increment("producer.produce.messages", tags: tags.merge(topic: topic))
277
+
278
+ # Information about typical/average/95p message size.
279
+ histogram("producer.produce.message_size", message_size, tags: tags.merge(topic: topic))
280
+
281
+ # Aggregate message size.
282
+ count("producer.produce.message_size.sum", message_size, tags: tags.merge(topic: topic))
283
+
284
+ # This gets us the avg/max buffer size per producer.
285
+ histogram("producer.buffer.size", buffer_size, tags: tags)
286
+ end
287
+
288
+ def produce_sync(event)
289
+ client = event.payload.fetch(:client_id)
290
+ topic = event.payload.fetch(:topic)
291
+ message_size = event.payload.fetch(:message_size)
292
+
293
+ tags = {
294
+ client: client,
295
+ topic: topic,
296
+ }
297
+
298
+ if event.payload.key?(:exception)
299
+ increment("producer.produce.errors", tags: tags)
300
+ end
301
+
302
+
303
+ # This gets us the write rate.
304
+ increment("producer.produce.messages", tags: tags.merge(topic: topic))
305
+
306
+ # Information about typical/average/95p message size.
307
+ histogram("producer.produce.message_size", message_size, tags: tags.merge(topic: topic))
308
+
309
+ # Aggregate message size.
310
+ count("producer.produce.message_size.sum", message_size, tags: tags.merge(topic: topic))
311
+ end
312
+
248
313
  attach_to "racecar"
249
314
  end
250
315
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Racecar
4
+ class DeliveryCallback
5
+ attr_reader :instrumenter
6
+
7
+ def initialize(instrumenter:)
8
+ @instrumenter = instrumenter
9
+ end
10
+
11
+ def call(delivery_report)
12
+ if delivery_report.error.to_i.zero?
13
+ payload = {
14
+ offset: delivery_report.offset,
15
+ partition: delivery_report.partition
16
+ }
17
+ instrumenter.instrument("acknowledged_message", payload)
18
+ else
19
+ payload = {
20
+ partition: delivery_report.partition,
21
+ exception: delivery_report.error
22
+ }
23
+ instrumenter.instrument("produce_delivery_error", payload)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -9,16 +9,9 @@ module Racecar
9
9
  NAMESPACE = "racecar"
10
10
  attr_reader :backend
11
11
 
12
- def initialize(default_payload = {})
12
+ def initialize(backend:, default_payload: {})
13
+ @backend = backend
13
14
  @default_payload = default_payload
14
-
15
- @backend = if defined?(ActiveSupport::Notifications)
16
- # ActiveSupport needs `concurrent-ruby` but doesn't `require` it.
17
- require 'concurrent/utility/monotonic_time'
18
- ActiveSupport::Notifications
19
- else
20
- NullInstrumenter
21
- end
22
15
  end
23
16
 
24
17
  def instrument(event_name, payload = {}, &block)
@@ -0,0 +1,78 @@
1
+ require "fileutils"
2
+
3
+ module Racecar
4
+ class LivenessProbe
5
+ def initialize(message_bus, file_path, max_interval)
6
+ @message_bus = message_bus
7
+ @file_path = file_path
8
+ @max_interval = max_interval
9
+ @subscribers = []
10
+ end
11
+
12
+ attr_reader :message_bus, :file_path, :max_interval, :subscribers
13
+ private :message_bus, :file_path, :max_interval, :subscribers
14
+
15
+ def check_liveness_within_interval!
16
+ unless liveness_event_within_interval?
17
+ $stderr.puts "Racecar healthcheck failed: No liveness within interval #{max_interval}s. Last liveness at #{last_liveness_event_at}, #{elapsed_since_liveness_event} seconds ago."
18
+ Process.exit(1)
19
+ end
20
+ end
21
+
22
+ def liveness_event_within_interval?
23
+ elapsed_since_liveness_event < max_interval
24
+ rescue Errno::ENOENT
25
+ $stderr.puts "Racecar healthcheck failed: Liveness file not found `#{file_path}`"
26
+ Process.exit(1)
27
+ end
28
+
29
+ def install
30
+ unless file_path && file_writeable?
31
+ raise(
32
+ "Liveness probe configuration error: `liveness_probe_file_path` must be set to a writable file path.\n" \
33
+ " Set `RACECAR_LIVENESS_PROBE_FILE_PATH` and `RACECAR_LIVENESS_MAX_INTERVAL` environment variables."
34
+ )
35
+ end
36
+
37
+ subscribers << message_bus.subscribe("start_main_loop.racecar") do
38
+ touch_liveness_file
39
+ end
40
+
41
+ subscribers = message_bus.subscribe("shut_down.racecar") do
42
+ delete_liveness_file
43
+ end
44
+
45
+ nil
46
+ end
47
+
48
+ def uninstall
49
+ subscribers.each { |s| message_bus.unsubscribe(s) }
50
+ end
51
+
52
+ private
53
+
54
+ def elapsed_since_liveness_event
55
+ Time.now - last_liveness_event_at
56
+ end
57
+
58
+ def last_liveness_event_at
59
+ File.mtime(file_path)
60
+ end
61
+
62
+ def touch_liveness_file
63
+ FileUtils.touch(file_path)
64
+ end
65
+
66
+ def delete_liveness_file
67
+ FileUtils.rm_rf(file_path)
68
+ end
69
+
70
+ def file_writeable?
71
+ File.write(file_path, "")
72
+ File.unlink(file_path)
73
+ true
74
+ rescue
75
+ false
76
+ end
77
+ end
78
+ end
@@ -37,6 +37,10 @@ module Racecar
37
37
  wait_for_exit
38
38
  end
39
39
 
40
+ def stop
41
+ terminate_workers
42
+ end
43
+
40
44
  private
41
45
 
42
46
  attr_accessor :workers
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "racecar/message_delivery_error"
4
+ require "racecar/delivery_callback"
5
+
6
+ at_exit do
7
+ Racecar::Producer.shutdown!
8
+ end
9
+
10
+ module Racecar
11
+ class Producer
12
+
13
+ @@mutex = Mutex.new
14
+
15
+ class << self
16
+ def shutdown!
17
+ @@mutex.synchronize do
18
+ if !@internal_producer.nil?
19
+ @internal_producer.close
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize(config: nil, logger: nil, instrumenter: NullInstrumenter)
26
+ @config = config
27
+ @logger = logger
28
+ @delivery_handles = []
29
+ @instrumenter = instrumenter
30
+ @batching = false
31
+ @internal_producer = init_internal_producer(config)
32
+ end
33
+
34
+ def init_internal_producer(config)
35
+ @@mutex.synchronize do
36
+ @@init_internal_producer ||= begin
37
+ # https://github.com/edenhill/librdkafka/blob/master/CONFIGURATION.md
38
+ producer_config = {
39
+ "bootstrap.servers" => config.brokers.join(","),
40
+ "client.id" => config.client_id,
41
+ "statistics.interval.ms" => config.statistics_interval_ms,
42
+ "message.timeout.ms" => config.message_timeout * 1000,
43
+ }
44
+ producer_config["compression.codec"] = config.producer_compression_codec.to_s unless config.producer_compression_codec.nil?
45
+ producer_config.merge!(config.rdkafka_producer)
46
+ Rdkafka::Config.new(producer_config).producer.tap do |producer|
47
+ producer.delivery_callback = DeliveryCallback.new(instrumenter: @instrumenter)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ # fire and forget - you won't get any guarantees or feedback from
54
+ # Racecar on the status of the message and it won't halt execution
55
+ # of the rest of your code.
56
+ def produce_async(value:, topic:, **options)
57
+ with_instrumentation(action: "produce_async", value: value, topic: topic, **options) do
58
+ begin
59
+ handle = internal_producer.produce(payload: value, topic: topic, **options)
60
+ @delivery_handles << handle if @batching
61
+ rescue Rdkafka::RdkafkaError => e
62
+ raise MessageDeliveryError.new(e, handle)
63
+ end
64
+ end
65
+
66
+ nil
67
+ end
68
+
69
+ # synchronous message production - will wait until the delivery handle succeeds, fails or times out.
70
+ def produce_sync(value:, topic:, **options)
71
+ with_instrumentation(action: "produce_sync", value: value, topic: topic, **options) do
72
+ begin
73
+ handle = internal_producer.produce(payload: value, topic: topic, **options)
74
+ deliver_with_error_handling(handle)
75
+ rescue Rdkafka::RdkafkaError => e
76
+ raise MessageDeliveryError.new(e, handle)
77
+ end
78
+ end
79
+
80
+ nil
81
+ end
82
+
83
+ # Blocks until all messages that have been asynchronously produced in the block have been delivered.
84
+ # Usage:
85
+ # messages = [
86
+ # {value: "message1", topic: "topic1"},
87
+ # {value: "message2", topic: "topic1"},
88
+ # {value: "message3", topic: "topic2"}
89
+ # ]
90
+ # Racecar.wait_for_delivery {
91
+ # messages.each do |msg|
92
+ # Racecar.produce_async(value: msg[:value], topic: msg[:topic])
93
+ # end
94
+ # }
95
+ def wait_for_delivery
96
+ @batching = true
97
+ @delivery_handles.clear
98
+ yield
99
+ @delivery_handles.each do |handle|
100
+ deliver_with_error_handling(handle)
101
+ end
102
+ ensure
103
+ @delivery_handles.clear
104
+ @batching = false
105
+
106
+ nil
107
+ end
108
+
109
+ private
110
+
111
+ attr_reader :internal_producer
112
+
113
+ def deliver_with_error_handling(handle)
114
+ handle.wait
115
+ rescue Rdkafka::AbstractHandle::WaitTimeoutError => e
116
+ partition = MessageDeliveryError.partition_from_delivery_handle(handle)
117
+ @logger.warn "Still trying to deliver message to (partition #{partition})... (will try up to Racecar.config.message_timeout)"
118
+ retry
119
+ rescue Rdkafka::RdkafkaError => e
120
+ raise MessageDeliveryError.new(e, handle)
121
+ end
122
+
123
+ def with_instrumentation(action:, value:, topic:, **options)
124
+ message_size = value.respond_to?(:bytesize) ? value.bytesize : 0
125
+ instrumentation_payload = {
126
+ value: value,
127
+ topic: topic,
128
+ message_size: message_size,
129
+ buffer_size: @delivery_handles.size,
130
+ key: options.fetch(:key, nil),
131
+ partition: options.fetch(:partition, nil),
132
+ partition_key: options.fetch(:partition_key, nil)
133
+ }
134
+ @instrumenter.instrument(action, instrumentation_payload) do
135
+ yield
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,22 @@
1
+ module Racecar
2
+ class RebalanceListener
3
+ def initialize(config)
4
+ @config = config
5
+ @consumer_class = config.consumer_class
6
+ end
7
+
8
+ attr_reader :config, :consumer_class
9
+
10
+ def on_partitions_assigned(_consumer, topic_partition_list)
11
+ consumer_class.respond_to?(:on_partitions_assigned) &&
12
+ consumer_class.on_partitions_assigned(topic_partition_list.to_h)
13
+ rescue
14
+ end
15
+
16
+ def on_partitions_revoked(_consumer, topic_partition_list)
17
+ consumer_class.respond_to?(:on_partitions_revoked) &&
18
+ consumer_class.on_partitions_revoked(topic_partition_list.to_h)
19
+ rescue
20
+ end
21
+ end
22
+ end
@@ -5,6 +5,7 @@ require "racecar/pause"
5
5
  require "racecar/message"
6
6
  require "racecar/message_delivery_error"
7
7
  require "racecar/erroneous_state_error"
8
+ require "racecar/delivery_callback"
8
9
 
9
10
  module Racecar
10
11
  class Runner
@@ -67,6 +68,8 @@ module Racecar
67
68
  loop do
68
69
  break if @stop_requested
69
70
  resume_paused_partitions
71
+
72
+ @instrumenter.instrument("start_main_loop", instrumentation_payload)
70
73
  @instrumenter.instrument("main_loop", instrumentation_payload) do
71
74
  case process_method
72
75
  when :batch then
@@ -94,6 +97,7 @@ module Racecar
94
97
  ensure
95
98
  producer.close
96
99
  Racecar::Datadog.close if Object.const_defined?("Racecar::Datadog")
100
+ @instrumenter.instrument("shut_down", instrumentation_payload || {})
97
101
  end
98
102
 
99
103
  def stop
@@ -138,7 +142,7 @@ module Racecar
138
142
 
139
143
  def producer
140
144
  @producer ||= Rdkafka::Config.new(producer_config).producer.tap do |producer|
141
- producer.delivery_callback = delivery_callback
145
+ producer.delivery_callback = Racecar::DeliveryCallback.new(instrumenter: @instrumenter)
142
146
  end
143
147
  end
144
148
 
@@ -149,22 +153,14 @@ module Racecar
149
153
  "client.id" => config.client_id,
150
154
  "statistics.interval.ms" => config.statistics_interval_ms,
151
155
  "message.timeout.ms" => config.message_timeout * 1000,
156
+ "partitioner" => config.partitioner.to_s,
152
157
  }
158
+
153
159
  producer_config["compression.codec"] = config.producer_compression_codec.to_s unless config.producer_compression_codec.nil?
154
160
  producer_config.merge!(config.rdkafka_producer)
155
161
  producer_config
156
162
  end
157
163
 
158
- def delivery_callback
159
- ->(delivery_report) do
160
- payload = {
161
- offset: delivery_report.offset,
162
- partition: delivery_report.partition
163
- }
164
- @instrumenter.instrument("acknowledged_message", payload)
165
- end
166
- end
167
-
168
164
  def install_signal_handlers
169
165
  # Stop the consumer on SIGINT, SIGQUIT or SIGTERM.
170
166
  trap("QUIT") { stop }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Racecar
4
- VERSION = "2.8.2"
4
+ VERSION = "2.9.0"
5
5
  end
data/lib/racecar.rb CHANGED
@@ -8,6 +8,7 @@ require "racecar/consumer"
8
8
  require "racecar/consumer_set"
9
9
  require "racecar/runner"
10
10
  require "racecar/parallel_runner"
11
+ require "racecar/producer"
11
12
  require "racecar/config"
12
13
  require "racecar/version"
13
14
  require "ensure_hash_compact"
@@ -39,25 +40,42 @@ module Racecar
39
40
  config.logger = logger
40
41
  end
41
42
 
42
- def self.instrumenter
43
- @instrumenter ||= begin
44
- default_payload = { client_id: config.client_id, group_id: config.group_id }
43
+ def self.produce_async(value:, topic:, **options)
44
+ producer.produce_async(value: value, topic: topic, **options)
45
+ end
45
46
 
46
- Instrumenter.new(default_payload).tap do |instrumenter|
47
- if instrumenter.backend == NullInstrumenter
48
- logger.warn "ActiveSupport::Notifications not available, instrumentation is disabled"
49
- end
47
+ def self.produce_sync(value:, topic:, **options)
48
+ producer.produce_sync(value: value, topic: topic, **options)
49
+ end
50
+
51
+ def self.wait_for_delivery(&block)
52
+ producer.wait_for_delivery(&block)
53
+ end
54
+
55
+ def self.producer
56
+ Thread.current[:racecar_producer] ||= begin
57
+ if config.datadog_enabled
58
+ require "racecar/datadog"
50
59
  end
60
+ Racecar::Producer.new(config: config, logger: logger, instrumenter: instrumenter)
51
61
  end
52
62
  end
53
63
 
64
+ def self.instrumenter
65
+ config.instrumenter
66
+ end
67
+
54
68
  def self.run(processor)
55
- runner = Runner.new(processor, config: config, logger: logger, instrumenter: instrumenter)
69
+ runner(processor).run
70
+ end
71
+
72
+ def self.runner(processor)
73
+ runner = Runner.new(processor, config: config, logger: logger, instrumenter: config.instrumenter)
56
74
 
57
75
  if config.parallel_workers && config.parallel_workers > 1
58
- ParallelRunner.new(runner: runner, config: config, logger: logger).run
76
+ ParallelRunner.new(runner: runner, config: config, logger: logger)
59
77
  else
60
- runner.run
78
+ runner
61
79
  end
62
80
  end
63
81
  end
data/racecar.gemspec CHANGED
@@ -26,10 +26,10 @@ Gem::Specification.new do |spec|
26
26
  spec.add_runtime_dependency "rdkafka", "~> 0.12.0"
27
27
 
28
28
  spec.add_development_dependency "bundler", [">= 1.13", "< 3"]
29
- spec.add_development_dependency "pry"
29
+ spec.add_development_dependency "pry-byebug"
30
30
  spec.add_development_dependency "rake", "> 10.0"
31
31
  spec.add_development_dependency "rspec", "~> 3.0"
32
32
  spec.add_development_dependency "timecop"
33
33
  spec.add_development_dependency "dogstatsd-ruby", ">= 4.0.0", "< 6.0.0"
34
- spec.add_development_dependency "activesupport", ">= 4.0", "< 6.1"
34
+ spec.add_development_dependency "activesupport"
35
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: racecar
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.2
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Schierbeck
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-07-12 00:00:00.000000000 Z
12
+ date: 2023-09-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: king_konf
@@ -60,7 +60,7 @@ dependencies:
60
60
  - !ruby/object:Gem::Version
61
61
  version: '3'
62
62
  - !ruby/object:Gem::Dependency
63
- name: pry
63
+ name: pry-byebug
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
@@ -141,20 +141,14 @@ dependencies:
141
141
  requirements:
142
142
  - - ">="
143
143
  - !ruby/object:Gem::Version
144
- version: '4.0'
145
- - - "<"
146
- - !ruby/object:Gem::Version
147
- version: '6.1'
144
+ version: '0'
148
145
  type: :development
149
146
  prerelease: false
150
147
  version_requirements: !ruby/object:Gem::Requirement
151
148
  requirements:
152
149
  - - ">="
153
150
  - !ruby/object:Gem::Version
154
- version: '4.0'
155
- - - "<"
156
- - !ruby/object:Gem::Version
157
- version: '6.1'
151
+ version: '0'
158
152
  description:
159
153
  email:
160
154
  - dschierbeck@zendesk.com
@@ -165,9 +159,9 @@ executables:
165
159
  extensions: []
166
160
  extra_rdoc_files: []
167
161
  files:
168
- - ".circleci/config.yml"
169
162
  - ".github/dependabot.yml"
170
163
  - ".github/workflows/ci.yml"
164
+ - ".github/workflows/publish.yml"
171
165
  - ".gitignore"
172
166
  - ".rspec"
173
167
  - CHANGELOG.md
@@ -200,15 +194,19 @@ files:
200
194
  - lib/racecar/ctl.rb
201
195
  - lib/racecar/daemon.rb
202
196
  - lib/racecar/datadog.rb
197
+ - lib/racecar/delivery_callback.rb
203
198
  - lib/racecar/erroneous_state_error.rb
204
199
  - lib/racecar/heroku.rb
205
200
  - lib/racecar/instrumenter.rb
201
+ - lib/racecar/liveness_probe.rb
206
202
  - lib/racecar/message.rb
207
203
  - lib/racecar/message_delivery_error.rb
208
204
  - lib/racecar/null_instrumenter.rb
209
205
  - lib/racecar/parallel_runner.rb
210
206
  - lib/racecar/pause.rb
207
+ - lib/racecar/producer.rb
211
208
  - lib/racecar/rails_config_file_loader.rb
209
+ - lib/racecar/rebalance_listener.rb
212
210
  - lib/racecar/runner.rb
213
211
  - lib/racecar/version.rb
214
212
  - racecar.gemspec
@@ -231,7 +229,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
229
  - !ruby/object:Gem::Version
232
230
  version: '0'
233
231
  requirements: []
234
- rubygems_version: 3.0.3
232
+ rubygems_version: 3.0.3.1
235
233
  signing_key:
236
234
  specification_version: 4
237
235
  summary: A framework for running Kafka consumers
data/.circleci/config.yml DELETED
@@ -1,56 +0,0 @@
1
- version: 2.1
2
- orbs:
3
- ruby: circleci/ruby@0.1.2
4
-
5
- jobs:
6
- build:
7
- docker:
8
- - image: circleci/ruby:2.6.3-stretch-node
9
- executor: ruby/default
10
- steps:
11
- - checkout
12
- - run:
13
- name: Which bundler?
14
- command: bundle -v
15
- - ruby/bundle-install
16
- - run: bundle exec rspec --exclude-pattern='spec/integration/*_spec.rb'
17
- integration-tests:
18
- docker:
19
- - image: circleci/ruby:2.6.3-stretch-node
20
- - image: wurstmeister/zookeeper
21
- - image: wurstmeister/kafka:2.11-2.0.0
22
- environment:
23
- KAFKA_ADVERTISED_HOST_NAME: localhost
24
- KAFKA_ADVERTISED_PORT: 9092
25
- KAFKA_PORT: 9092
26
- KAFKA_ZOOKEEPER_CONNECT: localhost:2181
27
- KAFKA_DELETE_TOPIC_ENABLE: true
28
- - image: wurstmeister/kafka:2.11-2.0.0
29
- environment:
30
- KAFKA_ADVERTISED_HOST_NAME: localhost
31
- KAFKA_ADVERTISED_PORT: 9093
32
- KAFKA_PORT: 9093
33
- KAFKA_ZOOKEEPER_CONNECT: localhost:2181
34
- KAFKA_DELETE_TOPIC_ENABLE: true
35
- - image: wurstmeister/kafka:2.11-2.0.0
36
- environment:
37
- KAFKA_ADVERTISED_HOST_NAME: localhost
38
- KAFKA_ADVERTISED_PORT: 9094
39
- KAFKA_PORT: 9094
40
- KAFKA_ZOOKEEPER_CONNECT: localhost:2181
41
- KAFKA_DELETE_TOPIC_ENABLE: true
42
- executor: ruby/default
43
- steps:
44
- - checkout
45
- - run:
46
- name: Which bundler?
47
- command: bundle -v
48
- - ruby/bundle-install
49
- - run: bundle exec rspec --pattern='spec/integration/*_spec.rb'
50
-
51
- workflows:
52
- version: 2
53
- test:
54
- jobs:
55
- - build
56
- - integration-tests