karafka 2.4.3 → 2.4.4

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: c37b855609a9d20ca0b5cf13875d30ccb71b4072b3f07886f10445ce761c6e1e
4
- data.tar.gz: 852fa3340f0dd1cbc2286823a2451c635568440c3e753e7235a72dc85289fcf5
3
+ metadata.gz: 1b7333a7812643bc128c721e411449dfd477adfa52968c7c291900e622cdcc04
4
+ data.tar.gz: 7062798152f0d4e097e5c4fcd228798103a680441579bbc1918c8bf5a65ce98b
5
5
  SHA512:
6
- metadata.gz: 373b460007cefdb039b5f2fd00abdc722f42c5bd8f324dad121c7e293264ca4a63cb63cb4fa5057e0f2ff856fe6c28073c04d2ffb21250dd534408ee511807c9
7
- data.tar.gz: 595e408578801d949bb2b1e8d3337e8535473b80c7a1db34e56822214de9fd2083a594433e264ed8675c2292cd3aa73bacaef44938ad7a4cd743292097fd4d8f
6
+ metadata.gz: bd24bd450ac1cda02d18d7d487317139988b943ecc92c606cb713f13c819db044d6feae978873d26445e1ebf8d8823bedfd7efae58bc53c8213be9ef56a84c1d
7
+ data.tar.gz: 0f2f4b50ea90cd76f6275e0cdc9eeada5e5cd30bf6c18a219209dfe4b717d90ef88d7c6b68797e9eb0615c83ba03ee738d41164014ce74aaf27970ef07ba5974
checksums.yaml.gz.sig CHANGED
Binary file
@@ -148,6 +148,11 @@ jobs:
148
148
 
149
149
  bundle config set without 'tools benchmarks docs'
150
150
 
151
+ - name: Fix directory permissions for Bundler
152
+ run: |
153
+ chmod -R o-w /opt/hostedtoolcache/Ruby/3*/x64/lib/ruby/gems/3*/gems
154
+ chmod +t /opt/hostedtoolcache/Ruby/3*/x64/lib/ruby/gems/3*/gems
155
+
151
156
  - name: Bundle install
152
157
  run: |
153
158
  bundle config set without development
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Karafka framework changelog
2
2
 
3
+ ## 2.4.4 (2024-07-04)
4
+ - [Enhancement] Allow for offset storing from the Filtering API.
5
+ - [Enhancement] Print more extensive error info on forceful shutdown.
6
+ - [Enhancement] Include `original_key` in the DLQ dispatch headers.
7
+ - [Enhancement] Support embedding mode control management from the trap context.
8
+ - [Enhancement] Make sure, that the listener thread is stopped before restarting.
9
+ - [Fix] Do not block on hanging listener shutdown when invoking forceful shutdown.
10
+ - [Fix] Static membership fencing error is not propagated explicitly enough.
11
+ - [Fix] Make sure DLQ dispatches raw headers and not deserialized headers (same as payload).
12
+ - [Fix] Fix a typo where `ms` in logger listener would not have space before it.
13
+ - [Maintenance] Require `karafka-core` `>=` `2.4.3`.
14
+ - [Maintenance] Allow for usage of `karafka-rdkafka` `~` `0.16` to support librdkafka `2.4.0`.
15
+ - [Maintenance] Lower the precision reporting to 100 microseconds in the logger listener.
16
+
3
17
  ## 2.4.3 (2024-06-12)
4
18
  - [Enhancement] Allow for customization of Virtual Partitions reducer for enhanced parallelization.
5
19
  - [Enhancement] Add more error codes to early report on polling issues (kidlab)
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka (2.4.3)
4
+ karafka (2.4.4)
5
5
  base64 (~> 0.2)
6
- karafka-core (>= 2.4.0, < 2.5.0)
6
+ karafka-core (>= 2.4.3, < 2.5.0)
7
7
  waterdrop (>= 2.7.3, < 3.0.0)
8
8
  zeitwerk (~> 2.3)
9
9
 
@@ -26,12 +26,12 @@ GEM
26
26
  base64 (0.2.0)
27
27
  bigdecimal (3.1.8)
28
28
  byebug (11.1.3)
29
- concurrent-ruby (1.3.1)
29
+ concurrent-ruby (1.3.3)
30
30
  connection_pool (2.4.1)
31
31
  diff-lcs (1.5.1)
32
32
  docile (1.4.0)
33
33
  drb (2.2.1)
