karafka 2.4.8 → 2.4.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.github/workflows/ci.yml +0 -1
  4. data/CHANGELOG.md +8 -0
  5. data/Gemfile +8 -5
  6. data/Gemfile.lock +23 -14
  7. data/bin/integrations +5 -0
  8. data/certs/cert.pem +26 -0
  9. data/config/locales/errors.yml +4 -0
  10. data/config/locales/pro_errors.yml +17 -0
  11. data/karafka.gemspec +1 -1
  12. data/lib/karafka/admin.rb +42 -0
  13. data/lib/karafka/contracts/config.rb +2 -0
  14. data/lib/karafka/errors.rb +3 -2
  15. data/lib/karafka/pro/loader.rb +2 -1
  16. data/lib/karafka/pro/processing/strategies/dlq/default.rb +16 -1
  17. data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +5 -1
  18. data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +17 -1
  19. data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +17 -1
  20. data/lib/karafka/pro/processing/strategies/dlq/mom.rb +22 -6
  21. data/lib/karafka/pro/recurring_tasks/consumer.rb +105 -0
  22. data/lib/karafka/pro/recurring_tasks/contracts/config.rb +53 -0
  23. data/lib/karafka/pro/recurring_tasks/contracts/task.rb +41 -0
  24. data/lib/karafka/pro/recurring_tasks/deserializer.rb +35 -0
  25. data/lib/karafka/pro/recurring_tasks/dispatcher.rb +87 -0
  26. data/lib/karafka/pro/recurring_tasks/errors.rb +34 -0
  27. data/lib/karafka/pro/recurring_tasks/executor.rb +152 -0
  28. data/lib/karafka/pro/recurring_tasks/listener.rb +38 -0
  29. data/lib/karafka/pro/recurring_tasks/matcher.rb +38 -0
  30. data/lib/karafka/pro/recurring_tasks/schedule.rb +63 -0
  31. data/lib/karafka/pro/recurring_tasks/serializer.rb +113 -0
  32. data/lib/karafka/pro/recurring_tasks/setup/config.rb +52 -0
  33. data/lib/karafka/pro/recurring_tasks/task.rb +151 -0
  34. data/lib/karafka/pro/recurring_tasks.rb +87 -0
  35. data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +130 -0
  36. data/lib/karafka/pro/routing/features/recurring_tasks/config.rb +28 -0
  37. data/lib/karafka/pro/routing/features/recurring_tasks/contracts/topic.rb +40 -0
  38. data/lib/karafka/pro/routing/features/recurring_tasks/proxy.rb +27 -0
  39. data/lib/karafka/pro/routing/features/recurring_tasks/topic.rb +44 -0
  40. data/lib/karafka/pro/routing/features/recurring_tasks.rb +25 -0
  41. data/lib/karafka/processing/strategies/dlq.rb +16 -2
  42. data/lib/karafka/processing/strategies/dlq_mom.rb +25 -6
  43. data/lib/karafka/processing/worker.rb +11 -1
  44. data/lib/karafka/railtie.rb +11 -22
  45. data/lib/karafka/routing/features/dead_letter_queue/config.rb +3 -0
  46. data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
  47. data/lib/karafka/routing/features/dead_letter_queue/topic.rb +7 -2
  48. data/lib/karafka/routing/features/eofed/contracts/topic.rb +12 -0
  49. data/lib/karafka/routing/topic.rb +14 -0
  50. data/lib/karafka/setup/config.rb +3 -0
  51. data/lib/karafka/version.rb +1 -1
  52. data.tar.gz.sig +0 -0
  53. metadata +44 -24
  54. metadata.gz.sig +0 -0
  55. data/certs/cert_chain.pem +0 -26
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module RecurringTasks
17
+ # Represents a single recurring task that can be executed when the time comes.
18
+ # Tasks should be lightweight. Anything heavy should be executed by scheduling appropriate
19
+ # jobs here.
20
+ class Task
21
+ include Helpers::ConfigImporter.new(
22
+ monitor: %i[monitor]
23
+ )
24
+
25
+ # @return [String] this task id
26
+ attr_reader :id
27
+
28
+ # @return [Fugit::Cron] cron from parsing the raw cron expression
29
+ attr_reader :cron
30
+
31
+ # Allows for update of previous time when restoring the materialized state
32
+ attr_accessor :previous_time
33
+
34
+ # @param id [String] unique id. If re-used between versions, will replace older occurrence.
35
+ # @param cron [String] cron expression matching this task expected execution times.
36
+ # @param previous_time [Integer, Time] previous time this task was executed. 0 if never.
37
+ # @param enabled [Boolean] should this task be enabled. Users may disable given task
38
+ # temporarily, this is why we need to know that.
39
+ # @param block [Proc] code to execute.
40
+ def initialize(id:, cron:, previous_time: 0, enabled: true, &block)
41
+ @id = id
42
+ @cron = ::Fugit::Cron.do_parse(cron)
43
+ @previous_time = previous_time
44
+ @start_time = Time.now
45
+ @executable = block
46
+ @enabled = enabled
47
+ @trigger = false
48
+ @changed = false
49
+ end
50
+
51
+ # @return [Boolean] true if anything in the task has changed and we should persist it
52
+ def changed?
53
+ @changed
54
+ end
55
+
56
+ # Disables this task execution indefinitely
57
+ def disable
58
+ touch
59
+ @enabled = false
60
+ end
61
+
62
+ # Enables back this task
63
+ def enable
64
+ touch
65
+ @enabled = true
66
+ end
67
+
68
+ # @return [Boolean] is this an executable task
69
+ def enabled?
70
+ @enabled
71
+ end
72
+
73
+ # Triggers the execution of this task at the earliest opportunity
74
+ def trigger
75
+ touch
76
+ @trigger = true
77
+ end
78
+
79
+ # @return [EtOrbi::EoTime] next execution time
80
+ def next_time
81
+ @cron.next_time(@previous_time.to_i.zero? ? @start_time : @previous_time)
82
+ end
83
+
84
+ # @return [Boolean] should we execute this task at this moment in time
85
+ def call?
86
+ return true if @trigger
87
+ return false unless enabled?
88
+
89
+ # Ensure the job is only due if current_time is strictly after the next_time
90
+ # Please note that we can only compare eorbi against time and not the other way around
91
+ next_time <= Time.now
92
+ end
93
+
94
+ # Executes the given task and publishes appropriate notification bus events.
95
+ def call
96
+ monitor.instrument(
97
+ 'recurring_tasks.task.executed',
98
+ task: self
99
+ ) do
100
+ # We check for presence of the `@executable` because user can define cron schedule
101
+ # without the code block
102
+ return unless @executable
103
+
104
+ execute
105
+ end
106
+ rescue StandardError => e
107
+ monitor.instrument(
108
+ 'error.occurred',
109
+ caller: self,
110
+ error: e,
111
+ task: self,
112
+ type: 'recurring_tasks.task.execute.error'
113
+ )
114
+ ensure
115
+ @trigger = false
116
+ @previous_time = Time.now
117
+ end
118
+
119
+ # Runs the executable block without any instrumentation or error handling. Useful for
120
+ # debugging and testing
121
+ def execute
122
+ @executable.call
123
+ end
124
+
125
+ # Removes the changes indicator flag
126
+ def clear
127
+ @changed = false
128
+ end
129
+
130
+ # @return [Hash] hash version of the task. Used for contract validation.
131
+ def to_h
132
+ {
133
+ id: id,
134
+ cron: @cron.original,
135
+ previous_time: previous_time,
136
+ next_time: next_time,
137
+ changed: changed?,
138
+ enabled: enabled?
139
+ }
140
+ end
141
+
142
+ private
143
+
144
+ # Marks the task as changed
145
+ def touch
146
+ @changed = true
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ # Recurring tasks functionality
17
+ module RecurringTasks
18
+ class << self
19
+ # @return [Schedule, nil] current defined schedule or nil if not defined
20
+ def schedule
21
+ @schedule || define('0.0.0') {}
22
+ end
23
+
24
+ # Simplified API for schedules definitions and validates the tasks data
25
+ #
26
+ # @param version [String]
27
+ # @param block [Proc]
28
+ #
29
+ # @example
30
+ # Karafka::Pro::RecurringTasks.define('1.0.1') do
31
+ # schedule(id: 'mailer', cron: '* * * * *') do
32
+ # MailingJob.perform_async
33
+ # end
34
+ # end
35
+ def define(version = '1.0.0', &block)
36
+ @schedule = Schedule.new(version: version)
37
+ @schedule.instance_exec(&block)
38
+
39
+ @schedule.each do |task|
40
+ Contracts::Task.new.validate!(task.to_h)
41
+ end
42
+
43
+ @schedule
44
+ end
45
+
46
+ # Defines nice command methods to dispatch cron requests
47
+ Executor::COMMANDS.each do |command_name|
48
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
49
+ # @param task_id [String] task to which we want to dispatch command or '*' if to all
50
+ def #{command_name}(task_id)
51
+ Dispatcher.command('#{command_name}', task_id)
52
+ end
53
+ RUBY
54
+ end
55
+
56
+ # Below are private APIs
57
+
58
+ # Sets up additional config scope, validations and other things
59
+ #
60
+ # @param config [Karafka::Core::Configurable::Node] root node config
61
+ def pre_setup(config)
62
+ # Expand the config with this feature specific stuff
63
+ config.instance_eval do
64
+ setting(:recurring_tasks, default: Setup::Config.config)
65
+ end
66
+ end
67
+
68
+ # @param config [Karafka::Core::Configurable::Node] root node config
69
+ def post_setup(config)
70
+ RecurringTasks::Contracts::Config.new.validate!(config.to_h)
71
+
72
+ # Published after task is successfully executed
73
+ Karafka.monitor.notifications_bus.register_event('recurring_tasks.task.executed')
74
+
75
+ # Initialize empty dummy schedule, so we always have one and so we do not have to
76
+ # deal with a case where there is no schedule
77
+ RecurringTasks.schedule
78
+
79
+ # User can disable logging of executions, in which case we don't track them
80
+ return unless Karafka::App.config.recurring_tasks.logging
81
+
82
+ Karafka.monitor.subscribe(Listener.new)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Routing extensions for recurring tasks
20
+ module Builder
21
+ # Enabled recurring tasks operations and adds needed topics and other stuff.
22
+ #
23
+ # @param active [Boolean] should recurring tasks be active. We use a boolean flag to
24
+ # have API consistency in the system, so it matches other routing related APIs.
25
+ # @param block [Proc] optional reconfiguration of the tasks topic definitions.
26
+ # @note Since we cannot provide two blocks, reconfiguration of logs topic can be only
27
+ # done if user explicitly redefines it in the routing.
28
+ def recurring_tasks(active = false, &block)
29
+ return unless active
30
+
31
+ # We only require zlib when we decide to run recurring tasks because it is not needed
32
+ # otherwise.
33
+ require 'zlib'
34
+ ensure_fugit_availability!
35
+
36
+ tasks_cfg = App.config.recurring_tasks
37
+ topics_cfg = tasks_cfg.topics
38
+
39
+ consumer_group tasks_cfg.group_id do
40
+ # Registers the primary topic that we use to control schedules execution. This is
41
+ # the one that we use to trigger recurring tasks.
42
+ topic(topics_cfg.schedules) do
43
+ consumer tasks_cfg.consumer_class
44
+ deserializer tasks_cfg.deserializer
45
+ # Because the topic method name as well as builder proxy method name is the same
46
+ # we need to reference it via target directly
47
+ target.recurring_tasks(true)
48
+
49
+ # We manage offsets directly because messages can have both schedules and
50
+ # commands and we need to apply them only when we need to
51
+ manual_offset_management(true)
52
+
53
+ # We use multi-batch operations and in-memory state for schedules. This needs to
54
+ # always operate without re-creation.
55
+ consumer_persistence(true)
56
+
57
+ # This needs to be enabled for the eof to work correctly
58
+ kafka('enable.partition.eof': true, inherit: true)
59
+ eofed(true)
60
+
61
+ # Favour latency. This is a low traffic topic that only accepts user initiated
62
+ # low-frequency commands
63
+ max_messages(1)
64
+ # Since we always react on the received message, we can block for longer periods
65
+ # of time
66
+ max_wait_time(10_000)
67
+
68
+ # Since the execution of particular tasks is isolated and guarded, it should not
69
+ # leak. This means, that this is to handle errors like schedule version
70
+ # incompatibility and other errors that will not go away without a redeployment
71
+ pause_timeout(60 * 1_000)
72
+ pause_max_timeout(60 * 1_000)
73
+
74
+ # Keep older data for a day and compact to the last state available
75
+ config(
76
+ 'cleanup.policy': 'compact,delete',
77
+ 'retention.ms': 86_400_000
78
+ )
79
+
80
+ # This is the core of execution. Since we're producers of states, we need a way
81
+ # to tick without having new data
82
+ periodic(
83
+ interval: App.config.recurring_tasks.interval,
84
+ during_pause: false,
85
+ during_retry: false
86
+ )
87
+
88
+ next unless block
89
+
90
+ instance_eval(&block)
91
+ end
92
+
93
+ # This topic is to store logs that we can then inspect either from the admin or via
94
+ # the Web UI
95
+ topic(topics_cfg.logs) do
96
+ active(false)
97
+ deserializer tasks_cfg.deserializer
98
+ target.recurring_tasks(true)
99
+
100
+ # Keep cron logs of executions for a week and after that remove. Week should be
101
+ # enough and should not produce too much data.
102
+ config(
103
+ 'cleanup.policy': 'delete',
104
+ 'retention.ms': 604_800_000
105
+ )
106
+ end
107
+ end
108
+ end
109
+
110
+ # Checks if fugit is present. If not, will try to require it as it might not have
111
+ # been required but is available. If fails, will crash.
112
+ def ensure_fugit_availability!
113
+ return if Object.const_defined?(:Fugit)
114
+
115
+ require 'fugit'
116
+ rescue LoadError
117
+ raise(
118
+ ::Karafka::Errors::DependencyConstraintsError,
119
+ <<~ERROR_MSG
120
+ Failed to require fugit gem.
121
+ Add it to your Gemfile, as it is required for the recurring tasks to work.
122
+ ERROR_MSG
123
+ )
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Recurring tasks configuration
20
+ Config = Struct.new(
21
+ :active,
22
+ keyword_init: true
23
+ ) { alias_method :active?, :active }
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Namespace for recurring tasks contracts
20
+ module Contracts
21
+ # Rules around recurring tasks settings
22
+ class Topic < Karafka::Contracts::Base
23
+ configure do |config|
24
+ config.error_messages = YAML.safe_load(
25
+ File.read(
26
+ File.join(Karafka.gem_root, 'config', 'locales', 'pro_errors.yml')
27
+ )
28
+ ).fetch('en').fetch('validations').fetch('topic')
29
+ end
30
+
31
+ nested(:recurring_tasks) do
32
+ required(:active) { |val| [true, false].include?(val) }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Routing proxy extensions for recurring tasks
20
+ module Proxy
21
+ include Builder
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ class RecurringTasks < Base
19
+ # Topic extensions to be able to check if given topic is a recurring tasks topic
20
+ # Please note, that this applies to both the schedules topics and reports topics
21
+ module Topic
22
+ # @param active [Boolean] should this topic be considered related to recurring tasks
23
+ def recurring_tasks(active = false)
24
+ @recurring_tasks ||= Config.new(active: active)
25
+ end
26
+
27
+ # @return [Boolean] is this an ActiveJob topic
28
+ def recurring_tasks?
29
+ recurring_tasks.active?
30
+ end
31
+
32
+ # @return [Hash] topic with all its native configuration options plus active job
33
+ # namespace settings
34
+ def to_h
35
+ super.merge(
36
+ recurring_tasks: recurring_tasks.to_h
37
+ ).freeze
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This Karafka component is a Pro component under a commercial license.
4
+ # This Karafka component is NOT licensed under LGPL.
5
+ #
6
+ # All of the commercial components are present in the lib/karafka/pro directory of this
7
+ # repository and their usage requires commercial license agreement.
8
+ #
9
+ # Karafka has also commercial-friendly license, commercial support and commercial components.
10
+ #
11
+ # By sending a pull request to the pro components, you are agreeing to transfer the copyright of
12
+ # your code to Maciej Mensfeld.
13
+
14
+ module Karafka
15
+ module Pro
16
+ module Routing
17
+ module Features
18
+ # This feature allows you to run recurring tasks aka cron jobs from within Karafka
19
+ # It uses two special topics for tracking the schedule and reporting executions
20
+ class RecurringTasks < Base
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -76,9 +76,13 @@ module Karafka
76
76
  dispatch_to_dlq(skippable_message)
