resque-scheduler 2.2.0 → 4.10.2

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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +12 -0
  3. data/.github/funding.yml +4 -0
  4. data/.github/workflows/codeql-analysis.yml +59 -0
  5. data/.github/workflows/rubocop.yml +27 -0
  6. data/.github/workflows/ruby.yml +81 -0
  7. data/AUTHORS.md +31 -0
  8. data/CHANGELOG.md +539 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/Gemfile +26 -2
  11. data/LICENSE +1 -1
  12. data/README.md +423 -91
  13. data/Rakefile +12 -35
  14. data/exe/resque-scheduler +5 -0
  15. data/lib/resque/scheduler/cli.rb +147 -0
  16. data/lib/resque/scheduler/configuration.rb +102 -0
  17. data/lib/resque/scheduler/delaying_extensions.rb +371 -0
  18. data/lib/resque/scheduler/env.rb +85 -0
  19. data/lib/resque/scheduler/extension.rb +13 -0
  20. data/lib/resque/scheduler/failure_handler.rb +11 -0
  21. data/lib/resque/scheduler/lock/base.rb +12 -3
  22. data/lib/resque/scheduler/lock/basic.rb +4 -5
  23. data/lib/resque/scheduler/lock/resilient.rb +52 -43
  24. data/lib/resque/scheduler/lock.rb +2 -1
  25. data/lib/resque/scheduler/locking.rb +104 -0
  26. data/lib/resque/scheduler/logger_builder.rb +83 -0
  27. data/lib/resque/scheduler/plugin.rb +31 -0
  28. data/lib/resque/scheduler/scheduling_extensions.rb +142 -0
  29. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed.erb +23 -8
  30. data/lib/resque/scheduler/server/views/delayed_schedules.erb +20 -0
  31. data/lib/{resque_scheduler → resque/scheduler}/server/views/delayed_timestamp.erb +1 -1
  32. data/lib/resque/scheduler/server/views/scheduler.erb +58 -0
  33. data/lib/resque/scheduler/server/views/search.erb +69 -0
  34. data/lib/resque/scheduler/server/views/search_form.erb +4 -0
  35. data/lib/resque/scheduler/server.rb +268 -0
  36. data/lib/resque/scheduler/signal_handling.rb +40 -0
  37. data/lib/resque/scheduler/tasks.rb +25 -0
  38. data/lib/resque/scheduler/util.rb +39 -0
  39. data/lib/resque/scheduler/version.rb +7 -0
  40. data/lib/resque/scheduler.rb +333 -149
  41. data/lib/resque-scheduler.rb +4 -0
  42. data/resque-scheduler.gemspec +56 -20
  43. metadata +205 -104
  44. data/.gitignore +0 -8
  45. data/.rubocop.yml +0 -120
  46. data/.travis.yml +0 -10
  47. data/HISTORY.md +0 -180
  48. data/lib/resque/scheduler_locking.rb +0 -90
  49. data/lib/resque_scheduler/logger_builder.rb +0 -51
  50. data/lib/resque_scheduler/plugin.rb +0 -25
  51. data/lib/resque_scheduler/server/views/scheduler.erb +0 -44
  52. data/lib/resque_scheduler/server.rb +0 -92
  53. data/lib/resque_scheduler/tasks.rb +0 -40
  54. data/lib/resque_scheduler/version.rb +0 -3
  55. data/lib/resque_scheduler.rb +0 -355
  56. data/script/migrate_to_timestamps_set.rb +0 -14
  57. data/tasks/resque_scheduler.rake +0 -2
  58. data/test/delayed_queue_test.rb +0 -383
  59. data/test/redis-test.conf +0 -108
  60. data/test/resque-web_test.rb +0 -116
  61. data/test/scheduler_args_test.rb +0 -156
  62. data/test/scheduler_hooks_test.rb +0 -23
  63. data/test/scheduler_locking_test.rb +0 -180
  64. data/test/scheduler_setup_test.rb +0 -59
  65. data/test/scheduler_test.rb +0 -256
  66. data/test/support/redis_instance.rb +0 -129
  67. data/test/test_helper.rb +0 -92
  68. /data/lib/{resque_scheduler → resque/scheduler}/server/views/requeue-params.erb +0 -0
