waterdrop 2.0.7 → 2.6.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +39 -13
- data/.ruby-version +1 -1
- data/CHANGELOG.md +212 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +45 -75
- data/README.md +22 -275
- data/certs/cert_chain.pem +26 -0
- data/config/locales/errors.yml +39 -0
- data/docker-compose.yml +21 -12
- data/lib/waterdrop/clients/buffered.rb +95 -0
- data/lib/waterdrop/clients/dummy.rb +69 -0
- data/lib/waterdrop/clients/rdkafka.rb +34 -0
- data/lib/{water_drop → waterdrop}/config.rb +39 -16
- data/lib/waterdrop/contracts/config.rb +43 -0
- data/lib/waterdrop/contracts/message.rb +64 -0
- data/lib/waterdrop/contracts/transactional_offset.rb +21 -0
- data/lib/{water_drop → waterdrop}/errors.rb +23 -7
- data/lib/waterdrop/helpers/counter.rb +27 -0
- data/lib/waterdrop/instrumentation/callbacks/delivery.rb +106 -0
- data/lib/{water_drop → waterdrop}/instrumentation/callbacks/error.rb +6 -2
- data/lib/{water_drop → waterdrop}/instrumentation/callbacks/statistics.rb +1 -1
- data/lib/{water_drop/instrumentation/stdout_listener.rb → waterdrop/instrumentation/logger_listener.rb} +91 -21
- data/lib/waterdrop/instrumentation/monitor.rb +20 -0
- data/lib/{water_drop/instrumentation/monitor.rb → waterdrop/instrumentation/notifications.rb} +15 -14
- data/lib/waterdrop/instrumentation/vendors/datadog/dashboard.json +1 -0
- data/lib/waterdrop/instrumentation/vendors/datadog/metrics_listener.rb +210 -0
- data/lib/waterdrop/middleware.rb +50 -0
- data/lib/{water_drop → waterdrop}/producer/async.rb +40 -4
- data/lib/{water_drop → waterdrop}/producer/buffer.rb +13 -31
- data/lib/{water_drop → waterdrop}/producer/builder.rb +6 -11
- data/lib/{water_drop → waterdrop}/producer/sync.rb +44 -15
- data/lib/waterdrop/producer/transactions.rb +219 -0
- data/lib/waterdrop/producer.rb +324 -0
- data/lib/{water_drop → waterdrop}/version.rb +1 -1
- data/lib/waterdrop.rb +27 -2
- data/renovate.json +6 -0
- data/waterdrop.gemspec +14 -11
- data.tar.gz.sig +0 -0
- metadata +73 -111
- metadata.gz.sig +0 -0
- data/certs/mensfeld.pem +0 -25
- data/config/errors.yml +0 -6
- data/lib/water_drop/contracts/config.rb +0 -26
- data/lib/water_drop/contracts/message.rb +0 -42
- data/lib/water_drop/instrumentation/callbacks/delivery.rb +0 -30
- data/lib/water_drop/instrumentation/callbacks/statistics_decorator.rb +0 -77
- data/lib/water_drop/instrumentation/callbacks_manager.rb +0 -39
- data/lib/water_drop/instrumentation.rb +0 -20
- data/lib/water_drop/patches/rdkafka/bindings.rb +0 -42
- data/lib/water_drop/patches/rdkafka/producer.rb +0 -20
- data/lib/water_drop/producer/dummy_client.rb +0 -32
- data/lib/water_drop/producer.rb +0 -162
- data/lib/water_drop.rb +0 -36
- /data/lib/{water_drop → waterdrop}/contracts.rb +0 -0
- /data/lib/{water_drop → waterdrop}/producer/status.rb +0 -0
data/lib/water_drop/producer.rb
DELETED
@@ -1,162 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WaterDrop
|
4
|
-
# Main WaterDrop messages producer
|
5
|
-
class Producer
|
6
|
-
include Sync
|
7
|
-
include Async
|
8
|
-
include Buffer
|
9
|
-
|
10
|
-
# @return [String] uuid of the current producer
|
11
|
-
attr_reader :id
|
12
|
-
# @return [Status] producer status object
|
13
|
-
attr_reader :status
|
14
|
-
# @return [Concurrent::Array] internal messages buffer
|
15
|
-
attr_reader :messages
|
16
|
-
# @return [Object] monitor we want to use
|
17
|
-
attr_reader :monitor
|
18
|
-
# @return [Object] dry-configurable config object
|
19
|
-
attr_reader :config
|
20
|
-
|
21
|
-
# Creates a not-yet-configured instance of the producer
|
22
|
-
# @param block [Proc] configuration block
|
23
|
-
# @return [Producer] producer instance
|
24
|
-
def initialize(&block)
|
25
|
-
@buffer_mutex = Mutex.new
|
26
|
-
@connecting_mutex = Mutex.new
|
27
|
-
@closing_mutex = Mutex.new
|
28
|
-
|
29
|
-
@status = Status.new
|
30
|
-
@messages = Concurrent::Array.new
|
31
|
-
|
32
|
-
return unless block
|
33
|
-
|
34
|
-
setup(&block)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Sets up the whole configuration and initializes all that is needed
|
38
|
-
# @param block [Block] configuration block
|
39
|
-
def setup(&block)
|
40
|
-
raise Errors::ProducerAlreadyConfiguredError, id unless @status.initial?
|
41
|
-
|
42
|
-
@config = Config
|
43
|
-
.new
|
44
|
-
.setup(&block)
|
45
|
-
.config
|
46
|
-
|
47
|
-
@id = @config.id
|
48
|
-
@monitor = @config.monitor
|
49
|
-
@contract = Contracts::Message.new(max_payload_size: @config.max_payload_size)
|
50
|
-
@status.configured!
|
51
|
-
end
|
52
|
-
|
53
|
-
# @return [Rdkafka::Producer] raw rdkafka producer
|
54
|
-
# @note Client is lazy initialized, keeping in mind also the fact of a potential fork that
|
55
|
-
# can happen any time.
|
56
|
-
# @note It is not recommended to fork a producer that is already in use so in case of
|
57
|
-
# bootstrapping a cluster, it's much better to fork configured but not used producers
|
58
|
-
def client
|
59
|
-
return @client if @client && @pid == Process.pid
|
60
|
-
|
61
|
-
# Don't allow to obtain a client reference for a producer that was not configured
|
62
|
-
raise Errors::ProducerNotConfiguredError, id if @status.initial?
|
63
|
-
|
64
|
-
@connecting_mutex.synchronize do
|
65
|
-
return @client if @client && @pid == Process.pid
|
66
|
-
|
67
|
-
# We should raise an error when trying to use a producer from a fork, that is already
|
68
|
-
# connected to Kafka. We allow forking producers only before they are used
|
69
|
-
raise Errors::ProducerUsedInParentProcess, Process.pid if @status.connected?
|
70
|
-
|
71
|
-
# We undefine all the finalizers, in case it was a fork, so the finalizers from the parent
|
72
|
-
# process don't leak
|
73
|
-
ObjectSpace.undefine_finalizer(id)
|
74
|
-
# Finalizer tracking is needed for handling shutdowns gracefully.
|
75
|
-
# I don't expect everyone to remember about closing all the producers all the time, thus
|
76
|
-
# this approach is better. Although it is still worth keeping in mind, that this will
|
77
|
-
# block GC from removing a no longer used producer unless closed properly but at least
|
78
|
-
# won't crash the VM upon closing the process
|
79
|
-
ObjectSpace.define_finalizer(id, proc { close })
|
80
|
-
|
81
|
-
@pid = Process.pid
|
82
|
-
@client = Builder.new.call(self, @config)
|
83
|
-
|
84
|
-
# Register statistics runner for this particular type of callbacks
|
85
|
-
::WaterDrop::Instrumentation.statistics_callbacks.add(
|
86
|
-
@id,
|
87
|
-
Instrumentation::Callbacks::Statistics.new(@id, @client.name, @config.monitor)
|
88
|
-
)
|
89
|
-
|
90
|
-
# Register error tracking callback
|
91
|
-
::WaterDrop::Instrumentation.error_callbacks.add(
|
92
|
-
@id,
|
93
|
-
Instrumentation::Callbacks::Error.new(@id, @client.name, @config.monitor)
|
94
|
-
)
|
95
|
-
|
96
|
-
@status.connected!
|
97
|
-
end
|
98
|
-
|
99
|
-
@client
|
100
|
-
end
|
101
|
-
|
102
|
-
# Flushes the buffers in a sync way and closes the producer
|
103
|
-
def close
|
104
|
-
@closing_mutex.synchronize do
|
105
|
-
return unless @status.active?
|
106
|
-
|
107
|
-
@monitor.instrument(
|
108
|
-
'producer.closed',
|
109
|
-
producer_id: id
|
110
|
-
) do
|
111
|
-
@status.closing!
|
112
|
-
|
113
|
-
# No need for auto-gc if everything got closed by us
|
114
|
-
# This should be used only in case a producer was not closed properly and forgotten
|
115
|
-
ObjectSpace.undefine_finalizer(id)
|
116
|
-
|
117
|
-
# Flush has its own buffer mutex but even if it is blocked, flushing can still happen
|
118
|
-
# as we close the client after the flushing (even if blocked by the mutex)
|
119
|
-
flush(true)
|
120
|
-
|
121
|
-
# We should not close the client in several threads the same time
|
122
|
-
# It is safe to run it several times but not exactly the same moment
|
123
|
-
# We also mark it as closed only if it was connected, if not, it would trigger a new
|
124
|
-
# connection that anyhow would be immediately closed
|
125
|
-
client.close if @client
|
126
|
-
|
127
|
-
# Remove callbacks runners that were registered
|
128
|
-
::WaterDrop::Instrumentation.statistics_callbacks.delete(@id)
|
129
|
-
::WaterDrop::Instrumentation.error_callbacks.delete(@id)
|
130
|
-
|
131
|
-
@status.closed!
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# Ensures that we don't run any operations when the producer is not configured or when it
|
137
|
-
# was already closed
|
138
|
-
def ensure_active!
|
139
|
-
return if @status.active?
|
140
|
-
|
141
|
-
raise Errors::ProducerNotConfiguredError, id if @status.initial?
|
142
|
-
raise Errors::ProducerClosedError, id if @status.closing? || @status.closed?
|
143
|
-
|
144
|
-
# This should never happen
|
145
|
-
raise Errors::StatusInvalidError, [id, @status.to_s]
|
146
|
-
end
|
147
|
-
|
148
|
-
# Ensures that the message we want to send out to Kafka is actually valid and that it can be
|
149
|
-
# sent there
|
150
|
-
# @param message [Hash] message we want to send
|
151
|
-
# @raise [Karafka::Errors::MessageInvalidError]
|
152
|
-
def validate_message!(message)
|
153
|
-
result = @contract.call(message)
|
154
|
-
return if result.success?
|
155
|
-
|
156
|
-
raise Errors::MessageInvalidError, [
|
157
|
-
result.errors.to_h,
|
158
|
-
message
|
159
|
-
]
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
data/lib/water_drop.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# External components
|
4
|
-
# delegate should be removed because we don't need it, we just add it because of ruby-kafka
|
5
|
-
%w[
|
6
|
-
concurrent/array
|
7
|
-
dry-configurable
|
8
|
-
dry/monitor/notifications
|
9
|
-
dry-validation
|
10
|
-
rdkafka
|
11
|
-
json
|
12
|
-
zeitwerk
|
13
|
-
securerandom
|
14
|
-
].each { |lib| require lib }
|
15
|
-
|
16
|
-
# WaterDrop library
|
17
|
-
module WaterDrop
|
18
|
-
class << self
|
19
|
-
# @return [String] root path of this gem
|
20
|
-
def gem_root
|
21
|
-
Pathname.new(File.expand_path('..', __dir__))
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
Zeitwerk::Loader
|
27
|
-
.for_gem
|
28
|
-
.tap { |loader| loader.ignore("#{__dir__}/waterdrop.rb") }
|
29
|
-
.tap(&:setup)
|
30
|
-
.tap(&:eager_load)
|
31
|
-
|
32
|
-
# Rdkafka uses a single global callback for things. We bypass that by injecting a manager for
|
33
|
-
# each callback type. Callback manager allows us to register more than one callback
|
34
|
-
# @note Those managers are also used by Karafka for consumer related statistics
|
35
|
-
Rdkafka::Config.statistics_callback = WaterDrop::Instrumentation.statistics_callbacks
|
36
|
-
Rdkafka::Config.error_callback = WaterDrop::Instrumentation.error_callbacks
|
File without changes
|
File without changes
|