77
77
 
78
78
  # We mark the broken message as consumed and move on
79
- mark_dispatched_to_dlq(skippable_message)
79
+ if mark_after_dispatch?
80
+ mark_dispatched_to_dlq(skippable_message)
80
81
 
81
- return if revoked?
82
+ return if revoked?
83
+ else
84
+ coordinator.seek_offset = skippable_message.offset + 1
85
+ end
82
86
 
83
87
  # We pause to backoff once just in case.
84
88
  pause(coordinator.seek_offset, nil, false)
@@ -120,6 +124,16 @@ module Karafka
120
124
  )
121
125
  end
122
126
 
127
+ # @return [Boolean] should we mark given message as consumed after dispatch. For default
128
+ # non MOM strategies if user did not explicitly tell us not to, we mark it. Default is
129
+ # `nil`, which means `true` in this case. If user provided alternative value, we go
130
+ # with it.
131
+ def mark_after_dispatch?
132
+ return true if topic.dead_letter_queue.mark_after_dispatch.nil?
133
+
134
+ topic.dead_letter_queue.mark_after_dispatch
135
+ end
136
+
123
137
  # Marks message that went to DLQ (if applicable) based on the requested method
124
138
  # @param skippable_message [Karafka::Messages::Message]
125
139
  def mark_dispatched_to_dlq(skippable_message)
