waterdrop 2.8.7 → 2.8.8.rc1

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: c6fd5bbc4f935b3d55794ad4f62970ab9feab3d4ebd100f9394c1bb456b8a9b5
4
- data.tar.gz: 00ab23a92637285766a29741324b65422b77aba3522aa812b0f72350e3061585
3
+ metadata.gz: 51626a5ebbcd31ccb011d37675d29da824a0066993cf1a5e0b6117bc49c03c3e
4
+ data.tar.gz: 9d3755db2b6daf1fd54bed8a526de70237d60ddda4660a1abeab778a5b92183d
5
5
  SHA512:
6
- metadata.gz: 0a41a342c8b16e167f1292ea6068647ac100f21f7f632342280a0bf69debcee3e6929f070e63b0fde2c28b85454edc6845d66895376a953c3ccaad48873646df
7
- data.tar.gz: 89956ae428a553f91d91a8064954dea2095cb81457fd78f1565e31ec47e31799b823942931a08f1e6f2d0a7f04b7f730d5d1856fe01fd4de9a57055b5e301360
6
+ metadata.gz: 9060a4f68526fb4d3522fd58f69eb7a11c8ff58fcb3bc2a7d140715693bf4f4d4342bb2f79a8d7b75b7b4ad3178a3fa1d5732cdf1add8395b4f93d4abf4daee6
7
+ data.tar.gz: 236a114d76f18fd84a9802583dd32eb4494298d5ec0e9a11677430e474b71639561a98dbf50766998ea994fdb26f136bdc5ee0e1c91ec0553b7e2d81bb76e3e7
@@ -28,7 +28,6 @@ jobs:
28
28
  - '3.4'
29
29
  - '3.3'
30
30
  - '3.2'
31
- - '3.1'
32
31
  force_ruby_platform:
33
32
  - true
34
33
  - false
@@ -8,14 +8,7 @@ on:
8
8
 
9
9
  jobs:
10
10
  trigger-wiki-refresh:
11
- runs-on: >-
12
- ${{
13
- (github.event_name != 'pull_request' ||
14
- github.event.pull_request.head.repo.full_name == github.repository)
15
- && fromJSON('["self-hosted", "linux", "x64", "qemu"]')
16
- || 'ubuntu-latest'
17
- }}
18
-
11
+ runs-on: ubuntu-latest
19
12
  environment: wiki-trigger
20
13
  if: github.repository_owner == 'karafka'
21
14
  steps:
@@ -5,14 +5,7 @@ on:
5
5
  - '.github/workflows/**'
6
6
  jobs:
7
7
  verify_action_pins:
8
- runs-on: >-
9
- ${{
10
- (github.event_name != 'pull_request' ||
11
- github.event.pull_request.head.repo.full_name == github.repository)
12
- && fromJSON('["self-hosted", "linux", "x64", "qemu"]')
13
- || 'ubuntu-latest'
14
- }}
15
-
8
+ runs-on: ubuntu-latest
16
9
  steps:
17
10
  - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
18
11
  - name: Check SHA pins
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # WaterDrop changelog
2
2
 
3
+ ## 2.8.8 (Unreleased)
4
+ - [Feature] Add `WaterDrop::ConnectionPool` for efficient connection pooling using the proven `connection_pool` gem.
5
+ - [Feature] Add `WaterDrop.instrumentation` class-level instrumentation for producer lifecycle events. This allows external libraries to subscribe to `producer.created` and `producer.configured` events without needing producer instance references, enabling middleware injection and configuration by libraries like Datadog tracing.
6
+ - [Change] Remove Ruby `3.1` specs according to the EOL schedule.
7
+
3
8
  ## 2.8.7 (2025-09-02)
4
9
  - [Enhancement] Disable Nagle algorithm by default (improves latency / aligned with librdkafka)
5
10
  - [Change] Normalize how libs and dependencies are required (no functional change for the end user)
data/Gemfile CHANGED
@@ -14,6 +14,7 @@ group :development do
14
14
  end
15
15
 
16
16
  group :test do
17
+ gem 'connection_pool'
17
18
  gem 'ostruct'
18
19
  gem 'rspec'
19
20
  gem 'simplecov'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- waterdrop (2.8.7)
4
+ waterdrop (2.8.8.rc1)
5
5
  karafka-core (>= 2.4.9, < 3.0.0)