34
- erubi (1.12.0)
34
+ erubi (1.13.0)
35
35
  factory_bot (6.4.6)
36
36
  activesupport (>= 5.0.0)
37
37
  ffi (1.17.0)
@@ -39,13 +39,13 @@ GEM
39
39
  activesupport (>= 6.1)
40
40
  i18n (1.14.5)
41
41
  concurrent-ruby (~> 1.0)
42
- karafka-core (2.4.0)
43
- karafka-rdkafka (>= 0.15.0, < 0.16.0)
44
- karafka-rdkafka (0.15.1)
42
+ karafka-core (2.4.3)
43
+ karafka-rdkafka (>= 0.15.0, < 0.17.0)
44
+ karafka-rdkafka (0.16.0)
45
45
  ffi (~> 1.15)
46
46
  mini_portile2 (~> 2.6)
47
47
  rake (> 12)
48
- karafka-testing (2.4.3)
48
+ karafka-testing (2.4.4)
49
49
  karafka (>= 2.4.0, < 2.5.0)
50
50
  waterdrop (>= 2.7.0)
51
51
  karafka-web (0.9.1)
@@ -55,12 +55,12 @@ GEM
55
55
  roda (~> 3.68, >= 3.69)
56
56
  tilt (~> 2.0)
57
57
  mini_portile2 (2.8.7)
58
- minitest (5.23.1)
58
+ minitest (5.24.0)
59
59
  mutex_m (0.2.0)
60
60
  ostruct (0.6.0)
61
- rack (3.0.11)
61
+ rack (3.1.4)
62
62
  rake (13.2.1)
63
- roda (3.80.0)
63
+ roda (3.81.0)
64
64
  rack
65
65
  rspec (3.13.0)
66
66
  rspec-core (~> 3.13.0)
@@ -68,7 +68,7 @@ GEM
68
68
  rspec-mocks (~> 3.13.0)
69
69
  rspec-core (3.13.0)
70
70
  rspec-support (~> 3.13.0)
71
- rspec-expectations (3.13.0)
71
+ rspec-expectations (3.13.1)
72
72
  diff-lcs (>= 1.2.0, < 2.0)
73
73
  rspec-support (~> 3.13.0)
74
74
  rspec-mocks (3.13.1)
@@ -81,14 +81,14 @@ GEM
81
81
  simplecov_json_formatter (~> 0.1)
82
82
  simplecov-html (0.12.3)
83
83
  simplecov_json_formatter (0.1.4)
84
- tilt (2.3.0)
84
+ tilt (2.4.0)
85
85
  tzinfo (2.0.6)
86
86
  concurrent-ruby (~> 1.0)
87
87
  waterdrop (2.7.3)
88
88
  karafka-core (>= 2.4.0, < 3.0.0)
89
89
  karafka-rdkafka (>= 0.15.1)
90
90
  zeitwerk (~> 2.3)
91
- zeitwerk (2.6.15)
91
+ zeitwerk (2.6.16)
92
92
 
93
93
  PLATFORMS
94
94
  ruby
@@ -106,4 +106,4 @@ DEPENDENCIES
106
106
  simplecov
107
107
 
108
108
  BUNDLED WITH
109
- 2.5.11
109
+ 2.5.14
data/karafka.gemspec CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
22
22
  DESC
23
23
 
24
24
  spec.add_dependency 'base64', '~> 0.2'
25
- spec.add_dependency 'karafka-core', '>= 2.4.0', '< 2.5.0'
25
+ spec.add_dependency 'karafka-core', '>= 2.4.3', '< 2.5.0'
26
26
  spec.add_dependency 'waterdrop', '>= 2.7.3', '< 3.0.0'
27
27
  spec.add_dependency 'zeitwerk', '~> 2.3'
28
28
 
@@ -559,6 +559,9 @@ module Karafka
559
559
  early_report = true
560
560
  when :cluster_authorization_failed # 31
561
561
  early_report = true
562
+ # This can happen for many reasons, including issues with static membership being fenced
563
+ when :fatal # -150
564
+ early_report = true
562
565
  # @see
563
566
  # https://github.com/confluentinc/confluent-kafka-dotnet/issues/1366#issuecomment-821842990
564
567
  # This will be raised each time poll detects a non-existing topic. When auto creation is
@@ -2,11 +2,28 @@
2
2
 
3
3
  module Karafka
4
4
  # Allows to start and stop Karafka as part of a different process
