karafka 2.4.8 → 2.4.10
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/workflows/ci.yml +0 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +17 -0
- data/Gemfile +8 -5
- data/Gemfile.lock +24 -15
- data/bin/integrations +5 -0
- data/certs/cert.pem +26 -0
- data/config/locales/errors.yml +5 -0
- data/config/locales/pro_errors.yml +34 -0
- data/karafka.gemspec +1 -1
- data/lib/karafka/admin.rb +42 -0
- data/lib/karafka/base_consumer.rb +23 -0
- data/lib/karafka/contracts/config.rb +2 -0
- data/lib/karafka/contracts/consumer_group.rb +17 -0
- data/lib/karafka/errors.rb +3 -2
- data/lib/karafka/instrumentation/logger_listener.rb +3 -0
- data/lib/karafka/instrumentation/notifications.rb +3 -0
- data/lib/karafka/instrumentation/vendors/appsignal/client.rb +32 -11
- data/lib/karafka/instrumentation/vendors/appsignal/errors_listener.rb +1 -1
- data/lib/karafka/messages/message.rb +6 -0
- data/lib/karafka/pro/loader.rb +3 -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 +131 -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/pro/routing/features/scheduled_messages/builder.rb +131 -0
- data/lib/karafka/pro/routing/features/scheduled_messages/config.rb +28 -0
- data/lib/karafka/pro/routing/features/scheduled_messages/contracts/topic.rb +40 -0
- data/lib/karafka/pro/routing/features/scheduled_messages/proxy.rb +27 -0
- data/lib/karafka/pro/routing/features/scheduled_messages/topic.rb +44 -0
- data/lib/karafka/pro/routing/features/scheduled_messages.rb +24 -0
- data/lib/karafka/pro/scheduled_messages/consumer.rb +185 -0
- data/lib/karafka/pro/scheduled_messages/contracts/config.rb +56 -0
- data/lib/karafka/pro/scheduled_messages/contracts/message.rb +61 -0
- data/lib/karafka/pro/scheduled_messages/daily_buffer.rb +79 -0
- data/lib/karafka/pro/scheduled_messages/day.rb +45 -0
- data/lib/karafka/pro/scheduled_messages/deserializers/headers.rb +46 -0
- data/lib/karafka/pro/scheduled_messages/deserializers/payload.rb +35 -0
- data/lib/karafka/pro/scheduled_messages/dispatcher.rb +122 -0
- data/lib/karafka/pro/scheduled_messages/errors.rb +28 -0
- data/lib/karafka/pro/scheduled_messages/max_epoch.rb +41 -0
- data/lib/karafka/pro/scheduled_messages/proxy.rb +176 -0
- data/lib/karafka/pro/scheduled_messages/schema_validator.rb +37 -0
- data/lib/karafka/pro/scheduled_messages/serializer.rb +55 -0
- data/lib/karafka/pro/scheduled_messages/setup/config.rb +60 -0
- data/lib/karafka/pro/scheduled_messages/state.rb +62 -0
- data/lib/karafka/pro/scheduled_messages/tracker.rb +64 -0
- data/lib/karafka/pro/scheduled_messages.rb +67 -0
- data/lib/karafka/processing/executor.rb +6 -0
- data/lib/karafka/processing/strategies/default.rb +10 -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 -42
- 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 +68 -25
- metadata.gz.sig +0 -0
- data/certs/cert_chain.pem +0 -26
@@ -0,0 +1,63 @@
|
|
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 the current code-context schedule with defined tasks and their cron execution
|
18
|
+
# details. Single schedule includes all the information about all the tasks that we have
|
19
|
+
# defined and to be executed in a given moment in time.
|
20
|
+
class Schedule
|
21
|
+
# @return [String]
|
22
|
+
attr_reader :version
|
23
|
+
|
24
|
+
# @return [Hash<String, Task>]
|
25
|
+
attr_reader :tasks
|
26
|
+
|
27
|
+
# @param version [String] schedule version. In case of usage of versioning it is used to
|
28
|
+
# ensure, that older still active processes do not intercept the assignment to run older
|
29
|
+
# version of the scheduler. It is important to make sure, that this string is comparable.
|
30
|
+
def initialize(version:)
|
31
|
+
@version = version
|
32
|
+
@tasks = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds task into the tasks accumulator
|
36
|
+
# @param task [Task]
|
37
|
+
# @note In case of multiple tasks with the same id, it will overwrite
|
38
|
+
def <<(task)
|
39
|
+
@tasks[task.id] = task
|
40
|
+
end
|
41
|
+
|
42
|
+
# Iterates over tasks yielding them one after another
|
43
|
+
# @param block [Proc] block that will be executed with each task
|
44
|
+
def each(&block)
|
45
|
+
@tasks.each_value(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param id [String] id of a particular recurring task
|
49
|
+
# @return [Task, nil] task with a given id or nil if not found
|
50
|
+
def find(id)
|
51
|
+
@tasks[id]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Allows us to have a nice DSL for defining schedules
|
55
|
+
# @param args [Array] attributes accepted by the task initializer
|
56
|
+
# @param block [Proc] block to execute
|
57
|
+
def schedule(**args, &block)
|
58
|
+
self << Task.new(**args, &block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,113 @@
|
|
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
|
+
# Converts schedule command and log details into data we can dispatch to Kafka.
|
18
|
+
class Serializer
|
19
|
+
# Current recurring tasks related schema structure
|
20
|
+
SCHEMA_VERSION = '1.0'
|
21
|
+
|
22
|
+
# @param schedule [Karafka::Pro::RecurringTasks::Schedule] schedule to serialize
|
23
|
+
# @return [String] serialized and compressed current schedule data with its tasks and their
|
24
|
+
# current state.
|
25
|
+
def schedule(schedule)
|
26
|
+
tasks = {}
|
27
|
+
|
28
|
+
schedule.each do |task|
|
29
|
+
tasks[task.id] = {
|
30
|
+
id: task.id,
|
31
|
+
cron: task.cron.original,
|
32
|
+
previous_time: task.previous_time.to_i,
|
33
|
+
next_time: task.next_time.to_i,
|
34
|
+
enabled: task.enabled?
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
data = {
|
39
|
+
schema_version: SCHEMA_VERSION,
|
40
|
+
schedule_version: schedule.version,
|
41
|
+
dispatched_at: Time.now.to_f,
|
42
|
+
type: 'schedule',
|
43
|
+
tasks: tasks
|
44
|
+
}
|
45
|
+
|
46
|
+
compress(
|
47
|
+
serialize(data)
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @param command_name [String] command name
|
52
|
+
# @param task_id [String] task id or '*' to match all.
|
53
|
+
# @return [String] serialized and compressed command data
|
54
|
+
def command(command_name, task_id)
|
55
|
+
data = {
|
56
|
+
schema_version: SCHEMA_VERSION,
|
57
|
+
schedule_version: ::Karafka::Pro::RecurringTasks.schedule.version,
|
58
|
+
dispatched_at: Time.now.to_f,
|
59
|
+
type: 'command',
|
60
|
+
command: {
|
61
|
+
name: command_name
|
62
|
+
},
|
63
|
+
task: {
|
64
|
+
id: task_id
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
compress(
|
69
|
+
serialize(data)
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @param event [Karafka::Core::Monitoring::Event] recurring task dispatch event
|
74
|
+
# @return [String] serialized and compressed event log data
|
75
|
+
def log(event)
|
76
|
+
task = event[:task]
|
77
|
+
|
78
|
+
data = {
|
79
|
+
schema_version: SCHEMA_VERSION,
|
80
|
+
schedule_version: ::Karafka::Pro::RecurringTasks.schedule.version,
|
81
|
+
dispatched_at: Time.now.to_f,
|
82
|
+
type: 'log',
|
83
|
+
task: {
|
84
|
+
id: task.id,
|
85
|
+
time_taken: event.payload[:time] || -1,
|
86
|
+
result: event.payload.key?(:error) ? 'failure' : 'success'
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
compress(
|
91
|
+
serialize(data)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# @param hash [Hash] hash to cast to json
|
98
|
+
# @return [String] json hash
|
99
|
+
def serialize(hash)
|
100
|
+
hash.to_json
|
101
|
+
end
|
102
|
+
|
103
|
+
# Compresses the provided data
|
104
|
+
#
|
105
|
+
# @param data [String] data to compress
|
106
|
+
# @return [String] compressed data
|
107
|
+
def compress(data)
|
108
|
+
Zlib::Deflate.deflate(data)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,52 @@
|
|
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
|
+
# Setup and config related recurring tasks components
|
18
|
+
module Setup
|
19
|
+
# Config for recurring tasks
|
20
|
+
class Config
|
21
|
+
extend ::Karafka::Core::Configurable
|
22
|
+
|
23
|
+
setting(:consumer_class, default: Consumer)
|
24
|
+
setting(:deserializer, default: Deserializer.new)
|
25
|
+
setting(:group_id, default: 'karafka_recurring_tasks')
|
26
|
+
# By default we will run the scheduling every 15 seconds since we provide a minute-based
|
27
|
+
# precision
|
28
|
+
setting(:interval, default: 15_000)
|
29
|
+
# Should we log the executions. If true (default) with each cron execution, there will
|
30
|
+
# be a special message published. Useful for debugging.
|
31
|
+
setting(:logging, default: true)
|
32
|
+
|
33
|
+
# Producer to be used by the recurring tasks.
|
34
|
+
# By default it is a `Karafka.producer`, however it may be overwritten if we want to use
|
35
|
+
# a separate instance in case of heavy usage of the transactional producer, etc.
|
36
|
+
setting(
|
37
|
+
:producer,
|
38
|
+
constructor: -> { ::Karafka.producer },
|
39
|
+
lazy: true
|
40
|
+
)
|
41
|
+
|
42
|
+
setting(:topics) do
|
43
|
+
setting(:schedules, default: 'karafka_recurring_tasks_schedules')
|
44
|
+
setting(:logs, default: 'karafka_recurring_tasks_logs')
|
45
|
+
end
|
46
|
+
|
47
|
+
configure
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -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,131 @@
|
|
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 topics definitions.
|
26
|
+
def recurring_tasks(active = false, &block)
|
27
|
+
return unless active
|
28
|
+
|
29
|
+
# We only require zlib when we decide to run recurring tasks because it is not needed
|
30
|
+
# otherwise.
|
31
|
+
require 'zlib'
|
32
|
+
ensure_fugit_availability!
|
33
|
+
|
34
|
+
tasks_cfg = App.config.recurring_tasks
|
35
|
+
topics_cfg = tasks_cfg.topics
|
36
|
+
|
37
|
+
consumer_group tasks_cfg.group_id do
|
38
|
+
# Registers the primary topic that we use to control schedules execution. This is
|
39
|
+
# the one that we use to trigger recurring tasks.
|
40
|
+
schedules_topic = topic(topics_cfg.schedules) do
|
41
|
+
consumer tasks_cfg.consumer_class
|
42
|
+
deserializer tasks_cfg.deserializer
|
43
|
+
# Because the topic method name as well as builder proxy method name is the same
|
44
|
+
# we need to reference it via target directly
|
45
|
+
target.recurring_tasks(true)
|
46
|
+
|
47
|
+
# We manage offsets directly because messages can have both schedules and
|
48
|
+
# commands and we need to apply them only when we need to
|
49
|
+
manual_offset_management(true)
|
50
|
+
|
51
|
+
# We use multi-batch operations and in-memory state for schedules. This needs to
|
52
|
+
# always operate without re-creation.
|
53
|
+
consumer_persistence(true)
|
54
|
+
|
55
|
+
# This needs to be enabled for the eof to work correctly
|
56
|
+
kafka('enable.partition.eof': true, inherit: true)
|
57
|
+
eofed(true)
|
58
|
+
|
59
|
+
# Favour latency. This is a low traffic topic that only accepts user initiated
|
60
|
+
# low-frequency commands
|
61
|
+
max_messages(1)
|
62
|
+
# Since we always react on the received message, we can block for longer periods
|
63
|
+
# of time
|
64
|
+
max_wait_time(10_000)
|
65
|
+
|
66
|
+
# Since the execution of particular tasks is isolated and guarded, it should not
|
67
|
+
# leak. This means, that this is to handle errors like schedule version
|
68
|
+
# incompatibility and other errors that will not go away without a redeployment
|
69
|
+
pause_timeout(60 * 1_000)
|
70
|
+
pause_max_timeout(60 * 1_000)
|
71
|
+
|
72
|
+
# Keep older data for a day and compact to the last state available
|
73
|
+
config(
|
74
|
+
'cleanup.policy': 'compact,delete',
|
75
|
+
'retention.ms': 86_400_000
|
76
|
+
)
|
77
|
+
|
78
|
+
# This is the core of execution. Since we're producers of states, we need a way
|
79
|
+
# to tick without having new data
|
80
|
+
periodic(
|
81
|
+
interval: App.config.recurring_tasks.interval,
|
82
|
+
during_pause: false,
|
83
|
+
during_retry: false
|
84
|
+
)
|
85
|
+
|
86
|
+
# If this is the direct schedules redefinition style, we run it
|
87
|
+
# The second one (see end of this method) allows for linear reconfiguration of
|
88
|
+
# both the topics
|
89
|
+
instance_eval(&block) if block && block.arity.zero?
|
90
|
+
end
|
91
|
+
|
92
|
+
# This topic is to store logs that we can then inspect either from the admin or via
|
93
|
+
# the Web UI
|
94
|
+
logs_topic = topic(topics_cfg.logs) do
|
95
|
+
active(false)
|
96
|
+
deserializer tasks_cfg.deserializer
|
97
|
+
target.recurring_tasks(true)
|
98
|
+
|
99
|
+
# Keep cron logs of executions for a week and after that remove. Week should be
|
100
|
+
# enough and should not produce too much data.
|
101
|
+
config(
|
102
|
+
'cleanup.policy': 'delete',
|
103
|
+
'retention.ms': 604_800_000
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
yield(schedules_topic, logs_topic) if block && block.arity.positive?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Checks if fugit is present. If not, will try to require it as it might not have
|
112
|
+
# been required but is available. If fails, will crash.
|
113
|
+
def ensure_fugit_availability!
|
114
|
+
return if Object.const_defined?(:Fugit)
|
115
|
+
|
116
|
+
require 'fugit'
|
117
|
+
rescue LoadError
|
118
|
+
raise(
|
119
|
+
::Karafka::Errors::DependencyConstraintsError,
|
120
|
+
<<~ERROR_MSG
|
121
|
+
Failed to require fugit gem.
|
122
|
+
Add it to your Gemfile, as it is required for the recurring tasks to work.
|
123
|
+
ERROR_MSG
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
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
|