6
6
  karafka-rdkafka (>= 0.20.0)
7
7
  zeitwerk (~> 2.3)
@@ -10,6 +10,7 @@ GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
12
  byebug (12.0.0)
13
+ connection_pool (2.5.4)
13
14
  diff-lcs (1.6.2)
14
15
  docile (1.4.1)
15
16
  ffi (1.17.2)
@@ -98,6 +99,7 @@ PLATFORMS
98
99
 
99
100
  DEPENDENCIES
100
101
  byebug
102
+ connection_pool
101
103
  ostruct
102
104
  rspec
103
105
  simplecov
@@ -0,0 +1,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WaterDrop library
4
+ module WaterDrop
5
+ # Connection pool wrapper for WaterDrop producers using the proven connection_pool gem.
6
+ #
7
+ # This provides a clean WaterDrop-specific API while leveraging the battle-tested,
8
+ # connection_pool gem underneath. The wrapper hides the direct usage of the connection_pool
9
+ # gem and provides WaterDrop-specific configuration.
10
+ #
11
+ # @example Basic usage
12
+ # pool = WaterDrop::ConnectionPool.new(size: 10) do |config|
13
+ # config.kafka = { 'bootstrap.servers': 'localhost:9092' }
14
+ # config.deliver = true
15
+ # end
16
+ #
17
+ # pool.with do |producer|
18
+ # producer.produce_sync(topic: 'events', payload: 'data')
19
+ # end
20
+ #
21
+ # @example Transactional producers with unique IDs
22
+ # pool = WaterDrop::ConnectionPool.new(size: 5) do |config, index|
23
+ # config.kafka = {
24
+ # 'bootstrap.servers': 'localhost:9092',
25
+ # 'transactional.id': "my-app-#{index}"
26
+ # }
27
+ # end
28
+ #
29
+ # @example Global connection pool
30
+ # WaterDrop::ConnectionPool.setup(size: 20) do |config|
31
+ # config.kafka = { 'bootstrap.servers': ENV['KAFKA_BROKERS'] }
32
+ # end
33
+ #
34
+ # WaterDrop::ConnectionPool.with do |producer|
35
+ # producer.produce_async(topic: 'events', payload: 'data')
36
+ # end
37
+ class ConnectionPool
38
+ # Delegate key methods to underlying connection pool
39
+ extend Forwardable
40
+
41
+ def_delegators :@pool, :with, :size, :available
42
+
43
+ class << self
44
+ # Global connection pool instance
45
+ attr_accessor :default_pool
46
+
47
+ # Sets up a global connection pool
48
+ #
49
+ # @param size [Integer] Pool size (default: 5)
50
+ # @param timeout [Numeric] Connection timeout in seconds (default: 5)
51
+ # @param producer_config [Proc] Block to configure each producer in the pool
52
+ # @yield [config, index] Block to configure each producer in the pool, receives config and
53
+ # pool index
54
+ # @return [ConnectionPool] The configured global pool
55
+ #
56
+ # @example Basic setup
57
+ # WaterDrop::ConnectionPool.setup(size: 15) do |config|
58
+ # config.kafka = { 'bootstrap.servers': ENV['KAFKA_BROKERS'] }
59
+ # config.deliver = true
60
+ # end
61
+ #
62
+ # @example Transactional setup with unique IDs
63
+ # WaterDrop::ConnectionPool.setup(size: 5) do |config, index|
64
+ # config.kafka = {
65
+ # 'bootstrap.servers': ENV['KAFKA_BROKERS'],
66
+ # 'transactional.id': "my-app-#{index}"
67
+ # }
68
+ # end
69
+ def setup(size: 5, timeout: 5, &producer_config)
70
+ ensure_connection_pool_gem!
71
+
72
+ @default_pool = new(size: size, timeout: timeout, &producer_config)
73
+ end
74
+
75
+ # Executes a block with a producer from the global pool
76
+ #
77
+ # @param block [Proc] Block to execute with a producer
78
+ # @yield [producer] Producer from the global pool
79
+ # @return [Object] Result of the block
80
+ # @raise [RuntimeError] If no global pool is configured
81
+ #
82
+ # @example
83
+ # WaterDrop::ConnectionPool.with do |producer|
84
+ # producer.produce_sync(topic: 'events', payload: 'data')
85
+ # end
86
+ def with(&block)
87
+ raise 'No global connection pool configured. Call setup first.' unless @default_pool
88
+
89
+ @default_pool.with(&block)
90
+ end
91
+
92
+ # Get statistics about the global pool
93
+ #
94
+ # @return [Hash, nil] Pool statistics or nil if no global pool
95
+ def stats
96
+ return nil unless @default_pool
97
+
98
+ {
99
+ size: @default_pool.size,
100
+ available: @default_pool.available
101
+ }
102
+ end
103
+
104
+ # Shutdown the global connection pool
105
+ def shutdown
106
+ return unless @default_pool
107
+
108
+ @default_pool.shutdown
109
+ @default_pool = nil
110
+ end
111
+
112
+ # Reload the global connection pool
113
+ def reload
114
+ @default_pool&.reload
115
+ end
116
+
117
+ # Check if the global connection pool is active (configured)
118
+ #
119
+ # @return [Boolean] true if global pool is configured, false otherwise
120
+ def active?
121
+ !@default_pool.nil?
122
+ end
123
+
124
+ private
125
+
126
+ # Ensures the connection_pool gem is available (class method)
127
+ # Only requires it when actually needed (lazy loading)
128
+ def ensure_connection_pool_gem!
129
+ return if defined?(::ConnectionPool)
130
+
131
+ require 'connection_pool'
132
+ rescue LoadError
133
+ raise LoadError, <<~ERROR
134
+ WaterDrop::ConnectionPool requires the 'connection_pool' gem.
135
+
136
+ Add this to your Gemfile:
137
+ gem 'connection_pool'
138
+
139
+ Then run:
140
+ bundle install
141
+ ERROR
142
+ end
143
+ end
144
+
145
+ # Creates a new WaterDrop connection pool
146
+ #
147
+ # @param size [Integer] Pool size (default: 5)
148
+ # @param timeout [Numeric] Connection timeout in seconds (default: 5)
149
+ # @param producer_config [Proc] Block to configure each producer in the pool
150
+ # @yield [config, index] Block to configure each producer in the pool, receives config and
151
+ # pool index
152
+ def initialize(size: 5, timeout: 5, &producer_config)
153
+ self.class.send(:ensure_connection_pool_gem!)
154
+
155
+ @producer_config = producer_config
156
+ @pool_index = 0
157
+ @pool_mutex = Mutex.new
158
+
159
+ @pool = ::ConnectionPool.new(size: size, timeout: timeout) do
160
+ producer_index = @pool_mutex.synchronize { @pool_index += 1 }
161
+
162
+ WaterDrop::Producer.new do |config|
163
+ if @producer_config.arity == 2
164
+ @producer_config.call(config, producer_index)
165
+ else
166
+ @producer_config.call(config)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ # Get pool statistics
173
+ #
174
+ # @return [Hash] Pool statistics
175
+ def stats
176
+ {
177
+ size: @pool.size,
178
+ available: @pool.available
179
+ }
180
+ end
181
+
182
+ # Shutdown the connection pool
183
+ def shutdown
184
+ @pool.shutdown do |producer|
185
+ producer.close! if producer&.status&.active?
186
+ end
187
+ end
188
+
189
+ # Reload all connections in the pool
190
+ # Useful for configuration changes or error recovery
191
+ def reload
192
+ @pool.reload do |producer|
193
+ producer.close! if producer&.status&.active?
194
+ end
195
+ end
196
+
197
+ # Returns the underlying connection_pool instance
198
+ # This allows access to advanced connection_pool features if needed
199
+ #
200
+ # @return [::ConnectionPool] The underlying connection pool
201
+ attr_reader :pool
202
+ end
203
+
204
+ # Convenience methods on the WaterDrop module for global pool access
205
+ class << self
206
+ # Execute a block with a producer from the global connection pool
207
+ # Only available when connection pool is configured
208
+ #
209
+ # @param block [Proc] Block to execute with a producer
210
+ # @yield [producer] Producer from the global pool
211
+ # @return [Object] Result of the block
212
+ #
213
+ # @example
214
+ # WaterDrop.with do |producer|
215
+ # producer.produce_sync(topic: 'events', payload: 'data')
216
+ # end
217
+ def with(&block)
218
+ ConnectionPool.with(&block)
219
+ end
220
+
221
+ # Access the global connection pool
222
+ #
223
+ # @return [WaterDrop::ConnectionPool] The global pool
224
+ #
225
+ # @example
226
+ # WaterDrop.pool.with do |producer|
227
+ # producer.produce_async(topic: 'events', payload: 'data')
228
+ # end
229
+ def pool
230
+ ConnectionPool.default_pool
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WaterDrop
4
+ module Instrumentation
5
+ # WaterDrop class-level instrumentation monitor for global events
6
+ # This monitor only supports class-level lifecycle events, not per-producer events
7
+ class ClassMonitor < ::Karafka::Core::Monitoring::Monitor
8
+ # @param notifications_bus [Object] class-level notifications bus
9
+ # @param namespace [String, nil] namespace for events or nil if no namespace
10
+ def initialize(
11
+ notifications_bus = WaterDrop::Instrumentation::ClassNotifications.new,
12
+ namespace = nil
13
+ )
14
+ super(notifications_bus, namespace)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WaterDrop
4
+ module Instrumentation
5
+ # Class-level notifications for WaterDrop global instrumentation
6
+ # This only supports events that occur at the class/module level, not per-producer instance
7
+ class ClassNotifications < ::Karafka::Core::Monitoring::Notifications
8
+ # List of events that are available at the class level via WaterDrop.instrumentation
9
+ # These are lifecycle events for producer creation and configuration
10
+ EVENTS = %w[
11
+ producer.created
12
+ producer.configured
13
+ ].freeze
14
+
15
+ # @return [WaterDrop::Instrumentation::ClassNotifications] class-level notification instance
16
+ def initialize
17
+ super
18
+ EVENTS.each { |event| register_event(event) }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -4,7 +4,7 @@ module WaterDrop
4
4
  module Instrumentation