@@ -33,16 +33,35 @@ module Karafka
33
33
 
34
34
  dispatch_to_dlq(skippable_message)
35
35
 
36
- # Save the next offset we want to go with after moving given message to DLQ
37
- # Without this, we would not be able to move forward and we would end up
38
- # in an infinite loop trying to un-pause from the message we've already processed
39
- # Of course, since it's a MoM a rebalance or kill, will move it back as no
40
- # offsets are being committed
41
- coordinator.seek_offset = skippable_message.offset + 1
36
+ # We mark the broken message as consumed and move on
37
+ if mark_after_dispatch?
38
+ mark_dispatched_to_dlq(skippable_message)
39
+
40
+ return if revoked?
41
+ else
42
+ # Save the next offset we want to go with after moving given message to DLQ
43
+ # Without this, we would not be able to move forward and we would end up
44
+ # in an infinite loop trying to un-pause from the message we've already processed
45
+ # Of course, since it's a MoM a rebalance or kill, will move it back as no
46
+ # offsets are being committed
47
+ coordinator.seek_offset = skippable_message.offset + 1
48
+ end
42
49
 
43
50
  pause(coordinator.seek_offset, nil, false)
44
51
  end
45
52
  end
53
+
54
+ # @return [Boolean] should we mark given message as consumed after dispatch. For
55
+ # MOM strategies if user did not explicitly tell us to mark, we do not mark. Default is
56
+ # `nil`, which means `false` in this case. If user provided alternative value, we go
57
+ # with it.
58
+ #
59
+ # @note Please note, this is the opposite behavior than in case of AOM strategies.
60
+ def mark_after_dispatch?
61
+ return false if topic.dead_letter_queue.mark_after_dispatch.nil?
62
+
63
+ topic.dead_letter_queue.mark_after_dispatch
64
+ end
46
65
  end
47
66
  end
48
67
  end
@@ -18,6 +18,9 @@ module Karafka
18
18
  # not user code but need to run after user code base is executed.
19
19
  class Worker
20
20
  include Helpers::Async
21
+ include Helpers::ConfigImporter.new(
22
+ worker_job_call_wrapper: %i[internal processing worker_job_call_wrapper]
23
+ )
21
24
 
22
25
  # @return [String] id of this worker
23
26
  attr_reader :id
@@ -27,6 +30,7 @@ module Karafka
27
30
  def initialize(jobs_queue)
28
31
  @id = SecureRandom.hex(6)
29
32
  @jobs_queue = jobs_queue
33
+ @non_wrapped_flow = worker_job_call_wrapper == false
30
34
  end
31
35
 
32
36
  private
@@ -60,7 +64,13 @@ module Karafka
60
64
  # pass it until done.
61
65
  @jobs_queue.tick(job.group_id) if job.non_blocking?
62
66
 
63
- job.call
67
+ if @non_wrapped_flow
68
+ job.call
69
+ else
70
+ worker_job_call_wrapper.wrap do
71
+ job.call
72
+ end
73
+ end
64
74
 
65
75
  job.after_call
66
76