@@ -1,355 +0,0 @@
1
- require 'rubygems'
2
- require 'resque'
3
- require 'resque_scheduler/version'
4
- require 'resque/scheduler'
5
- require 'resque_scheduler/plugin'
6
-
7
- module ResqueScheduler
8
-
9
- #
10
- # Accepts a new schedule configuration of the form:
11
- #
12
- # {
13
- # "MakeTea" => {
14
- # "every" => "1m" },
15
- # "some_name" => {
16
- # "cron" => "5/* * * *",
17
- # "class" => "DoSomeWork",
18
- # "args" => "work on this string",
19
- # "description" => "this thing works it"s butter off" },
20
- # ...
21
- # }
22
- #
23
- # Hash keys can be anything and are used to describe and reference
24
- # the scheduled job. If the "class" argument is missing, the key
25
- # is used implicitly as "class" argument - in the "MakeTea" example,
26
- # "MakeTea" is used both as job name and resque worker class.
27
- #
28
- # Any jobs that were in the old schedule, but are not
29
- # present in the new schedule, will be removed.
30
- #
31
- # :cron can be any cron scheduling string
32
- #
33
- # :every can be used in lieu of :cron. see rufus-scheduler's 'every' usage
34
- # for valid syntax. If :cron is present it will take precedence over :every.
35
- #
36
- # :class must be a resque worker class. If it is missing, the job name (hash key)
37
- # will be used as :class.
38
- #
39
- # :args can be any yaml which will be converted to a ruby literal and
40
- # passed in a params. (optional)
41
- #
42
- # :rails_envs is the list of envs where the job gets loaded. Envs are
43
- # comma separated (optional)
44
- #
45
- # :description is just that, a description of the job (optional). If
46
- # params is an array, each element in the array is passed as a separate
47
- # param, otherwise params is passed in as the only parameter to perform.
48
- def schedule=(schedule_hash)
49
- # clean the schedules as it exists in redis
50
- clean_schedules
51
-
52
- schedule_hash = prepare_schedule(schedule_hash)
53
-
54
- # store all schedules in redis, so we can retrieve them back everywhere.
55
- schedule_hash.each do |name, job_spec|
56
- set_schedule(name, job_spec)
57
- end
58
-
59
- # ensure only return the successfully saved data!
60
- reload_schedule!
61
- end
62
-
63
- # Returns the schedule hash
64
- def schedule
65
- @schedule ||= get_schedules
66
- if @schedule.nil?
67
- return {}
68
- end
69
- @schedule
70
- end
71
-
72
- # reloads the schedule from redis
73
- def reload_schedule!
74
- @schedule = get_schedules
75
- end
76
-
77
- # gets the schedules as it exists in redis
78
- def get_schedules
79
- unless redis.exists(:schedules)
80
- return nil
81
- end
82
-
83
- redis.hgetall(:schedules).tap do |h|
84
- h.each do |name, config|
85
- h[name] = decode(config)
86
- end
87
- end
88
- end
89
-
90
- # clean the schedules as it exists in redis, useful for first setup?
91
- def clean_schedules
92
- if redis.exists(:schedules)
93
- redis.hkeys(:schedules).each do |key|
94
- remove_schedule(key)
95
- end
96
- end
97
- @schedule = nil
98
- true
99
- end
100
-
101
- # Create or update a schedule with the provided name and configuration.
102
- #
103
- # Note: values for class and custom_job_class need to be strings,
104
- # not constants.
105
- #
106
- # Resque.set_schedule('some_job', {:class => 'SomeJob',
107
- # :every => '15mins',
108
- # :queue => 'high',
109
- # :args => '/tmp/poop'})
110
- def set_schedule(name, config)
111
- existing_config = get_schedule(name)
112
- unless existing_config && existing_config == config
113
- redis.hset(:schedules, name, encode(config))
114
- redis.sadd(:schedules_changed, name)
115
- end
116
- config
117
- end
118
-
119
- # retrive the schedule configuration for the given name
120
- def get_schedule(name)
121
- decode(redis.hget(:schedules, name))
122
- end
123
-
124
- # remove a given schedule by name
125
- def remove_schedule(name)
126
- redis.hdel(:schedules, name)
127
- redis.sadd(:schedules_changed, name)
128
- end
129
-
130
- # This method is nearly identical to +enqueue+ only it also
131
- # takes a timestamp which will be used to schedule the job
132
- # for queueing. Until timestamp is in the past, the job will
133
- # sit in the schedule list.
134
- def enqueue_at(timestamp, klass, *args)
135
- validate_job!(klass)
136
- enqueue_at_with_queue(queue_from_class(klass), timestamp, klass, *args)
137
- end
138
-
139
- # Identical to +enqueue_at+, except you can also specify
140
- # a queue in which the job will be placed after the
141
- # timestamp has passed. It respects Resque.inline option, by
142
- # creating the job right away instead of adding to the queue.
143
- def enqueue_at_with_queue(queue, timestamp, klass, *args)
144
- return false unless Plugin.run_before_schedule_hooks(klass, *args)
145
-
146
- if Resque.inline? || timestamp.to_i < Time.now.to_i
147
- # Just create the job and let resque perform it right away with inline.
148
- # If the class is a custom job class, call self#scheduled on it. This allows you to do things like
149
- # Resque.enqueue_at(timestamp, CustomJobClass, :opt1 => val1). Otherwise, pass off to Resque.
150
- if klass.respond_to?(:scheduled)
151
- klass.scheduled(queue, klass.to_s(), *args)
152
- else
153
- Resque::Job.create(queue, klass, *args)
154
- end
155
- else
156
- delayed_push(timestamp, job_to_hash_with_queue(queue, klass, args))
157
- end
158
-
159
- Plugin.run_after_schedule_hooks(klass, *args)
160
- end
161
-
162
- # Identical to enqueue_at but takes number_of_seconds_from_now
163
- # instead of a timestamp.
164
- def enqueue_in(number_of_seconds_from_now, klass, *args)
165
- enqueue_at(Time.now + number_of_seconds_from_now, klass, *args)
166
- end
167
-
168
- # Identical to +enqueue_in+, except you can also specify
169
- # a queue in which the job will be placed after the
170
- # number of seconds has passed.
171
- def enqueue_in_with_queue(queue, number_of_seconds_from_now, klass, *args)
172
- enqueue_at_with_queue(queue, Time.now + number_of_seconds_from_now, klass, *args)
173
- end
174
-
175
- # Used internally to stuff the item into the schedule sorted list.
176
- # +timestamp+ can be either in seconds or a datetime object
177
- # Insertion if O(log(n)).
178
- # Returns true if it's the first job to be scheduled at that time, else false
179
- def delayed_push(timestamp, item)
180
- # First add this item to the list for this timestamp
181
- redis.rpush("delayed:#{timestamp.to_i}", encode(item))
182
-
183
- # Store the timestamps at with this item occurs
184
- redis.sadd("timestamps:#{encode(item)}", "delayed:#{timestamp.to_i}")
185
-
186
- # Now, add this timestamp to the zsets. The score and the value are
187
- # the same since we'll be querying by timestamp, and we don't have
188
- # anything else to store.
189
- redis.zadd :delayed_queue_schedule, timestamp.to_i, timestamp.to_i
190
- end
191
-
192
- # Returns an array of timestamps based on start and count
193
- def delayed_queue_peek(start, count)
194
- Array(redis.zrange(:delayed_queue_schedule, start, start+count-1)).collect { |x| x.to_i }
195
- end
196
-
197
- # Returns the size of the delayed queue schedule
198
- def delayed_queue_schedule_size
199
- redis.zcard :delayed_queue_schedule
200
- end
201
-
202
- # Returns the number of jobs for a given timestamp in the delayed queue schedule
203
- def delayed_timestamp_size(timestamp)
204
- redis.llen("delayed:#{timestamp.to_i}").to_i
205
- end
206
-
207
- # Returns an array of delayed items for the given timestamp
208
- def delayed_timestamp_peek(timestamp, start, count)
209
- if 1 == count
210
- r = list_range "delayed:#{timestamp.to_i}", start, count
211
- r.nil? ? [] : [r]
212
- else
213
- list_range "delayed:#{timestamp.to_i}", start, count
214
- end
215
- end
216
-
217
- # Returns the next delayed queue timestamp
218
- # (don't call directly)
219
- def next_delayed_timestamp(at_time=nil)
220
- items = redis.zrangebyscore :delayed_queue_schedule, '-inf', (at_time || Time.now).to_i, :limit => [0, 1]
221
- timestamp = items.nil? ? nil : Array(items).first
222
- timestamp.to_i unless timestamp.nil?
223
- end
224
-
225
- # Returns the next item to be processed for a given timestamp, nil if
226
- # done. (don't call directly)
227
- # +timestamp+ can either be in seconds or a datetime
228
- def next_item_for_timestamp(timestamp)
229
- key = "delayed:#{timestamp.to_i}"
230
-
231
- encoded_item = redis.lpop(key)
232
- redis.srem("timestamps:#{encoded_item}", key)
233
- item = decode(encoded_item)
234
-
235
- # If the list is empty, remove it.
236
- clean_up_timestamp(key, timestamp)
237
- item
238
- end
239
-
240
- # Clears all jobs created with enqueue_at or enqueue_in
241
- def reset_delayed_queue
242
- Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |item|
243
- key = "delayed:#{item}"
244
- items = redis.lrange(key, 0, -1)
245
- redis.pipelined do
246
- items.each { |ts_item| redis.del("timestamps:#{ts_item}") }
247
- end
248
- redis.del key
249
- end
250
-
251
- redis.del :delayed_queue_schedule
252
- end
253
-
254
- # Given an encoded item, remove it from the delayed_queue
255
- def remove_delayed(klass, *args)
256
- search = encode(job_to_hash(klass, args))
257
- timestamps = redis.smembers("timestamps:#{search}")
258
-
259
- replies = redis.pipelined do
260
- timestamps.each do |key|
261
- redis.lrem(key, 0, search)
262
- redis.srem("timestamps:#{search}", key)
263
- end
264
- end
265
-
266
- (replies.nil? || replies.empty?) ? 0 : replies.each_slice(2).collect { |slice| slice.first }.inject(:+)
267
- end
268
-
269
- # Given an encoded item, enqueue it now
270
- def enqueue_delayed(klass, *args)
271
- hash = job_to_hash(klass, args)
272
- remove_delayed(klass, *args).times { Resque::Scheduler.enqueue_from_config(hash) }
273
- end
274
-
275
- # Given a timestamp and job (klass + args) it removes all instances and
276
- # returns the count of jobs removed.
277
- #
278
- # O(N) where N is the number of jobs scheduled to fire at the given
279
- # timestamp
280
- def remove_delayed_job_from_timestamp(timestamp, klass, *args)
281
- key = "delayed:#{timestamp.to_i}"
282
- encoded_job = encode(job_to_hash(klass, args))
283
-
284
- redis.srem("timestamps:#{encoded_job}", key)
285
- count = redis.lrem(key, 0, encoded_job)
286
- clean_up_timestamp(key, timestamp)
287
-
288
- count
289
- end
290
-
291
- def count_all_scheduled_jobs
292
- total_jobs = 0
293
- Array(redis.zrange(:delayed_queue_schedule, 0, -1)).each do |timestamp|
294
- total_jobs += redis.llen("delayed:#{timestamp}").to_i
295
- end
296
- total_jobs
297
- end
298
-
299
- # Returns delayed jobs schedule timestamp for +klass+, +args+.
300
- def scheduled_at(klass, *args)
301
- search = encode(job_to_hash(klass, args))
302
- redis.smembers("timestamps:#{search}").collect do |key|
303
- key.tr('delayed:', '').to_i
304
- end
305
- end
306
-
307
- private
308
-
309
- def job_to_hash(klass, args)
310
- {:class => klass.to_s, :args => args, :queue => queue_from_class(klass)}
311
- end
312
-
313
- def job_to_hash_with_queue(queue, klass, args)
314
- {:class => klass.to_s, :args => args, :queue => queue}
315
- end
316
-
317
- def clean_up_timestamp(key, timestamp)
318
- # If the list is empty, remove it.
319
-
320
- # Use a watch here to ensure nobody adds jobs to this delayed
321
- # queue while we're removing it.
322
- redis.watch key
323
- if 0 == redis.llen(key).to_i
324
- redis.multi do
325
- redis.del key
326
- redis.zrem :delayed_queue_schedule, timestamp.to_i
327
- end
328
- else
329
- redis.unwatch
330
- end
331
- end
332
-
333
- def validate_job!(klass)
334
- if klass.to_s.empty?
335
- raise Resque::NoClassError.new("Jobs must be given a class.")
336
- end
337
-
338
- unless queue_from_class(klass)
339
- raise Resque::NoQueueError.new("Jobs must be placed onto a queue.")
340
- end
341
- end
342
-
343
- def prepare_schedule(schedule_hash)
344
- prepared_hash = {}
345
- schedule_hash.each do |name, job_spec|
346
- job_spec = job_spec.dup
347
- job_spec['class'] = name unless job_spec.key?('class') || job_spec.key?(:class)
348
- prepared_hash[name] = job_spec
349
- end
350
- prepared_hash
351
- end
352
-
353
- end
354
-
355
- Resque.extend ResqueScheduler
@@ -1,14 +0,0 @@
1
- require 'redis'
2
- require 'resque'
3
-
4
- if ARGV.size != 1
5
- puts "migrate_to_timestamps_set.rb <redis-host:redis-port>"
6
- exit
7
- end
8
-
9
- Resque.redis = ARGV[0]
10
- redis = Resque.redis
11
- Array(redis.keys("delayed:*")).each do |key|
12
- jobs = redis.lrange(key, 0, -1)
13
- jobs.each {|job| redis.sadd("timestamps:#{job}", key)}
14
- end
@@ -1,2 +0,0 @@
1
- $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
- require 'resque_scheduler/tasks'