5
+ # Following limitations and restrictions apply:
6
+ #
7
+ # - `#start` cannot be called from a trap context - non blocking
8
+ # - `#quiet` - can be called from a trap context - non blocking
9
+ # - `#stop` - can be called from a trap context - blocking
5
10
  module Embedded
6
11
  class << self
12
+ # Lock for ensuring we do not control embedding in parallel
13
+ MUTEX = Mutex.new
14
+
15
+ private_constant :MUTEX
16
+
7
17
  # Starts Karafka without supervision and without ownership of signals in a background thread
8
18
  # so it won't interrupt other things running
9
19
  def start
20
+ MUTEX.synchronize do
21
+ # Prevent from double-starting
22
+ return if @started
23
+
24
+ @started = true
25
+ end
26
+
10
27
  Thread.new do
11
28
  Thread.current.name = 'karafka.embedded'
12
29
 
@@ -19,9 +36,32 @@ module Karafka
19
36
  #
20
37
  # @note This method is blocking because we want to wait until Karafka is stopped with final
21
38
  # process shutdown
39
+ #
40
+ # @note This method **is** safe to run from a trap context.
22
41
  def stop
23
- # Stop needs to be blocking to wait for all the things to finalize
24
- Karafka::Server.stop
42
+ # Prevent from double stopping
43
+ unless @stopping
44
+ Thread.new do
45
+ Thread.current.name = 'karafka.embedded.stopping'
46
+
47
+ stop = false
48
+
49
+ # We spawn a new thread because `#stop` may be called from a trap context
50
+ MUTEX.synchronize do
51
+ break if @stopping
52
+
53
+ @stopping = true
54
+ stop = true
55
+ end
56
+
57
+ next unless stop
58
+
59
+ Karafka::Server.stop
60
+ end
61
+ end
62
+
63
+ # Since we want to have this blocking, we wait for the background thread
64
+ sleep(0.1) until Karafka::App.terminated?
25
65
  end
26
66
 
27
67
  # Quiets Karafka upon any event
@@ -29,6 +69,8 @@ module Karafka
29
69
  # @note This method is not blocking and will not wait for Karafka to fully quiet.
30
70
  # It will trigger the quiet procedure but won't wait.
31
71
  #
72
+ # @note This method **can** be called from a trap context.
73
+ #
32
74
  # @note Please keep in mind you need to `#stop` to actually stop the server anyhow.
33
75
  def quiet
34
76
  Karafka::Server.quiet
@@ -22,7 +22,16 @@ module Karafka
22
22
  def included(base)
23
23
  base.extend ::Forwardable
24
24
 
25
- base.def_delegators :@thread, :join, :terminate, :alive?, :name
25
+ base.def_delegators :@thread, :join, :terminate, :name
26
+ end
27
+ end
28
+
29
+ # @return [Boolean] true if thread is present and is running, false otherwise
30
+ def alive?
31
+ MUTEX.synchronize do
32
+ return false unless @thread
33
+
34
+ @thread.alive?
26
35
  end
27
36
  end
28
37
 
@@ -41,7 +41,7 @@ module Karafka
41
41
  return unless log_polling?
42
42
 
43
43
  listener = event[:caller]
44
- time = event[:time]
44
+ time = event[:time].round(2)
45
45
  messages_count = event[:messages_buffer].size
46
46
 
47
47
  message = "[#{listener.id}] Polled #{messages_count} messages in #{time}ms"
@@ -69,14 +69,14 @@ module Karafka
69
69
  # @param event [Karafka::Core::Monitoring::Event] event details including payload
70
70
  def on_worker_processed(event)
71
71
  job = event[:job]
72
- time = event[:time]
72
+ time = event[:time].round(2)
73
73
  job_type = job.class.to_s.split('::').last
74
74
  consumer = job.executor.topic.consumer
75
75
  topic = job.executor.topic.name
76
76
  partition = job.executor.partition
77
77
  info <<~MSG.tr("\n", ' ').strip!
