karafka 2.0.13 → 2.0.14

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: 0a38f7b176a40f09dc07c58b8abc8b13863f66fc2f3bff4d61fcd889c0bbb213
4
- data.tar.gz: 9962f21cb52e566843e4b6994da8ac182752523a937cffa18bd735eefadc409f
3
+ metadata.gz: 5d21c47bcabdc50e520f7eb81f17fd31f00916a902aba6e596a2a70e1586a927
4
+ data.tar.gz: eff93034c3d6275d067c5b29b1b7b8d7ad2fb8cca73b08bd64398917338077af
5
5
  SHA512:
6
- metadata.gz: 5b722f8b0baced05f35e907dadb523c129fe12f64066f5efe9bd81fdc0634ccfb824d463baa8f34b7918410c3f6e8a451ceaf5645bb08a8adeaaccff99ec29f1
7
- data.tar.gz: d2649dda560f9c171f804da107df33c1a7fdf81c21bb92d33b7b967aee950a042868c566b63fb32cd495d870e4164246f8ee15aca7c169c66fb82f0994b321ff
6
+ metadata.gz: adc869f03b9f3774f9c5f4351980be5f12f36606242da7062f8abe2886d85fb8576fa10c8f2615f4e0e30a3329abd27b943ae4bbcfe842607e32d26b988ef58a
7
+ data.tar.gz: a7412443e62cb84dcba452aedd230769860c1a8ecb84ede8c34d20b47995117fae22cb60598190436347f50f38811b0a05d55e4219b4b41c3dc382a0f5117511
checksums.yaml.gz.sig CHANGED
Binary file
@@ -18,7 +18,7 @@ jobs:
18
18
  strategy:
19
19
  fail-fast: false
20
20
  steps:
21
- - uses: actions/checkout@v2
21
+ - uses: actions/checkout@v3
22
22
  with:
23
23
  fetch-depth: 0
24
24
 
@@ -39,7 +39,7 @@ jobs:
39
39
  strategy:
40
40
  fail-fast: false
41
41
  steps:
42
- - uses: actions/checkout@v2
42
+ - uses: actions/checkout@v3
43
43
  with:
44
44
  fetch-depth: 0
45
45
  - name: Run Coditsu
@@ -64,7 +64,7 @@ jobs:
64
64
  - ruby: '3.1'
65
65
  coverage: 'true'
66
66
  steps:
67
- - uses: actions/checkout@v2
67
+ - uses: actions/checkout@v3
68
68
  - name: Install package dependencies
69
69
  run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
70
70
 
@@ -97,7 +97,7 @@ jobs:
97
97
  - ruby: '3.1'
98
98
  coverage: 'true'
99
99
  steps:
100
- - uses: actions/checkout@v2
100
+ - uses: actions/checkout@v3
101
101
  - name: Install package dependencies
102
102
  run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS"
103
103
 
data/.rspec CHANGED
@@ -1 +1,3 @@
1
1
  --require spec_helper
2
+ # Integration specs run with their one framework, not via RSpec
3
+ --exclude-pattern "spec/integrations/**/*_spec.rb"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Karafka framework changelog
2
2
 
