waterdrop 2.0.7 → 2.6.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +22 -11
- data/.ruby-version +1 -1
- data/CHANGELOG.md +200 -0
- data/Gemfile +0 -2
- data/Gemfile.lock +32 -75
- data/README.md +22 -275
- data/certs/cert_chain.pem +26 -0
- data/config/locales/errors.yml +33 -0
- data/docker-compose.yml +19 -12
- data/lib/waterdrop/clients/buffered.rb +90 -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/{water_drop → waterdrop}/errors.rb +14 -7
- data/lib/waterdrop/instrumentation/callbacks/delivery.rb +102 -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} +66 -21
- data/lib/waterdrop/instrumentation/monitor.rb +20 -0
- data/lib/{water_drop/instrumentation/monitor.rb → waterdrop/instrumentation/notifications.rb} +12 -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 +12 -30
- 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 +170 -0
- data/lib/waterdrop/producer.rb +308 -0
- data/lib/{water_drop → waterdrop}/version.rb +1 -1
- data/lib/waterdrop.rb +28 -2
- data/renovate.json +6 -0
- data/waterdrop.gemspec +14 -11
- data.tar.gz.sig +0 -0
- metadata +71 -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
|