78
78
  [#{job.id}] #{job_type} job for #{consumer}
79
- on #{topic}/#{partition} finished in #{time}ms
79
+ on #{topic}/#{partition} finished in #{time} ms
80
80
  MSG
81
81
  end
82
82
 
@@ -306,7 +306,24 @@ module Karafka
306
306
  fatal "Runner crashed due to an error: #{error}"
307
307
  fatal details
308
308
  when 'app.stopping.error'
309
- error 'Forceful Karafka server stop'
309
+ # Counts number of workers and listeners that were still active when forcing the
310
+ # shutdown. Please note, that unless all listeners are closed, workers will not finalize
311
+ # their operations as well.
312
+ # We need to check if listeners and workers are assigned as during super early stages of
313
+ # boot they are not.
314
+ listeners = Server.listeners ? Server.listeners.count(&:active?) : 0
315
+ workers = Server.workers ? Server.workers.count(&:alive?) : 0
316
+
317
+ message = <<~MSG.tr("\n", ' ').strip!
318
+ Forceful Karafka server stop with:
319
+ #{workers} active workers and
320
+ #{listeners} active listeners
321
+ MSG
322
+
323
+ error message
324
+ when 'app.forceful_stopping.error'
325
+ error "Forceful shutdown error occurred: #{error}"
326
+ error details
310
327
  when 'librdkafka.error'
311
328
  error "librdkafka internal error occurred: #{error}"
312
329
  error details
@@ -216,6 +216,10 @@ module Karafka
216
216
  next unless multi_part_sgs_families.include?(sg_listener.subscription_group.name)
217
217
  # Skip already active connections
218
218
  next unless sg_listener.pending? || sg_listener.stopped?
219
+ # Ensure that the listener thread under which we operate is already stopped and
220
+ # is not dangling. While not likely to happen, this may protect against a
221
+ # case where a shutdown critical crash would case a restart of the same listener
222
+ next if sg_listener.alive?
219
223
 
220
224
  touch(sg_listener.subscription_group.id)
221
225
  sg_listener.start!
@@ -89,6 +89,23 @@ module Karafka
89
89
  applied.map(&:cursor).compact.min_by(&:offset)
90
90
  end
91
91
 
92
+ # @return [Boolean] did any of the filters requested offset storage during filter
93
+ # application
94
+ def mark_as_consumed?
95
+ # We can manage filtering offset only when user wanted that and there is a cursor
96
+ # to use
97
+ applied.any?(&:mark_as_consumed?) && cursor
98
+ end
99
+
100
+ # @return [Symbol] `:mark_as_consumed` or `:mark_as_consumed!`
101
+ def marking_method
102
+ candidates = applied.map(&:marking_method)
103
+
104
+ return :mark_as_consumed! if candidates.include?(:mark_as_consumed!)
105
+
106
+ :mark_as_consumed
107
+ end
108
+
92
109
  private
93
110
 
94
111
  # @return [Boolean] is filtering active
@@ -54,6 +54,18 @@ module Karafka
54
54
  def timeout
55
55
  0
56
56
  end
57
+
58
+ # @return [Boolean] should we use the cursor value to mark as consumed. If any of the
59
+ # filters returns true, we return lowers applicable cursor value (if any)
60
+ def mark_as_consumed?
61
+ false
62
+ end
63
+
64
+ # @return [Symbol] `:mark_as_consumed` or `:mark_as_consumed!`. Applicable only if
65
+ # marking is requested
66
+ def marking_method
67
+ :mark_as_consumed
68
+ end
57
69
  end
58
70
  end
59
71
  end
@@ -33,7 +33,7 @@ module Karafka
33
33
 
34
34
  # Time on message is in seconds with ms precision, so we need to convert the ttl that
35
35
  # is in ms to this format
36
- border = ::Time.now.utc - @delay / 1_000.to_f
36
+ border = ::Time.now.utc - @delay / 1_000.0
37
37
 
38
38
  messages.delete_if do |message|
39
39
  too_young = message.timestamp > border
@@ -52,7 +52,7 @@ module Karafka
52
52
  def timeout
53
53
  return 0 unless @cursor
54
54
 
55
- timeout = (@delay / 1_000.to_f) - (::Time.now.utc - @cursor.timestamp)
55
+ timeout = (@delay / 1_000.0) - (::Time.now.utc - @cursor.timestamp)
56
56
 
57
57
  timeout <= 0 ? 0 : timeout * 1_000
58
58
  end
@@ -154,11 +154,12 @@ module Karafka
154
154
  topic: topic.dead_letter_queue.topic,
155
155
  key: original_partition,
156
156
  payload: skippable_message.raw_payload,
157
- headers: skippable_message.headers.merge(
157
+ headers: skippable_message.raw_headers.merge(
158
158
  'original_topic' => topic.name,
159
159
  'original_partition' => original_partition,
160
160
  'original_offset' => skippable_message.offset.to_s,
161
161
  'original_consumer_group' => topic.consumer_group.id,
162
+ 'original_key' => skippable_message.raw_key.to_s,
162
163
  'original_attempts' => attempt.to_s
163
164
  )
164
165
  }
@@ -66,6 +66,19 @@ module Karafka
66
66
  # and this should not happen
67
67
  throttle_timeout = filter.timeout
68
68
 
69
+ # If user requested marking when applying filter, we mark. We may be in the user
70
+ # flow but even then this is not a problem. Older offsets will be ignored since
71
+ # we do not force the offset update (expected) and newer are on the user to control.
72
+ # This can be primarily used when filtering large quantities of data to mark on the
73
+ # idle runs, so lag reporting is aware that those messages were not consumed but also
74
+ # are no longer relevant
75
+ if filter.mark_as_consumed?
76
+ send(
77
+ filter.marking_method,
78
+ filter.cursor
79
+ )
80
+ end
81
+
69
82
  case filter.action
70
83
  when :skip
71
84
  nil
@@ -3,6 +3,12 @@
3
3
  module Karafka
4
4
  # Karafka consuming server class
5
5
  class Server
6
+ # How long should we wait on the listeners forceful shutdown when they are stuck beyond the
7
+ # shutdown timeout before forcing a bypass
8
+ FORCEFUL_SHUTDOWN_WAIT = 5
9
+
10
+ private_constant :FORCEFUL_SHUTDOWN_WAIT
11
+
6
12
  class << self
7
13
  # Set of consuming threads. Each consumer thread contains a single consumer
8
14
  attr_accessor :listeners
@@ -105,9 +111,23 @@ module Karafka
105
111
  # We're done waiting, lets kill them!
106
112
  workers.each(&:terminate)
107
113
  listeners.active.each(&:terminate)
114
+
108
115
  # We always need to shutdown clients to make sure we do not force the GC to close consumer.
109
116
  # This can cause memory leaks and crashes.
110
- listeners.each(&:shutdown)
117
+ # We run it in a separate thread in case this would hang and we ignore it after the time
118
+ # we assigned to it and force shutdown as we prefer to stop the process rather than wait
119
+ # indefinitely even with risk of VM crash as this is a last resort.
120
+ Thread.new do
121
+ listeners.each(&:shutdown)
122
+ rescue StandardError => e
123
+ # If anything wrong happened during shutdown, we also want to record it
124
+ Karafka.monitor.instrument(
125
+ 'error.occurred',
126
+ caller: self,
127
+ error: e,
128
+ type: 'app.forceful_stopping.error'
129
+ )
130
+ end.join(FORCEFUL_SHUTDOWN_WAIT)
111
131
 
112
132
  # We also do not forcefully terminate everything when running in the embedded mode,
113
133
  # otherwise we would overwrite the shutdown process of the process that started Karafka
@@ -52,7 +52,9 @@ module Karafka
52
52
  fetch.wait.max.ms
53
53
  group.id
54
54
  group.instance.id
55
+ group.protocol
55
56
  group.protocol.type
57
+ group.remote.assignor
56
58
  heartbeat.interval.ms
57
59
  interceptors
58
60
  internal.termination.signal
@@ -3,5 +3,5 @@
3
3
  # Main module namespace
4
4
  module Karafka
5
5
  # Current Karafka version
6
- VERSION = '2.4.3'
6
+ VERSION = '2.4.4'
7
7
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: karafka
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.3
4
+ version: 2.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -35,7 +35,7 @@ cert_chain:
35
35
  AnG1dJU+yL2BK7vaVytLTstJME5mepSZ46qqIJXMuWob/YPDmVaBF39TDSG9e34s
36
36
  msG3BiCqgOgHAnL23+CN3Rt8MsuRfEtoTKpJVcCfoEoNHOkc
37
37
  -----END CERTIFICATE-----
38
- date: 2024-06-12 00:00:00.000000000 Z
38
+ date: 2024-07-04 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: base64
@@ -57,7 +57,7 @@ dependencies:
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 2.4.0
60
+ version: 2.4.3
61
61
  - - "<"
62
62
  - !ruby/object:Gem::Version
63
63
  version: 2.5.0
@@ -67,7 +67,7 @@ dependencies:
67
67
  requirements:
68
68
  - - ">="
69
69
  - !ruby/object:Gem::Version
70
- version: 2.4.0
70
+ version: 2.4.3
71
71
  - - "<"
72
72
  - !ruby/object:Gem::Version
73
73
  version: 2.5.0
metadata.gz.sig CHANGED
Binary file