karafka 2.4.8 → 2.4.9
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/workflows/ci.yml +0 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile +8 -5
- data/Gemfile.lock +23 -14
- data/bin/integrations +5 -0
- data/certs/cert.pem +26 -0
- data/config/locales/errors.yml +4 -0
- data/config/locales/pro_errors.yml +17 -0
- data/karafka.gemspec +1 -1
- data/lib/karafka/admin.rb +42 -0
- data/lib/karafka/contracts/config.rb +2 -0
- data/lib/karafka/errors.rb +3 -2
- data/lib/karafka/pro/loader.rb +2 -1
- data/lib/karafka/pro/processing/strategies/dlq/default.rb +16 -1
- data/lib/karafka/pro/processing/strategies/dlq/ftr_lrj_mom.rb +5 -1
- data/lib/karafka/pro/processing/strategies/dlq/ftr_mom.rb +17 -1
- data/lib/karafka/pro/processing/strategies/dlq/lrj_mom.rb +17 -1
- data/lib/karafka/pro/processing/strategies/dlq/mom.rb +22 -6
- data/lib/karafka/pro/recurring_tasks/consumer.rb +105 -0
- data/lib/karafka/pro/recurring_tasks/contracts/config.rb +53 -0
- data/lib/karafka/pro/recurring_tasks/contracts/task.rb +41 -0
- data/lib/karafka/pro/recurring_tasks/deserializer.rb +35 -0
- data/lib/karafka/pro/recurring_tasks/dispatcher.rb +87 -0
- data/lib/karafka/pro/recurring_tasks/errors.rb +34 -0
- data/lib/karafka/pro/recurring_tasks/executor.rb +152 -0
- data/lib/karafka/pro/recurring_tasks/listener.rb +38 -0
- data/lib/karafka/pro/recurring_tasks/matcher.rb +38 -0
- data/lib/karafka/pro/recurring_tasks/schedule.rb +63 -0
- data/lib/karafka/pro/recurring_tasks/serializer.rb +113 -0
- data/lib/karafka/pro/recurring_tasks/setup/config.rb +52 -0
- data/lib/karafka/pro/recurring_tasks/task.rb +151 -0
- data/lib/karafka/pro/recurring_tasks.rb +87 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/builder.rb +130 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/config.rb +28 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/contracts/topic.rb +40 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/proxy.rb +27 -0
- data/lib/karafka/pro/routing/features/recurring_tasks/topic.rb +44 -0
- data/lib/karafka/pro/routing/features/recurring_tasks.rb +25 -0
- data/lib/karafka/processing/strategies/dlq.rb +16 -2
- data/lib/karafka/processing/strategies/dlq_mom.rb +25 -6
- data/lib/karafka/processing/worker.rb +11 -1
- data/lib/karafka/railtie.rb +11 -22
- data/lib/karafka/routing/features/dead_letter_queue/config.rb +3 -0
- data/lib/karafka/routing/features/dead_letter_queue/contracts/topic.rb +1 -0
- data/lib/karafka/routing/features/dead_letter_queue/topic.rb +7 -2
- data/lib/karafka/routing/features/eofed/contracts/topic.rb +12 -0
- data/lib/karafka/routing/topic.rb +14 -0
- data/lib/karafka/setup/config.rb +3 -0
- data/lib/karafka/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +44 -24
- metadata.gz.sig +0 -0
- 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
|
-
|
79
|
+
if mark_after_dispatch?
|
80
|
+
mark_dispatched_to_dlq(skippable_message)
|
80
81
|
|
81
|
-
|
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
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
-
|
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
|
|