5
5
  # Instrumented is used to hookup external monitoring services to monitor how WaterDrop works
6
6
  class Notifications < ::Karafka::Core::Monitoring::Notifications
7
- # List of events that we support in the system and to which a monitor client can hook up
7
+ # List of events that we support at the instance level (per-producer instrumentation)
8
8
  # @note The non-error once support timestamp benchmarking
9
9
  EVENTS = %w[
10
10
  producer.connected
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WaterDrop
4
+ class Producer
5
+ # Module that provides class-level instrumentation capabilities to producers
6
+ # This allows producers to emit lifecycle events that can be subscribed to at the global level
7
+ module ClassMonitor
8
+ private
9
+
10
+ # @return [WaterDrop::Instrumentation::ClassMonitor] global class-level monitor for
11
+ # instrumentation events
12
+ def class_monitor
13
+ WaterDrop.instrumentation
14
+ end
15
+ end
16
+ end
17
+ end
@@ -8,6 +8,7 @@ module WaterDrop
8
8
  include Async
9
9
  include Buffer
10
10
  include Transactions
11
+ include ClassMonitor
11
12
  include ::Karafka::Core::Helpers::Time
12
13
  include ::Karafka::Core::Taggable
13
14
 
@@ -60,6 +61,13 @@ module WaterDrop
60
61
  @status = Status.new
61
62
  @messages = []
62
63
 
64
+ # Instrument producer creation for global listeners
65
+ class_monitor.instrument(
66
+ 'producer.created',
67
+ producer: self,
68
+ producer_id: @id
69
+ )
70
+
63
71
  return unless block
64
72
 
65
73
  setup(&block)
@@ -80,7 +88,19 @@ module WaterDrop
80
88
  @contract = Contracts::Message.new(max_payload_size: @config.max_payload_size)
81
89
  @default_variant = Variant.new(self, default: true)
82
90
 
83
- return @status.configured! if @config.idle_disconnect_timeout.zero?
91
+ if @config.idle_disconnect_timeout.zero?
92
+ @status.configured!
93
+
94
+ # Instrument producer configuration for global listeners
95
+ class_monitor.instrument(
96
+ 'producer.configured',
97
+ producer: self,
98
+ producer_id: @id,
99
+ config: @config
100
+ )
101
+
102
+ return
103
+ end
84
104
 
85
105
  # Setup idle disconnect listener if configured so we preserve tcp connections on rarely
86
106
  # used producers
@@ -92,6 +112,14 @@ module WaterDrop
92
112
  @monitor.subscribe(disconnector)
93
113
 
94
114
  @status.configured!
115
+
116
+ # Instrument producer configuration for global listeners
117
+ class_monitor.instrument(
118
+ 'producer.configured',
119
+ producer: self,
120
+ producer_id: @id,
121
+ config: @config
122
+ )
95
123
  end
96
124
 
97
125
  # @return [Rdkafka::Producer] raw rdkafka producer
@@ -3,5 +3,5 @@
3
3
  # WaterDrop library
4
4
  module WaterDrop
5
5
  # Current WaterDrop version
6
- VERSION = '2.8.7'
6
+ VERSION = '2.8.8.rc1'
7
7
  end
data/lib/waterdrop.rb CHANGED
@@ -16,6 +16,22 @@ module WaterDrop
16
16
  def gem_root
17
17
  Pathname.new(File.expand_path('..', __dir__))
18
18
  end
19
+
20
+ # @return [WaterDrop::Instrumentation::ClassMonitor] global instrumentation monitor for
21
+ # class-level event subscriptions. This allows external libraries to subscribe to WaterDrop
22
+ # lifecycle events without needing producer instance references.
23
+ #
24
+ # @note Only supports class-level events (producer.created, producer.configured), not
25
+ # instance events
26
+ #
27
+ # @example Subscribe to producer creation events
28
+ # WaterDrop.instrumentation.subscribe('producer.created') do |event|
29
+ # producer = event[:producer]
30
+ # # Configure producer or add middleware
31
+ # end
32
+ def instrumentation
33
+ @instrumentation ||= Instrumentation::ClassMonitor.new
34
+ end
19
35
  end
20
36
  end
21
37
 
data/waterdrop.gemspec CHANGED
@@ -27,7 +27,6 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = %w[lib]
28
28
 
29
29
  spec.metadata = {
30
- 'funding_uri' => 'https://karafka.io/#become-pro',
31
30
  'homepage_uri' => 'https://karafka.io',
32
31
  'changelog_uri' => 'https://karafka.io/docs/Changelog-WaterDrop',
33
32
  'bug_tracker_uri' => 'https://github.com/karafka/waterdrop/issues',
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waterdrop
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.7
4
+ version: 2.8.8.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -94,6 +94,7 @@ files:
94
94
  - lib/waterdrop/clients/dummy.rb
95
95
  - lib/waterdrop/clients/rdkafka.rb
96
96
  - lib/waterdrop/config.rb
97
+ - lib/waterdrop/connection_pool.rb
97
98
  - lib/waterdrop/contracts.rb
98
99
  - lib/waterdrop/contracts/config.rb
99
100
  - lib/waterdrop/contracts/message.rb
@@ -105,6 +106,8 @@ files:
105
106
  - lib/waterdrop/instrumentation/callbacks/error.rb
106
107
  - lib/waterdrop/instrumentation/callbacks/oauthbearer_token_refresh.rb
107
108
  - lib/waterdrop/instrumentation/callbacks/statistics.rb
109
+ - lib/waterdrop/instrumentation/class_monitor.rb
110
+ - lib/waterdrop/instrumentation/class_notifications.rb
108
111
  - lib/waterdrop/instrumentation/idle_disconnector_listener.rb
109
112
  - lib/waterdrop/instrumentation/logger_listener.rb
110
113
  - lib/waterdrop/instrumentation/monitor.rb
@@ -116,6 +119,7 @@ files:
116
119
  - lib/waterdrop/producer/async.rb
117
120
  - lib/waterdrop/producer/buffer.rb
118
121
  - lib/waterdrop/producer/builder.rb
122
+ - lib/waterdrop/producer/class_monitor.rb
119
123
  - lib/waterdrop/producer/status.rb
120
124
  - lib/waterdrop/producer/sync.rb
121
125
  - lib/waterdrop/producer/transactions.rb
@@ -129,7 +133,6 @@ licenses:
129
133
  - LGPL-3.0-only
130
134
  - Commercial
131
135
  metadata:
132
- funding_uri: https://karafka.io/#become-pro
133
136
  homepage_uri: https://karafka.io
134
137
  changelog_uri: https://karafka.io/docs/Changelog-WaterDrop
135
138
  bug_tracker_uri: https://github.com/karafka/waterdrop/issues