3
+ ## 2.0.14 (2022-10-16)
4
+ - Prevent consecutive stop signals from starting multiple supervision shutdowns.
5
+ - Provide `Karafka::Embedded` to simplify the start/stop process when running Karafka from within other process (Puma, Sidekiq, etc).
6
+ - Fix a race condition when un-pausing a long-running-job exactly upon listener resuming would crash the listener loop (#1072).
7
+
3
8
  ## 2.0.13 (2022-10-14)
4
9
  - Early exit upon attempts to commit current or earlier offset twice.
5
10
  - Add more integration specs covering edge cases.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- karafka (2.0.13)
4
+ karafka (2.0.14)
5
5
  karafka-core (>= 2.0.2, < 3.0.0)
6
6
  rdkafka (>= 0.12)
7
7
  thor (>= 0.20)
data/bin/integrations CHANGED
@@ -24,7 +24,7 @@ ROOT_PATH = Pathname.new(File.expand_path(File.join(File.dirname(__FILE__), '../
24
24
  # When the value is high, there's a problem with thread allocation on Github CI, tht is why
25
25
  # we limit it. Locally we can run a lot of those, as many of them have sleeps and do not use a lot
26
26
  # of CPU
27
- CONCURRENCY = ENV.key?('CI') ? 3 : Etc.nprocessors * 2
27
+ CONCURRENCY = ENV.key?('CI') ? 4 : Etc.nprocessors * 2
28
28
 
29
29
  # How may bytes do we want to keep from the stdout in the buffer for when we need to print it
30
30
  MAX_BUFFER_OUTPUT = 51_200
@@ -39,10 +39,10 @@ class Scenario
39
39
  # This includes exactly those
40
40
  EXIT_CODES = {
41
41
  default: [0],
42
- 'consumption/worker_critical_error_behaviour.rb' => [0, 2].freeze,
43
- 'shutdown/on_hanging_jobs_and_a_shutdown.rb' => [2].freeze,
44
- 'shutdown/on_hanging_on_shutdown_job_and_a_shutdown.rb' => [2].freeze,
45
- 'shutdown/on_hanging_listener_and_shutdown.rb' => [2].freeze
42
+ 'consumption/worker_critical_error_behaviour_spec.rb' => [0, 2].freeze,
43
+ 'shutdown/on_hanging_jobs_and_a_shutdown_spec.rb' => [2].freeze,
44
+ 'shutdown/on_hanging_on_shutdown_job_and_a_shutdown_spec.rb' => [2].freeze,
45
+ 'shutdown/on_hanging_listener_and_shutdown_spec.rb' => [2].freeze
46
46
  }.freeze
47
47
 
48
48
  private_constant :MAX_RUN_TIME, :EXIT_CODES
@@ -202,7 +202,7 @@ class Scenario
202
202
  end
203
203
 
204
204
  # Load all the specs
205
- specs = Dir[ROOT_PATH.join('spec/integrations/**/*.rb')]
205
+ specs = Dir[ROOT_PATH.join('spec/integrations/**/*_spec.rb')]
206
206
 
207
207
  # If filters is provided, apply
208
208
  # Allows to provide several filters one after another and applies all of them
@@ -210,6 +210,7 @@ ARGV.each do |filter|
210
210
  specs.delete_if { |name| !name.include?(filter) }
211
211
  end
212
212
 
213
+
213
214
  raise ArgumentError, "No integration specs with filters: #{ARGV.join(', ')}" if specs.empty?
214
215
 
215
216
  # Randomize order
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Karafka
4
+ # Allows to start and stop Karafka as part of a different process
5
+ module Embedded
6
+ class << self
7
+ # Starts Karafka without supervision and without ownership of signals in a background thread
8
+ # so it won't interrupt other things running
9
+ def start
10
+ Thread.new { Karafka::Server.start }
11
+ end
12
+
13
+ # Stops Karafka upon any event
14
+ #
15
+ # @note This method is blocking because we want to wait until Karafka is stopped with final
16
+ # process shutdown
17
+ def stop
18
+ # Stop needs to be blocking to wait for all the things to finalize
19
+ Karafka::Server.stop
20
+ end
21
+ end
22
+ end
23
+ end
@@ -29,12 +29,19 @@ module Karafka
29
29
  # Creates an instance of process and creates empty hash for callbacks
30
30
  def initialize
31
31
  @callbacks = Hash.new { |hsh, key| hsh[key] = [] }
32
+ @supervised = false
32
33
  end
33
34
 
34
35
  # Method catches all HANDLED_SIGNALS and performs appropriate callbacks (if defined)
35
36
  # @note If there are no callbacks, this method will just ignore a given signal that was sent
36
37
  def supervise
37
38
  HANDLED_SIGNALS.each { |signal| trap_signal(signal) }
39
+ @supervised = true
40
+ end
41
+
42
+ # Is the current process supervised and are trap signals installed
43
+ def supervised?
44
+ @supervised
38
45
  end
39
46
 
40
47
  private
@@ -31,6 +31,7 @@ module Karafka
31
31
  process.on_sigint { Thread.new { stop } }
32
32
  process.on_sigquit { Thread.new { stop } }
33
33
  process.on_sigterm { Thread.new { stop } }
34
+ process.supervise
34
35
 
35
36
  # Start is blocking until stop is called and when we stop, it will wait until
36
37
  # all of the things are ready to stop
@@ -61,7 +62,6 @@ module Karafka
61
62
  # @note We don't need to sleep because Karafka::Fetcher is locking and waiting to
62
63
  # finish loop (and it won't happen until we explicitly want to stop)
63
64
  def start
64
- process.supervise
65
65
  Karafka::App.run!
66
66
  Karafka::Runner.new.call
67
67
  end
@@ -73,6 +73,9 @@ module Karafka
73
73
  # lock them forever. If you need to run Karafka shutdown from within workers threads,
74
74
  # please start a separate thread to do so.
75
75
  def stop
76
+ # Initialize the stopping process only if Karafka was running
77
+ return unless Karafka::App.running?
78
+
76
79
  Karafka::App.stop!
77
80
 
78
81
  timeout = Karafka::App.config.shutdown_timeout
@@ -110,8 +113,12 @@ module Karafka
110
113
 
111
114
  Karafka::App.producer.close
112
115
 
116
+ # We also do not forcefully terminate everything when running in the embedded mode,
117
+ # otherwise we would overwrite the shutdown process of the process that started Karafka
118
+ return unless process.supervised?
119
+
113
120
  # exit! is not within the instrumentation as it would not trigger due to exit
114
- Kernel.exit! FORCEFUL_EXIT_CODE
121
+ Kernel.exit!(FORCEFUL_EXIT_CODE)
115
122
  ensure
116
123
  Karafka::App.stopped!
117
124
  end
@@ -25,6 +25,9 @@ module Karafka
25
25
  end
26
26
 
27
27
  define_method transition do
28
+ # Do nothing if the state change would change nothing (same state)
29
+ return if @status == state
30
+
28
31
  @status = state
29
32
 
30
33
  # Skip on creation (initializing)
@@ -3,6 +3,12 @@
3
3
  module Karafka
4
4
  module TimeTrackers
5
5
  # Handles Kafka topic partition pausing and resuming with exponential back-offs.
6
+ # Since expiring and pausing can happen from both consumer and listener, this needs to be
7
+ # thread-safe.
8
+ #
9
+ # @note We do not have to worry about performance implications of a mutex wrapping most of the
10
+ # code here, as this is not a frequently used tracker. It is active only once per batch in
11
+ # case of long-running-jobs and upon errors.
6
12
  class Pause < Base
7
13
  attr_reader :count
8
14
 
@@ -36,6 +42,7 @@ module Karafka
36
42
  @timeout = timeout
37
43
  @max_timeout = max_timeout
38
44
  @exponential_backoff = exponential_backoff
45
+ @mutex = Mutex.new
39
46
  super()
40
47
  end
41
48
 
@@ -45,35 +52,47 @@ module Karafka
45
52
  # @note Providing this value can be useful when we explicitly want to pause for a certain
46
53
  # period of time, outside of any regular pausing logic
47
54
  def pause(timeout = backoff_interval)
48
- @started_at = now
49
- @ends_at = @started_at + timeout
50
- @count += 1
55
+ @mutex.synchronize do
56
+ @started_at = now
57
+ @ends_at = @started_at + timeout
58
+ @count += 1
59
+ end
51
60
  end
52
61
 
53
62
  # Marks the pause as resumed.
54
63
  def resume
55
- @started_at = nil
56
- @ends_at = nil
64
+ @mutex.synchronize do
65
+ @started_at = nil
66
+ @ends_at = nil
67
+ end
57
68
  end
58
69
 
59
70
  # Expires the pause, so it can be considered expired
60
71
  def expire
61
- @ends_at = nil
72
+ @mutex.synchronize do
73
+ @ends_at = nil
74
+ end
62
75
  end
63
76
 
64
77
  # @return [Boolean] are we paused from processing
65
78
  def paused?
66
- !@started_at.nil?
79
+ @mutex.synchronize do
80
+ !@started_at.nil?
81
+ end
67
82
  end
68
83
 
69
84
  # @return [Boolean] did the pause expire
70
85
  def expired?
71
- @ends_at ? now >= @ends_at : true
86
+ @mutex.synchronize do
87
+ @ends_at ? now >= @ends_at : true
88
+ end
72
89
  end
73
90
 
74
91
  # Resets the pause counter.
75
92
  def reset
76
- @count = 0
93
+ @mutex.synchronize do
94
+ @count = 0
95
+ end
77
96
  end
78
97
 
79
98
  private
@@ -2,7 +2,8 @@
2
2
 
3
3
  module Karafka
4
4
  module TimeTrackers
5
- # Object used to keep track of time we've used running certain operations.
5
+ # Object used to keep track of time we've used running certain operations. Polling is
6
+ # running in a single thread, thus we do not have to worry about this being thread-safe.
6
7
  #
7
8
  # @example Keep track of sleeping and stop after 3 seconds of 0.1 sleep intervals
8
9
  # time_poll = Poll.new(3000)
@@ -3,5 +3,5 @@
3
3
  # Main module namespace
4
4
  module Karafka
5
5
  # Current Karafka version
6
- VERSION = '2.0.13'
6
+ VERSION = '2.0.14'
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.0.13
4
+ version: 2.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Mensfeld
@@ -35,7 +35,7 @@ cert_chain:
35
35
  Qf04B9ceLUaC4fPVEz10FyobjaFoY4i32xRto3XnrzeAgfEe4swLq8bQsR3w/EF3
36
36
  MGU0FeSV2Yj7Xc2x/7BzLK8xQn5l7Yy75iPF+KP3vVmDHnNl
37
37
  -----END CERTIFICATE-----
38
- date: 2022-10-14 00:00:00.000000000 Z
38
+ date: 2022-10-16 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: karafka-core
@@ -188,6 +188,7 @@ files:
188
188
  - lib/karafka/contracts/consumer_group.rb
189
189
  - lib/karafka/contracts/consumer_group_topic.rb
190
190
  - lib/karafka/contracts/server_cli_options.rb
191
+ - lib/karafka/embedded.rb
191
192
  - lib/karafka/env.rb
192
193
  - lib/karafka/errors.rb
193
194
  - lib/karafka/helpers/async.rb
metadata.gz.sig CHANGED
Binary file