sidekiq-scheduler 4.0.2 → 5.0.6
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
- data/CHANGELOG.md +77 -1
- data/README.md +136 -48
- data/lib/sidekiq-scheduler/config.rb +91 -0
- data/lib/sidekiq-scheduler/extensions/web.rb +20 -7
- data/lib/sidekiq-scheduler/job_presenter.rb +1 -1
- data/lib/sidekiq-scheduler/manager.rb +26 -26
- data/lib/sidekiq-scheduler/redis_manager.rb +49 -20
- data/lib/sidekiq-scheduler/rufus_utils.rb +2 -2
- data/lib/sidekiq-scheduler/schedule.rb +2 -4
- data/lib/sidekiq-scheduler/scheduler.rb +52 -22
- data/lib/sidekiq-scheduler/sidekiq_adapter.rb +91 -0
- data/lib/sidekiq-scheduler/utils.rb +47 -7
- data/lib/sidekiq-scheduler/version.rb +1 -1
- data/lib/sidekiq-scheduler/web.rb +6 -1
- data/lib/sidekiq-scheduler.rb +6 -17
- data/web/assets/{stylesheets → recurring_jobs/stylesheets-scheduler}/recurring_jobs.css +15 -0
- data/web/locales/cs.yml +1 -0
- data/web/locales/de.yml +3 -0
- data/web/locales/en.yml +3 -0
- data/web/locales/es.yml +1 -0
- data/web/locales/fr.yml +5 -1
- data/web/locales/gd.yml +17 -0
- data/web/locales/it.yml +1 -0
- data/web/locales/ja.yml +1 -0
- data/web/locales/nl.yml +1 -0
- data/web/locales/pl.yml +1 -0
- data/web/locales/pt-BR.yml +2 -1
- data/web/locales/ru.yml +3 -0
- data/web/locales/sv.yml +1 -0
- data/web/locales/zh-cn.yml +1 -0
- data/web/views/recurring_jobs.erb +15 -24
- metadata +33 -45
@@ -9,7 +9,7 @@ module SidekiqScheduler
|
|
9
9
|
#
|
10
10
|
# @return [String] schedule in JSON format
|
11
11
|
def self.get_job_schedule(name)
|
12
|
-
hget(
|
12
|
+
hget(schedules_key, name)
|
13
13
|
end
|
14
14
|
|
15
15
|
# Returns the state of a given job
|
@@ -44,7 +44,7 @@ module SidekiqScheduler
|
|
44
44
|
# @param [String] name The name of the job
|
45
45
|
# @param [Hash] config The new schedule for the job
|
46
46
|
def self.set_job_schedule(name, config)
|
47
|
-
hset(
|
47
|
+
hset(schedules_key, name, JSON.generate(config))
|
48
48
|
end
|
49
49
|
|
50
50
|
# Sets the state for a given job
|
@@ -60,7 +60,7 @@ module SidekiqScheduler
|
|
60
60
|
# @param [String] name The name of the job
|
61
61
|
# @param [String] next_time The next time the job has to be executed
|
62
62
|
def self.set_job_next_time(name, next_time)
|
63
|
-
hset(next_times_key, name, next_time)
|
63
|
+
hset(next_times_key, name, String(next_time))
|
64
64
|
end
|
65
65
|
|
66
66
|
# Sets the last execution time for a given job
|
@@ -68,14 +68,14 @@ module SidekiqScheduler
|
|
68
68
|
# @param [String] name The name of the job
|
69
69
|
# @param [String] last_time The last time the job was executed
|
70
70
|
def self.set_job_last_time(name, last_time)
|
71
|
-
hset(last_times_key, name, last_time)
|
71
|
+
hset(last_times_key, name, String(last_time))
|
72
72
|
end
|
73
73
|
|
74
74
|
# Removes the schedule for a given job
|
75
75
|
#
|
76
76
|
# @param [String] name The name of the job
|
77
77
|
def self.remove_job_schedule(name)
|
78
|
-
hdel(
|
78
|
+
hdel(schedules_key, name)
|
79
79
|
end
|
80
80
|
|
81
81
|
# Removes the next execution time for a given job
|
@@ -89,14 +89,14 @@ module SidekiqScheduler
|
|
89
89
|
#
|
90
90
|
# @return [Hash] hash with all the job schedules
|
91
91
|
def self.get_all_schedules
|
92
|
-
Sidekiq.redis { |r| r.hgetall(
|
92
|
+
Sidekiq.redis { |r| r.hgetall(schedules_key) }
|
93
93
|
end
|
94
94
|
|
95
95
|
# Returns boolean value that indicates if the schedules value exists
|
96
96
|
#
|
97
97
|
# @return [Boolean] true if the schedules key is set, false otherwise
|
98
98
|
def self.schedule_exist?
|
99
|
-
|
99
|
+
SidekiqScheduler::SidekiqAdapter.redis_key_exists?(schedules_key)
|
100
100
|
end
|
101
101
|
|
102
102
|
# Returns all the schedule changes for a given time range.
|
@@ -106,22 +106,22 @@ module SidekiqScheduler
|
|
106
106
|
#
|
107
107
|
# @return [Array] array with all the changed job names
|
108
108
|
def self.get_schedule_changes(from, to)
|
109
|
-
|
109
|
+
SidekiqScheduler::SidekiqAdapter.redis_zrangebyscore(schedules_changed_key, from, "(#{to}")
|
110
110
|
end
|
111
111
|
|
112
112
|
# Register a schedule change for a given job
|
113
113
|
#
|
114
114
|
# @param [String] name The name of the job
|
115
115
|
def self.add_schedule_change(name)
|
116
|
-
Sidekiq.redis { |r| r.zadd(
|
116
|
+
Sidekiq.redis { |r| r.zadd(schedules_changed_key, Time.now.to_f, name) }
|
117
117
|
end
|
118
118
|
|
119
119
|
# Remove all the schedule changes records
|
120
120
|
def self.clean_schedules_changed
|
121
|
-
Sidekiq.redis { |r| r.del(
|
121
|
+
Sidekiq.redis { |r| r.del(schedules_changed_key) unless r.type(schedules_changed_key) == 'zset' }
|
122
122
|
end
|
123
123
|
|
124
|
-
#
|
124
|
+
# Registers a queued job instance
|
125
125
|
#
|
126
126
|
# @param [String] job_name The name of the job
|
127
127
|
# @param [Time] time The time at which the job was cleared by the scheduler
|
@@ -130,13 +130,13 @@ module SidekiqScheduler
|
|
130
130
|
def self.register_job_instance(job_name, time)
|
131
131
|
job_key = pushed_job_key(job_name)
|
132
132
|
registered, _ = Sidekiq.redis do |r|
|
133
|
-
r.
|
134
|
-
|
135
|
-
|
133
|
+
r.multi do |m|
|
134
|
+
m.zadd(job_key, time.to_i, time.to_i)
|
135
|
+
m.expire(job_key, REGISTERED_JOBS_THRESHOLD_IN_SECONDS)
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
-
registered
|
139
|
+
registered.instance_of?(Integer) ? (registered > 0) : registered
|
140
140
|
end
|
141
141
|
|
142
142
|
# Removes instances of the job older than 24 hours
|
@@ -156,32 +156,61 @@ module SidekiqScheduler
|
|
156
156
|
#
|
157
157
|
# @return [String] the pushed job key
|
158
158
|
def self.pushed_job_key(job_name)
|
159
|
-
"sidekiq-scheduler:pushed:#{job_name}"
|
159
|
+
"#{key_prefix}sidekiq-scheduler:pushed:#{job_name}"
|
160
160
|
end
|
161
161
|
|
162
162
|
# Returns the key of the Redis hash for job's execution times hash
|
163
163
|
#
|
164
164
|
# @return [String] with the key
|
165
165
|
def self.next_times_key
|
166
|
-
|
166
|
+
"#{key_prefix}sidekiq-scheduler:next_times"
|
167
167
|
end
|
168
168
|
|
169
169
|
# Returns the key of the Redis hash for job's last execution times hash
|
170
170
|
#
|
171
171
|
# @return [String] with the key
|
172
172
|
def self.last_times_key
|
173
|
-
|
173
|
+
"#{key_prefix}sidekiq-scheduler:last_times"
|
174
174
|
end
|
175
175
|
|
176
176
|
# Returns the Redis's key for saving schedule states.
|
177
177
|
#
|
178
178
|
# @return [String] with the key
|
179
179
|
def self.schedules_state_key
|
180
|
-
|
180
|
+
"#{key_prefix}sidekiq-scheduler:states"
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the Redis's key for saving schedules.
|
184
|
+
#
|
185
|
+
# @return [String] with the key
|
186
|
+
def self.schedules_key
|
187
|
+
"#{key_prefix}schedules"
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns the Redis's key for saving schedule changes.
|
191
|
+
#
|
192
|
+
# @return [String] with the key
|
193
|
+
def self.schedules_changed_key
|
194
|
+
"#{key_prefix}schedules_changed"
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns the key prefix used to generate all scheduler keys
|
198
|
+
#
|
199
|
+
# @return [String] with the key prefix
|
200
|
+
def self.key_prefix
|
201
|
+
@key_prefix
|
202
|
+
end
|
203
|
+
|
204
|
+
# Sets the key prefix used to scope all scheduler keys
|
205
|
+
#
|
206
|
+
# @param [String] value The string to use as the prefix. A ":" will be appended as a delimiter if needed.
|
207
|
+
def self.key_prefix=(value)
|
208
|
+
value = "#{value}:" if value && !%w[. :].include?(value[-1])
|
209
|
+
@key_prefix = value
|
181
210
|
end
|
182
211
|
|
183
212
|
private
|
184
|
-
|
213
|
+
|
185
214
|
# Returns the value of a Redis stored hash field
|
186
215
|
#
|
187
216
|
# @param [String] hash_key The key name of the hash
|
@@ -3,9 +3,9 @@ require 'sidekiq-scheduler/utils'
|
|
3
3
|
module SidekiqScheduler
|
4
4
|
class RufusUtils
|
5
5
|
|
6
|
-
# Normalizes schedule options to
|
6
|
+
# Normalizes schedule options to rufus scheduler options
|
7
7
|
#
|
8
|
-
# @param options [String,
|
8
|
+
# @param options [String, Array]
|
9
9
|
#
|
10
10
|
# @return [Array]
|
11
11
|
#
|
@@ -65,7 +65,7 @@ module SidekiqScheduler
|
|
65
65
|
end
|
66
66
|
alias_method :schedule!, :reload_schedule!
|
67
67
|
|
68
|
-
#
|
68
|
+
# Retrieve the schedule configuration for the given name
|
69
69
|
# if the name is nil it returns a hash with all the
|
70
70
|
# names end their schedules.
|
71
71
|
def get_schedule(name = nil)
|
@@ -148,9 +148,7 @@ module SidekiqScheduler
|
|
148
148
|
end
|
149
149
|
|
150
150
|
def try_to_constantize(klass)
|
151
|
-
|
152
|
-
rescue NameError
|
153
|
-
klass
|
151
|
+
SidekiqScheduler::Utils.try_to_constantize(klass)
|
154
152
|
end
|
155
153
|
end
|
156
154
|
end
|
@@ -2,6 +2,7 @@ require 'rufus/scheduler'
|
|
2
2
|
require 'json'
|
3
3
|
require 'sidekiq-scheduler/rufus_utils'
|
4
4
|
require 'sidekiq-scheduler/redis_manager'
|
5
|
+
require 'sidekiq-scheduler/config'
|
5
6
|
|
6
7
|
module SidekiqScheduler
|
7
8
|
class Scheduler
|
@@ -10,6 +11,15 @@ module SidekiqScheduler
|
|
10
11
|
alias_method :params, :opts
|
11
12
|
end
|
12
13
|
|
14
|
+
# TODO: Can we remove those attr_accessor's? If we need to keep them, we should
|
15
|
+
# update those values on the config object instead of just here in the scheduler.
|
16
|
+
# That's why we need to do what we do in the set_current_scheduler_options (not
|
17
|
+
# saying we will have to do it somehow still)
|
18
|
+
#
|
19
|
+
# NOTE: ^ Keeping this TODO here for now, in a future version of this project
|
20
|
+
# we will remove those attr accessors and use only our config object. For now,
|
21
|
+
# let's keep as it is.
|
22
|
+
|
13
23
|
# Set to enable or disable the scheduler.
|
14
24
|
attr_accessor :enabled
|
15
25
|
|
@@ -41,12 +51,14 @@ module SidekiqScheduler
|
|
41
51
|
end
|
42
52
|
end
|
43
53
|
|
44
|
-
def initialize(
|
45
|
-
|
46
|
-
|
47
|
-
self.
|
48
|
-
self.
|
49
|
-
self.
|
54
|
+
def initialize(config = SidekiqScheduler::Config.new(without_defaults: true))
|
55
|
+
@scheduler_config = config
|
56
|
+
|
57
|
+
self.enabled = config.enabled?
|
58
|
+
self.dynamic = config.dynamic?
|
59
|
+
self.dynamic_every = config.dynamic_every?
|
60
|
+
self.listened_queues_only = config.listened_queues_only?
|
61
|
+
self.rufus_scheduler_options = config.rufus_scheduler_options || {}
|
50
62
|
end
|
51
63
|
|
52
64
|
# the Rufus::Scheduler jobs that are scheduled
|
@@ -82,7 +94,7 @@ module SidekiqScheduler
|
|
82
94
|
Sidekiq.logger.info 'Schedule empty! Set Sidekiq.schedule' if Sidekiq.schedule.empty?
|
83
95
|
|
84
96
|
@scheduled_jobs = {}
|
85
|
-
queues = sidekiq_queues
|
97
|
+
queues = scheduler_config.sidekiq_queues
|
86
98
|
|
87
99
|
Sidekiq.schedule.each do |name, config|
|
88
100
|
if !listened_queues_only || enabled_queue?(config['queue'].to_s, queues)
|
@@ -116,6 +128,8 @@ module SidekiqScheduler
|
|
116
128
|
schedule, options = SidekiqScheduler::RufusUtils.normalize_schedule_options(config_interval_type)
|
117
129
|
|
118
130
|
rufus_job = new_job(name, interval_type, config, schedule, options)
|
131
|
+
return unless rufus_job
|
132
|
+
|
119
133
|
@scheduled_jobs[name] = rufus_job
|
120
134
|
SidekiqScheduler::Utils.update_job_next_time(name, rufus_job.next_time)
|
121
135
|
|
@@ -158,7 +172,7 @@ module SidekiqScheduler
|
|
158
172
|
config = prepare_arguments(job_config.dup)
|
159
173
|
|
160
174
|
if config.delete('include_metadata')
|
161
|
-
config['args'] = arguments_with_metadata(config['args'], scheduled_at
|
175
|
+
config['args'] = arguments_with_metadata(config['args'], "scheduled_at" => time.to_f.round(3))
|
162
176
|
end
|
163
177
|
|
164
178
|
if SidekiqScheduler::Utils.active_job_enqueue?(config['class'])
|
@@ -182,7 +196,7 @@ module SidekiqScheduler
|
|
182
196
|
@rufus_scheduler = nil
|
183
197
|
end
|
184
198
|
|
185
|
-
|
199
|
+
@scheduled_jobs = {}
|
186
200
|
|
187
201
|
rufus_scheduler
|
188
202
|
end
|
@@ -228,13 +242,37 @@ module SidekiqScheduler
|
|
228
242
|
set_schedule_state(name, state)
|
229
243
|
end
|
230
244
|
|
245
|
+
def toggle_all_jobs(new_state)
|
246
|
+
Sidekiq.schedule!.keys.each do |name|
|
247
|
+
state = schedule_state(name)
|
248
|
+
state['enabled'] = new_state
|
249
|
+
set_schedule_state(name, state)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def to_hash
|
254
|
+
{
|
255
|
+
scheduler_config: @scheduler_config.to_hash
|
256
|
+
}
|
257
|
+
end
|
258
|
+
|
231
259
|
private
|
232
260
|
|
261
|
+
attr_reader :scheduler_config
|
262
|
+
|
233
263
|
def new_job(name, interval_type, config, schedule, options)
|
234
264
|
options = options.merge({ :job => true, :tags => [name] })
|
235
265
|
|
236
266
|
rufus_scheduler.send(interval_type, schedule, options) do |job, time|
|
237
|
-
|
267
|
+
if job_enabled?(name)
|
268
|
+
conf = SidekiqScheduler::Utils.sanitize_job_config(config)
|
269
|
+
|
270
|
+
if job.is_a?(Rufus::Scheduler::CronJob)
|
271
|
+
idempotent_job_enqueue(name, SidekiqScheduler::Utils.calc_cron_run_time(job.cron_line, time.to_t), conf)
|
272
|
+
else
|
273
|
+
idempotent_job_enqueue(name, time.to_t, conf)
|
274
|
+
end
|
275
|
+
end
|
238
276
|
end
|
239
277
|
end
|
240
278
|
|
@@ -259,18 +297,18 @@ module SidekiqScheduler
|
|
259
297
|
# Saves a schedule state
|
260
298
|
#
|
261
299
|
# @param name [String] with the schedule's name
|
262
|
-
# @param
|
300
|
+
# @param state [Hash] with the schedule's state
|
263
301
|
def set_schedule_state(name, state)
|
264
302
|
SidekiqScheduler::RedisManager.set_job_state(name, state)
|
265
303
|
end
|
266
304
|
|
267
305
|
# Adds a Hash with schedule metadata as the last argument to call the worker.
|
268
|
-
# It currently returns the schedule time as a Float number representing the
|
306
|
+
# It currently returns the schedule time as a Float number representing the milliseconds
|
269
307
|
# since epoch.
|
270
308
|
#
|
271
309
|
# @example with hash argument
|
272
|
-
# arguments_with_metadata({value: 1}, scheduled_at: Time.now)
|
273
|
-
# #=> [{value: 1}, {scheduled_at: <
|
310
|
+
# arguments_with_metadata({value: 1}, scheduled_at: Time.now.round(3))
|
311
|
+
# #=> [{value: 1}, {scheduled_at: <milliseconds since epoch>}]
|
274
312
|
#
|
275
313
|
# @param args [Array|Hash]
|
276
314
|
# @param metadata [Hash]
|
@@ -283,14 +321,6 @@ module SidekiqScheduler
|
|
283
321
|
end
|
284
322
|
end
|
285
323
|
|
286
|
-
def sidekiq_queues
|
287
|
-
if SIDEKIQ_GTE_6_5_0
|
288
|
-
Sidekiq[:queues].map(&:to_s)
|
289
|
-
else
|
290
|
-
Sidekiq.options[:queues].map(&:to_s)
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
324
|
# Returns true if a job's queue is included in the array of queues
|
295
325
|
#
|
296
326
|
# If queues are empty, returns true.
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module SidekiqScheduler
|
2
|
+
class OptionNotSupportedAnymore < StandardError; end
|
3
|
+
|
4
|
+
class SidekiqAdapter
|
5
|
+
SIDEKIQ_GTE_6_5_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('6.5.0')
|
6
|
+
SIDEKIQ_GTE_7_0_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('7.0.0')
|
7
|
+
SIDEKIQ_GTE_7_3_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('7.3.0')
|
8
|
+
|
9
|
+
def self.fetch_scheduler_config_from_sidekiq(sidekiq_config)
|
10
|
+
return {} if sidekiq_config.nil?
|
11
|
+
|
12
|
+
check_using_old_sidekiq_scheduler_config!(sidekiq_config)
|
13
|
+
|
14
|
+
if SIDEKIQ_GTE_6_5_0
|
15
|
+
sidekiq_config.fetch(:scheduler, {})
|
16
|
+
else
|
17
|
+
sidekiq_config.options.fetch(:scheduler, {})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.check_using_old_sidekiq_scheduler_config!(sidekiq_config)
|
22
|
+
%i[enabled dynamic dynamic_every schedule listened_queues_only rufus_scheduler_options].each do |option|
|
23
|
+
if SIDEKIQ_GTE_7_0_0
|
24
|
+
if sidekiq_config.key?(option)
|
25
|
+
raise OptionNotSupportedAnymore, ":#{option} option should be under the :scheduler: key"
|
26
|
+
end
|
27
|
+
elsif SIDEKIQ_GTE_6_5_0
|
28
|
+
unless sidekiq_config[option].nil?
|
29
|
+
raise OptionNotSupportedAnymore, ":#{option} option should be under the :scheduler: key"
|
30
|
+
end
|
31
|
+
else
|
32
|
+
if sidekiq_config.options.key?(option)
|
33
|
+
raise OptionNotSupportedAnymore, ":#{option} option should be under the :scheduler: key"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.start_schedule_manager(sidekiq_config:, schedule_manager:)
|
40
|
+
if SIDEKIQ_GTE_6_5_0
|
41
|
+
sidekiq_config[:schedule_manager] = schedule_manager
|
42
|
+
sidekiq_config[:schedule_manager].start
|
43
|
+
else
|
44
|
+
sidekiq_config.options[:schedule_manager] = schedule_manager
|
45
|
+
sidekiq_config.options[:schedule_manager].start
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.stop_schedule_manager(sidekiq_config:)
|
50
|
+
if SIDEKIQ_GTE_6_5_0
|
51
|
+
sidekiq_config[:schedule_manager].stop
|
52
|
+
else
|
53
|
+
sidekiq_config.options[:schedule_manager].stop
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.sidekiq_queues(sidekiq_config)
|
58
|
+
if SIDEKIQ_GTE_7_0_0
|
59
|
+
if sidekiq_config.nil? || (sidekiq_config.respond_to?(:empty?) && sidekiq_config.empty?)
|
60
|
+
Sidekiq.instance_variable_get(:@config).queues.map(&:to_s)
|
61
|
+
else
|
62
|
+
sidekiq_config.queues.map(&:to_s)
|
63
|
+
end
|
64
|
+
elsif SIDEKIQ_GTE_6_5_0
|
65
|
+
Sidekiq[:queues].map(&:to_s)
|
66
|
+
else
|
67
|
+
Sidekiq.options[:queues].map(&:to_s)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.redis_key_exists?(key_name)
|
72
|
+
Sidekiq.redis do |r|
|
73
|
+
if SIDEKIQ_GTE_7_0_0
|
74
|
+
r.exists(key_name) > 0
|
75
|
+
else
|
76
|
+
r.exists?(key_name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.redis_zrangebyscore(key, from, to)
|
82
|
+
Sidekiq.redis do |r|
|
83
|
+
if SIDEKIQ_GTE_7_0_0
|
84
|
+
r.zrange(key, from, to, "BYSCORE")
|
85
|
+
else
|
86
|
+
r.zrangebyscore(key, from, to)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -49,7 +49,7 @@ module SidekiqScheduler
|
|
49
49
|
#
|
50
50
|
# @return [Class] the class corresponding to the klass param
|
51
51
|
def self.try_to_constantize(klass)
|
52
|
-
klass.is_a?(String) ? klass
|
52
|
+
klass.is_a?(String) ? Object.const_get(klass) : klass
|
53
53
|
rescue NameError
|
54
54
|
klass
|
55
55
|
end
|
@@ -57,12 +57,14 @@ module SidekiqScheduler
|
|
57
57
|
# Initializes active_job using the passed parameters.
|
58
58
|
#
|
59
59
|
# @param [Class] klass The class to initialize
|
60
|
-
# @param [Array
|
60
|
+
# @param [Array, Hash] args The parameters passed to the klass initializer
|
61
61
|
#
|
62
62
|
# @return [Object] instance of the class klass
|
63
|
-
def self.initialize_active_job(klass, args)
|
63
|
+
def self.initialize_active_job(klass, args, keyword_argument = false)
|
64
64
|
if args.is_a?(Array)
|
65
65
|
klass.new(*args)
|
66
|
+
elsif args.is_a?(Hash) && keyword_argument
|
67
|
+
klass.new(**symbolize_keys(args))
|
66
68
|
else
|
67
69
|
klass.new(args)
|
68
70
|
end
|
@@ -71,7 +73,7 @@ module SidekiqScheduler
|
|
71
73
|
# Returns true if the enqueuing needs to be done for an ActiveJob
|
72
74
|
# class false otherwise.
|
73
75
|
#
|
74
|
-
# @param [Class] klass the class to check is
|
76
|
+
# @param [Class] klass the class to check is descendant from ActiveJob
|
75
77
|
#
|
76
78
|
# @return [Boolean]
|
77
79
|
def self.active_job_enqueue?(klass)
|
@@ -94,7 +96,7 @@ module SidekiqScheduler
|
|
94
96
|
queue: config['queue']
|
95
97
|
}.keep_if { |_, v| !v.nil? }
|
96
98
|
|
97
|
-
initialize_active_job(config['class'], config['args']).enqueue(options)
|
99
|
+
initialize_active_job(config['class'], config['args'], config['keyword_argument']).enqueue(options)
|
98
100
|
end
|
99
101
|
|
100
102
|
# Removes the hash values associated to the rufus metadata keys.
|
@@ -112,8 +114,10 @@ module SidekiqScheduler
|
|
112
114
|
def self.new_rufus_scheduler(options = {})
|
113
115
|
Rufus::Scheduler.new(options).tap do |scheduler|
|
114
116
|
scheduler.define_singleton_method(:on_post_trigger) do |job, triggered_time|
|
115
|
-
|
116
|
-
|
117
|
+
if (job_name = job.tags[0])
|
118
|
+
SidekiqScheduler::Utils.update_job_last_time(job_name, triggered_time)
|
119
|
+
SidekiqScheduler::Utils.update_job_next_time(job_name, job.next_time)
|
120
|
+
end
|
117
121
|
end
|
118
122
|
end
|
119
123
|
end
|
@@ -137,5 +141,41 @@ module SidekiqScheduler
|
|
137
141
|
def self.update_job_last_time(name, last_time)
|
138
142
|
SidekiqScheduler::RedisManager.set_job_last_time(name, last_time) if last_time
|
139
143
|
end
|
144
|
+
|
145
|
+
# Try to figure out when the cron job was supposed to run.
|
146
|
+
#
|
147
|
+
# Rufus calls the scheduler block with the current time and not the time the block was scheduled to run.
|
148
|
+
# This means under certain conditions you could have a job get scheduled multiple times because `time.to_i` is used
|
149
|
+
# to key the job in redis. If one server is under load and Rufus tries to run the jobs 1 seconds after the other
|
150
|
+
# server then the job will be queued twice.
|
151
|
+
# This method essentially makes a best guess at when this job was supposed to run and return that.
|
152
|
+
#
|
153
|
+
# @param [Fugit::Cron] cron
|
154
|
+
# @param [Time] time
|
155
|
+
#
|
156
|
+
# @return [Time]
|
157
|
+
def self.calc_cron_run_time(cron, time)
|
158
|
+
time = time.floor # remove sub seconds to prevent rounding errors.
|
159
|
+
return time if cron.match?(time) # If the time is a perfect match then return it.
|
160
|
+
|
161
|
+
next_t = cron.next_time(time).to_t
|
162
|
+
previous_t = cron.previous_time(time).to_t
|
163
|
+
# The `time` var is some point between `previous_t` and `next_t`.
|
164
|
+
# Figure out how far off we are from each side in seconds.
|
165
|
+
next_diff = next_t - time
|
166
|
+
previous_diff = time - previous_t
|
167
|
+
|
168
|
+
if next_diff == previous_diff
|
169
|
+
# In the event `time` is exactly between `previous_t` and `next_t` the diff will not be equal to
|
170
|
+
# `cron.rough_frequency`. In that case we round down.
|
171
|
+
cron.rough_frequency == next_diff ? time : previous_t
|
172
|
+
elsif next_diff > previous_diff
|
173
|
+
# We are closer to the previous run time so return that.
|
174
|
+
previous_t
|
175
|
+
else
|
176
|
+
# We are closer to the next run time so return that.
|
177
|
+
next_t
|
178
|
+
end
|
179
|
+
end
|
140
180
|
end
|
141
181
|
end
|
@@ -3,7 +3,7 @@ require 'sidekiq-scheduler'
|
|
3
3
|
require_relative 'job_presenter'
|
4
4
|
|
5
5
|
module SidekiqScheduler
|
6
|
-
# Hook into *Sidekiq::Web*
|
6
|
+
# Hook into *Sidekiq::Web* app which adds a new '/recurring-jobs' page
|
7
7
|
|
8
8
|
module Web
|
9
9
|
VIEW_PATH = File.expand_path('../../../web/views', __FILE__)
|
@@ -27,6 +27,11 @@ module SidekiqScheduler
|
|
27
27
|
SidekiqScheduler::Scheduler.instance.toggle_job_enabled(params[:name])
|
28
28
|
redirect "#{root_path}recurring-jobs"
|
29
29
|
end
|
30
|
+
|
31
|
+
app.post '/recurring-jobs/toggle-all' do
|
32
|
+
SidekiqScheduler::Scheduler.instance.toggle_all_jobs(params[:action] == 'enable')
|
33
|
+
redirect "#{root_path}recurring-jobs"
|
34
|
+
end
|
30
35
|
end
|
31
36
|
end
|
32
37
|
end
|
data/lib/sidekiq-scheduler.rb
CHANGED
@@ -5,9 +5,9 @@ require_relative 'sidekiq/scheduler'
|
|
5
5
|
require_relative 'sidekiq-scheduler/version'
|
6
6
|
require_relative 'sidekiq-scheduler/manager'
|
7
7
|
require_relative 'sidekiq-scheduler/redis_manager'
|
8
|
+
require_relative 'sidekiq-scheduler/config'
|
8
9
|
require_relative 'sidekiq-scheduler/extensions/schedule'
|
9
|
-
|
10
|
-
SIDEKIQ_GTE_6_5_0 = Gem::Version.new(Sidekiq::VERSION) >= Gem::Version.new('6.5.0')
|
10
|
+
require_relative 'sidekiq-scheduler/sidekiq_adapter'
|
11
11
|
|
12
12
|
Sidekiq.configure_server do |config|
|
13
13
|
|
@@ -15,25 +15,14 @@ Sidekiq.configure_server do |config|
|
|
15
15
|
# schedules_changed's type was changed from SET to ZSET, so we remove old versions at startup
|
16
16
|
SidekiqScheduler::RedisManager.clean_schedules_changed
|
17
17
|
|
18
|
-
|
19
|
-
config_options = SIDEKIQ_GTE_6_5_0 ? Sidekiq.instance_variable_get(:@config) : config.options
|
18
|
+
scheduler_config = SidekiqScheduler::Config.new(sidekiq_config: config)
|
20
19
|
|
21
|
-
schedule_manager = SidekiqScheduler::Manager.new(
|
22
|
-
|
23
|
-
config[:schedule_manager] = schedule_manager
|
24
|
-
config[:schedule_manager].start
|
25
|
-
else
|
26
|
-
config.options[:schedule_manager] = schedule_manager
|
27
|
-
config.options[:schedule_manager].start
|
28
|
-
end
|
20
|
+
schedule_manager = SidekiqScheduler::Manager.new(scheduler_config)
|
21
|
+
SidekiqScheduler::SidekiqAdapter.start_schedule_manager(sidekiq_config: config, schedule_manager: schedule_manager)
|
29
22
|
end
|
30
23
|
|
31
24
|
config.on(:quiet) do
|
32
|
-
|
33
|
-
config[:schedule_manager].stop
|
34
|
-
else
|
35
|
-
config.options[:schedule_manager].stop
|
36
|
-
end
|
25
|
+
SidekiqScheduler::SidekiqAdapter.stop_schedule_manager(sidekiq_config: config)
|
37
26
|
end
|
38
27
|
|
39
28
|
end
|
@@ -25,3 +25,18 @@
|
|
25
25
|
border: 1px solid #555;
|
26
26
|
}
|
27
27
|
}
|
28
|
+
|
29
|
+
.toggle-all-buttons {
|
30
|
+
margin-top: 20px;
|
31
|
+
margin-bottom: 10px;
|
32
|
+
line-height: 45px;
|
33
|
+
text-align: right;
|
34
|
+
}
|
35
|
+
|
36
|
+
@media (max-width: 768px) {
|
37
|
+
.toggle-all-buttons {
|
38
|
+
margin-top: 0;
|
39
|
+
text-align: left;
|
40
|
+
line-height: inherit;
|
41
|
+
}
|
42
|
+
}
|
data/web/locales/cs.yml
CHANGED
data/web/locales/de.yml
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
de:
|
2
|
+
"Recurring Jobs": Wiederkehrende Jobs
|
2
3
|
recurring_jobs: Wiederkehrende Jobs
|
3
4
|
name: Name
|
4
5
|
description: Beschreibung
|
@@ -12,3 +13,5 @@ de:
|
|
12
13
|
no_next_time: Keine zukünftige Ausführung für diesen Job
|
13
14
|
disable: Deaktivieren
|
14
15
|
enable: Aktivieren
|
16
|
+
disable_all: Alle deaktivieren
|
17
|
+
enable_all: Alle aktivieren
|
data/web/locales/en.yml
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
en:
|
2
|
+
"Recurring Jobs": Recurring Jobs
|
2
3
|
recurring_jobs: Recurring Jobs
|
3
4
|
name: Name
|
4
5
|
description: Description
|
@@ -12,3 +13,5 @@ en:
|
|
12
13
|
no_next_time: no next execution for this job
|
13
14
|
disable: Disable
|
14
15
|
enable: Enable
|
16
|
+
disable_all: Disable all
|
17
|
+
enable_all: Enable all
|