que-scheduler 3.2.5 → 3.4.0
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/README.md +66 -43
- data/lib/que/scheduler.rb +2 -0
- data/lib/que/scheduler/audit.rb +3 -2
- data/lib/que/scheduler/defined_job.rb +73 -30
- data/lib/que/scheduler/jobs/que_scheduler_audit_clear_down_job.rb +45 -0
- data/lib/que/scheduler/migrations/5/down.sql +1 -0
- data/lib/que/scheduler/migrations/5/up.sql +1 -0
- data/lib/que/scheduler/schedule.rb +29 -13
- data/lib/que/scheduler/scheduler_job.rb +18 -27
- data/lib/que/scheduler/state_checks.rb +17 -1
- data/lib/que/scheduler/to_enqueue.rb +157 -0
- data/lib/que/scheduler/version.rb +1 -1
- data/lib/que/scheduler/version_support.rb +46 -4
- metadata +49 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53cadc9fafa321124d4f52caa66503d8590cb1eaa11ee2ac86e0208c5d6a0fef
|
4
|
+
data.tar.gz: 2a5731c305089b58c3841f51b67c2dc698718e123a9b68395e92e1dfff37a693
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '087a32c4e9245c3bbaa26fb0e891f2b232c2617bad351d354eb502c0edb853a32ec786601a71e796fe5baa1b13f3691f1dc1c17f55d1b1973648101cd60602c7'
|
7
|
+
data.tar.gz: 1ba223766fa1d52b79b7ebcdcd534da366636cc306f538e6bc067830a2999b57913c1ade0f5a381287883222aedadc4aa380f9fb479223838468f7df3dfa5d73
|
data/README.md
CHANGED
@@ -22,12 +22,13 @@ needs to be run, enqueueing those jobs, then enqueueing itself to check again la
|
|
22
22
|
look for it is `config/que_schedule.yml`. They are essentially the same as resque-scheduler
|
23
23
|
files, but with additional features.
|
24
24
|
|
25
|
-
1. Add a migration to start the job scheduler and prepare the audit table.
|
25
|
+
1. Add a migration to start the job scheduler and prepare the audit table. Note that this migration
|
26
|
+
will fail if Que is set to execute jobs synchronously, i.e. `Que::Job.run_synchronously = true`.
|
26
27
|
|
27
28
|
```ruby
|
28
29
|
class CreateQueSchedulerSchema < ActiveRecord::Migration
|
29
30
|
def change
|
30
|
-
Que::Scheduler::Migrations.migrate!(version:
|
31
|
+
Que::Scheduler::Migrations.migrate!(version: 5)
|
31
32
|
end
|
32
33
|
end
|
33
34
|
```
|
@@ -65,10 +66,21 @@ BatchOrders:
|
|
65
66
|
cron: "0 0 * * *"
|
66
67
|
priority: 25
|
67
68
|
|
68
|
-
# Specify job arguments
|
69
|
+
# Specify array job arguments
|
69
70
|
SendOrders:
|
70
71
|
cron: "0 0 * * *"
|
71
72
|
args: ['open']
|
73
|
+
|
74
|
+
# Specify hash job arguments. Note, this appears as a single hash to `run`, not as kwargs.
|
75
|
+
SendPreorders:
|
76
|
+
cron: "0 0 * * *"
|
77
|
+
args:
|
78
|
+
order_type: special
|
79
|
+
|
80
|
+
# Specify a single nil argument
|
81
|
+
SendPostorders:
|
82
|
+
cron: "0 0 * * *"
|
83
|
+
args: ~ # See https://stackoverflow.com/a/51990876/1267203
|
72
84
|
|
73
85
|
# Use simpler cron syntax
|
74
86
|
SendBilling:
|
@@ -136,8 +148,54 @@ end
|
|
136
148
|
|
137
149
|
## Scheduler Audit
|
138
150
|
|
139
|
-
An audit table
|
140
|
-
|
151
|
+
An audit table `que_scheduler_audit` is written to by the scheduler to keep a history of when the
|
152
|
+
scheduler ran to calculate what was necessary to run (if anything). It is created by the included
|
153
|
+
migration tasks.
|
154
|
+
|
155
|
+
Additionally, there is the audit table `que_scheduler_audit_enqueued`. This logs every job that
|
156
|
+
the scheduler enqueues.
|
157
|
+
|
158
|
+
When there is a major version (breaking) change, a migration should be run in. The version of the
|
159
|
+
migration proceeds at a faster rate than the version of the gem. To run in all the migrations required
|
160
|
+
up to a number, just migrate to that number with one line, and it will perform all the intermediary steps.
|
161
|
+
|
162
|
+
ie, This will perform all migrations necessary up to the latest version, skipping any already
|
163
|
+
performed.
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
class CreateQueSchedulerSchema < ActiveRecord::Migration
|
167
|
+
def change
|
168
|
+
Que::Scheduler::Migrations.migrate!(version: 5)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
```
|
172
|
+
|
173
|
+
The changes in past migrations were:
|
174
|
+
|
175
|
+
| Version | Changes |
|
176
|
+
|:-------:|---------------------------------------------------------------------------------|
|
177
|
+
| 1 | Enqueued the main Que::Scheduler. This is the job that performs the scheduling. |
|
178
|
+
| 2 | Added the audit table `que_scheduler_audit`. |
|
179
|
+
| 3 | Added the audit table `que_scheduler_audit_enqueued`. |
|
180
|
+
| 4 | Updated the the audit tables to use bigints |
|
181
|
+
| 5 | Dropped an unnecessary index |
|
182
|
+
|
183
|
+
## Built in optional job for audit clear down
|
184
|
+
|
185
|
+
que-scheduler comes with the `QueSchedulerAuditClearDownJob` job built in that you can optionally
|
186
|
+
schedule to clear down audit rows if you don't need to retain them indefinitely. You should add this
|
187
|
+
to your own scheduler config yaml.
|
188
|
+
|
189
|
+
For example:
|
190
|
+
|
191
|
+
```yaml
|
192
|
+
# This will clear down the oldest que-scheduler audit rows. Since que-scheduler
|
193
|
+
# runs approximately every minute, 129600 is 90 days.
|
194
|
+
Que::Scheduler::Jobs::QueSchedulerAuditClearDownJob:
|
195
|
+
cron: "0 0 * * *"
|
196
|
+
args:
|
197
|
+
retain_row_count: 129600
|
198
|
+
```
|
141
199
|
|
142
200
|
## HA Redundancy and DB restores
|
143
201
|
|
@@ -172,23 +230,6 @@ then reschedules itself. The flow is as follows:
|
|
172
230
|
1. After a deploy that changes the schedule, the job notices any new jobs to schedule, and knows which
|
173
231
|
ones to forget. It does not need to be re-enqueued or restarted.
|
174
232
|
|
175
|
-
## DB Migrations
|
176
|
-
|
177
|
-
When there is a major version (breaking) change, a migration should be run in. The version of the
|
178
|
-
migration proceeds at a faster rate than the version of the gem. To run in all the migrations required
|
179
|
-
up to a number, just migrate to that number with one line, and it will perform all the intermediary steps.
|
180
|
-
|
181
|
-
ie, `Que::Scheduler::Migrations.migrate!(version: 4)` will perform all migrations necessary to
|
182
|
-
reach migration version `4`.
|
183
|
-
|
184
|
-
As of migration `4`, two elements are added to the DB for que-scheduler to run.
|
185
|
-
|
186
|
-
1. The first is the scheduler job itself, which runs forever, re-enqueuing itself to performs its
|
187
|
-
duties.
|
188
|
-
1. The second part comprises the audit table `que_scheduler_audit` and the "enqueued" table
|
189
|
-
`que_scheduler_audit_enqueued`. The first tracks when the scheduler calculated what was necessary to run
|
190
|
-
(if anything). The second then logs every job that the scheduler enqueues.
|
191
|
-
|
192
233
|
## Testing Configuration
|
193
234
|
|
194
235
|
You can add tests to validate your configuration during the spec phase. This will perform a variety
|
@@ -222,27 +263,6 @@ The scheduler will then continue to retry indefinitely.
|
|
222
263
|
que-scheduler uses [semantic versioning](https://semver.org/), so major version changes will usually
|
223
264
|
require additional actions to be taken upgrading from one major version to another.
|
224
265
|
|
225
|
-
Major feature changes are listed below. The full
|
226
|
-
[CHANGELOG](https://github.com/hlascelles/que-scheduler/blob/master/CHANGELOG.md) can be found in
|
227
|
-
the root of the project.
|
228
|
-
|
229
|
-
#### Versions 3.x
|
230
|
-
- Addition of a config initializer.
|
231
|
-
- Addition of numerous extra columns to the audit table.
|
232
|
-
- Drop support for ruby 2.1 and rails 3.x
|
233
|
-
- Required cumulative migration: `Que::Scheduler::Migrations.migrate!(version: 4)`
|
234
|
-
#### Versions 2.x
|
235
|
-
- Introduction of the audit table.
|
236
|
-
- Support for older versions of postgres
|
237
|
-
- Required cumulative migration: `Que::Scheduler::Migrations.migrate!(version: 3)`
|
238
|
-
#### Versions 1.x
|
239
|
-
- Sequel support
|
240
|
-
- Config specified Timezone support
|
241
|
-
- Required migration adding the initial job: `Que::Scheduler::SchedulerJob.enqueue`
|
242
|
-
#### Versions 0.x
|
243
|
-
- The first public release.
|
244
|
-
- Required migration adding the initial job: `Que::Scheduler::SchedulerJob.enqueue`
|
245
|
-
|
246
266
|
## System requirements
|
247
267
|
|
248
268
|
Your [postgres](https://www.postgresql.org/) database must be at least version 9.4.0.
|
@@ -255,3 +275,6 @@ This gem was inspired by the makers of the excellent [Que](https://github.com/ch
|
|
255
275
|
|
256
276
|
* @jish
|
257
277
|
* @joehorsnell
|
278
|
+
* @bnauta
|
279
|
+
* @papodaca
|
280
|
+
* @krzyzak
|
data/lib/que/scheduler.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'que/scheduler/version'
|
2
|
+
require 'que/scheduler/version_support'
|
2
3
|
require 'que/scheduler/config'
|
3
4
|
require 'que/scheduler/scheduler_job'
|
4
5
|
require 'que/scheduler/db'
|
5
6
|
require 'que/scheduler/audit'
|
6
7
|
require 'que/scheduler/migrations'
|
8
|
+
require 'que/scheduler/jobs/que_scheduler_audit_clear_down_job'
|
data/lib/que/scheduler/audit.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'to_enqueue'
|
4
|
+
|
3
5
|
module Que
|
4
6
|
module Scheduler
|
5
7
|
module Audit
|
@@ -24,11 +26,10 @@ module Que
|
|
24
26
|
def append(scheduler_job_id, executed_at, enqueued_jobs)
|
25
27
|
::Que::Scheduler::VersionSupport.execute(INSERT_AUDIT, [scheduler_job_id, executed_at])
|
26
28
|
enqueued_jobs.each do |j|
|
27
|
-
attrs = Que::Scheduler::VersionSupport.job_attributes(j)
|
28
29
|
inserted = ::Que::Scheduler::VersionSupport.execute(
|
29
30
|
INSERT_AUDIT_ENQUEUED,
|
30
31
|
[scheduler_job_id] +
|
31
|
-
|
32
|
+
j.values_at(:job_class, :queue, :priority, :args, :job_id, :run_at)
|
32
33
|
)
|
33
34
|
raise "Cannot save audit row #{scheduler_job_id} #{executed_at} #{j}" if inserted.empty?
|
34
35
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'hashie'
|
2
2
|
require 'fugit'
|
3
|
-
require 'backports/2.4.0/hash/compact'
|
4
3
|
|
5
4
|
# This is the definition of one scheduleable job in the que-scheduler config yml file.
|
6
5
|
module Que
|
@@ -10,23 +9,23 @@ module Que
|
|
10
9
|
|
11
10
|
DEFINED_JOB_TYPES = [
|
12
11
|
DEFINED_JOB_TYPE_DEFAULT = :default,
|
13
|
-
DEFINED_JOB_TYPE_EVERY_EVENT = :every_event
|
12
|
+
DEFINED_JOB_TYPE_EVERY_EVENT = :every_event,
|
14
13
|
].freeze
|
15
14
|
|
16
|
-
property :name
|
17
|
-
property :job_class,
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
property :
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
15
|
+
property :name
|
16
|
+
property :job_class, transform_with: ->(v) { Object.const_get(v) }
|
17
|
+
property :cron, transform_with: ->(v) { Fugit::Cron.parse(v) }
|
18
|
+
property :queue
|
19
|
+
property :priority
|
20
|
+
property :args_array
|
21
|
+
property :schedule_type, default: DEFINED_JOB_TYPE_DEFAULT
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def create(options)
|
25
|
+
defined_job = new(options.compact)
|
26
|
+
defined_job.freeze.tap { |dj| dj.validate(options) }
|
27
|
+
end
|
28
|
+
end
|
30
29
|
|
31
30
|
# Given a "last time", return the next Time the event will occur, or nil if it
|
32
31
|
# is after "to".
|
@@ -49,36 +48,80 @@ module Que
|
|
49
48
|
generate_to_enqueue_list(missed_times)
|
50
49
|
end
|
51
50
|
|
52
|
-
|
53
|
-
|
51
|
+
def validate(options)
|
52
|
+
validate_fields_presence(options)
|
53
|
+
validate_fields_types(options)
|
54
|
+
validate_job_class_related(options)
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
54
58
|
|
55
|
-
|
56
|
-
|
57
|
-
|
59
|
+
# rubocop:disable Style/GuardClause This reads better as a conditional
|
60
|
+
def validate_fields_types(options)
|
61
|
+
unless queue.nil? || queue.is_a?(String)
|
62
|
+
err_field(:queue, options, 'queue must be a string')
|
63
|
+
end
|
64
|
+
unless priority.nil? || priority.is_a?(Integer)
|
65
|
+
err_field(:priority, options, 'priority must be an integer')
|
66
|
+
end
|
67
|
+
unless DEFINED_JOB_TYPES.include?(schedule_type)
|
68
|
+
err_field(:schedule_type, options, "Not in #{DEFINED_JOB_TYPES}")
|
58
69
|
end
|
59
70
|
end
|
71
|
+
# rubocop:enable Style/GuardClause
|
60
72
|
|
61
|
-
|
73
|
+
def validate_fields_presence(options)
|
74
|
+
err_field(:name, options, 'name must be present') if name.nil?
|
75
|
+
err_field(:job_class, options, 'job_class must be present') if job_class.nil?
|
76
|
+
# An invalid cron is nil
|
77
|
+
err_field(:cron, options, 'cron must be present') if cron.nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_job_class_related(options)
|
81
|
+
# Only support known job engines
|
82
|
+
unless Que::Scheduler::ToEnqueue.valid_job_class?(job_class)
|
83
|
+
err_field(:job_class, options, "Job #{job_class} was not a supported job type")
|
84
|
+
end
|
85
|
+
|
86
|
+
# queue name is only supported for a subrange of ActiveJob versions. Print this out as a
|
87
|
+
# warning.
|
88
|
+
if queue &&
|
89
|
+
Que::Scheduler::ToEnqueue.active_job_sufficient_version? &&
|
90
|
+
job_class < ::ActiveJob::Base &&
|
91
|
+
Que::Scheduler::ToEnqueue.active_job_version < Gem::Version.create('6.0.3')
|
92
|
+
puts <<-ERR
|
93
|
+
WARNING from que-scheduler....
|
94
|
+
Between versions 4.2.3 and 6.0.2 (inclusive) Rails did not support setting queue names
|
95
|
+
on que jobs with ActiveJob, so que-scheduler cannot support it.
|
96
|
+
See removed in Rails 4.2.3
|
97
|
+
https://github.com/rails/rails/pull/19498
|
98
|
+
And readded in Rails 6.0.3
|
99
|
+
https://github.com/rails/rails/pull/38635
|
100
|
+
|
101
|
+
Please remove all "queue" keys from ActiveJobs defined in the que-scheduler.yml config.
|
102
|
+
Specifically #{queue} for job #{name}.
|
103
|
+
ERR
|
104
|
+
end
|
105
|
+
end
|
62
106
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
107
|
+
def err_field(field, options, reason = '')
|
108
|
+
schedule = Que::Scheduler.configuration.schedule_location
|
109
|
+
value = options[field]
|
110
|
+
raise "Invalid #{field} '#{value}' for '#{name}' in que-scheduler schedule #{schedule}.\n" \
|
111
|
+
"#{reason}"
|
68
112
|
end
|
69
113
|
|
70
114
|
def generate_to_enqueue_list(missed_times)
|
71
115
|
return [] if missed_times.empty?
|
72
116
|
|
73
117
|
options = to_h.slice(:args, :queue, :priority, :job_class).compact
|
74
|
-
args_array = args.is_a?(Array) ? args : Array(args)
|
75
118
|
|
76
119
|
if schedule_type == DefinedJob::DEFINED_JOB_TYPE_EVERY_EVENT
|
77
120
|
missed_times.map do |time_missed|
|
78
|
-
ToEnqueue.
|
121
|
+
ToEnqueue.create(options.merge(args: [time_missed] + args_array))
|
79
122
|
end
|
80
123
|
else
|
81
|
-
[ToEnqueue.
|
124
|
+
[ToEnqueue.create(options.merge(args: args_array))]
|
82
125
|
end
|
83
126
|
end
|
84
127
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'que'
|
2
|
+
|
3
|
+
# This job can optionally be scheduled to clear down the que-scheduler audit log if it
|
4
|
+
# isn't required in the long term.
|
5
|
+
module Que
|
6
|
+
module Scheduler
|
7
|
+
module Jobs
|
8
|
+
class QueSchedulerAuditClearDownJob < Que::Job
|
9
|
+
class << self
|
10
|
+
def build_sql(table_name)
|
11
|
+
<<-SQL
|
12
|
+
WITH deleted AS (
|
13
|
+
DELETE FROM #{table_name}
|
14
|
+
WHERE scheduler_job_id <= (
|
15
|
+
SELECT scheduler_job_id FROM que_scheduler_audit
|
16
|
+
ORDER BY scheduler_job_id DESC
|
17
|
+
LIMIT 1 OFFSET $1
|
18
|
+
) RETURNING *
|
19
|
+
) SELECT count(*) FROM deleted;
|
20
|
+
SQL
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
DELETE_AUDIT_ENQUEUED_SQL = build_sql('que_scheduler_audit_enqueued').freeze
|
25
|
+
DELETE_AUDIT_SQL = build_sql('que_scheduler_audit').freeze
|
26
|
+
|
27
|
+
# Very low priority
|
28
|
+
Que::Scheduler::VersionSupport.set_priority(self, 100)
|
29
|
+
|
30
|
+
def run(options)
|
31
|
+
retain_row_count = options.fetch(:retain_row_count)
|
32
|
+
Que::Scheduler::Db.transaction do
|
33
|
+
# This may delete zero or more than `retain_row_count` depending on if anything was
|
34
|
+
# scheduled in each of the past schedule runs
|
35
|
+
Que::Scheduler::VersionSupport.execute(DELETE_AUDIT_ENQUEUED_SQL, [retain_row_count])
|
36
|
+
# This will delete all but `retain_row_count` oldest rows
|
37
|
+
count = Que::Scheduler::VersionSupport.execute(DELETE_AUDIT_SQL, [retain_row_count])
|
38
|
+
log = "#{self.class} cleared down #{count.first.fetch(:count)} rows"
|
39
|
+
::Que.log(event: 'que-scheduler'.to_sym, message: log)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
CREATE UNIQUE INDEX index_que_scheduler_audit_on_scheduler_job_id ON que_scheduler_audit USING btree (scheduler_job_id);
|
@@ -0,0 +1 @@
|
|
1
|
+
DROP INDEX index_que_scheduler_audit_on_scheduler_job_id;
|
@@ -24,20 +24,36 @@ module Que
|
|
24
24
|
end.to_h
|
25
25
|
end
|
26
26
|
|
27
|
-
private
|
28
|
-
|
29
27
|
def hash_item_to_defined_job(name, defined_job_hash)
|
30
|
-
Que
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
28
|
+
# Que stores arguments as a json array. If the args we have to provide are already an
|
29
|
+
# array we can can simply pass them through. If it is a single non-nil value, then we make
|
30
|
+
# an array with one item which is that value (this includes if it is a hash). It could
|
31
|
+
# also be a single nil value.
|
32
|
+
args_array =
|
33
|
+
if !defined_job_hash.key?('args')
|
34
|
+
# No args were requested
|
35
|
+
[]
|
36
|
+
else
|
37
|
+
args = defined_job_hash['args']
|
38
|
+
if args.is_a?(Array)
|
39
|
+
# An array of args was requested
|
40
|
+
args
|
41
|
+
else
|
42
|
+
# A single value, a nil, or a hash was requested. que expects this to
|
43
|
+
# be enqueued as an array of 1 item
|
44
|
+
[args]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Que::Scheduler::DefinedJob.create(
|
49
|
+
name: name,
|
50
|
+
job_class: defined_job_hash['class'] || name,
|
51
|
+
queue: defined_job_hash['queue'],
|
52
|
+
args_array: args_array,
|
53
|
+
priority: defined_job_hash['priority'],
|
54
|
+
cron: defined_job_hash['cron'],
|
55
|
+
schedule_type: defined_job_hash['schedule_type']&.to_sym
|
56
|
+
)
|
41
57
|
end
|
42
58
|
end
|
43
59
|
end
|
@@ -4,6 +4,7 @@ require_relative 'schedule'
|
|
4
4
|
require_relative 'enqueueing_calculator'
|
5
5
|
require_relative 'scheduler_job_args'
|
6
6
|
require_relative 'state_checks'
|
7
|
+
require_relative 'to_enqueue'
|
7
8
|
require_relative 'version_support'
|
8
9
|
|
9
10
|
# The main job that runs every minute, determining what needs to be enqueued, enqueues the required
|
@@ -13,8 +14,8 @@ module Que
|
|
13
14
|
class SchedulerJob < Que::Job
|
14
15
|
SCHEDULER_FREQUENCY = 60
|
15
16
|
|
16
|
-
# Always highest possible priority.
|
17
17
|
Que::Scheduler::VersionSupport.set_priority(self, 0)
|
18
|
+
Que::Scheduler::VersionSupport.apply_retry_semantics(self)
|
18
19
|
|
19
20
|
def run(options = nil)
|
20
21
|
Que::Scheduler::Db.transaction do
|
@@ -22,46 +23,36 @@ module Que
|
|
22
23
|
|
23
24
|
scheduler_job_args = SchedulerJobArgs.build(options)
|
24
25
|
logs = ["que-scheduler last ran at #{scheduler_job_args.last_run_time}."]
|
25
|
-
|
26
26
|
result = EnqueueingCalculator.parse(Scheduler.schedule.values, scheduler_job_args)
|
27
27
|
enqueued_jobs = enqueue_required_jobs(result, logs)
|
28
28
|
enqueue_self_again(
|
29
29
|
scheduler_job_args, scheduler_job_args.as_time, result.job_dictionary, enqueued_jobs
|
30
30
|
)
|
31
|
-
|
32
31
|
# Only now we're sure nothing errored, log the results
|
33
|
-
logs.each { |str| ::Que.log(message: str) }
|
32
|
+
logs.each { |str| ::Que.log(event: 'que-scheduler'.to_sym, message: str) }
|
34
33
|
destroy
|
35
34
|
end
|
36
35
|
end
|
37
36
|
|
38
|
-
def enqueue_required_jobs(
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
enqueued_job =
|
44
|
-
if args.is_a?(Hash)
|
45
|
-
job_class.enqueue(args.merge(remaining_hash))
|
46
|
-
else
|
47
|
-
job_class.enqueue(*args, remaining_hash)
|
48
|
-
end
|
49
|
-
check_enqueued_job(enqueued_job, job_class, args, logs)
|
37
|
+
def enqueue_required_jobs(calculator_result, logs)
|
38
|
+
calculator_result.missed_jobs.map do |to_enqueue|
|
39
|
+
to_enqueue.enqueue.tap do |enqueued_job|
|
40
|
+
check_enqueued_job(to_enqueue, enqueued_job, logs)
|
41
|
+
end
|
50
42
|
end.compact
|
51
43
|
end
|
52
44
|
|
53
45
|
private
|
54
46
|
|
55
|
-
def check_enqueued_job(
|
56
|
-
if enqueued_job.
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
47
|
+
def check_enqueued_job(to_enqueue, enqueued_job, logs)
|
48
|
+
logs << if enqueued_job.present?
|
49
|
+
"que-scheduler enqueueing #{enqueued_job.job_class} " \
|
50
|
+
"#{enqueued_job.job_id} with args: #{enqueued_job.args}"
|
51
|
+
else
|
52
|
+
# This can happen if a middleware nixes the enqueue call
|
53
|
+
"que-scheduler called enqueue on #{to_enqueue.job_class} " \
|
54
|
+
'but it reported no job was scheduled. Has `enqueue` been overridden?'
|
55
|
+
end
|
65
56
|
end
|
66
57
|
|
67
58
|
def enqueue_self_again(scheduler_job_args, last_full_execution, job_dictionary, enqueued_jobs)
|
@@ -79,7 +70,7 @@ module Que
|
|
79
70
|
)
|
80
71
|
|
81
72
|
# rubocop:disable Style/GuardClause This reads better as a conditional
|
82
|
-
unless
|
73
|
+
unless Que::Scheduler::VersionSupport.job_attributes(enqueued_job).fetch(:job_id)
|
83
74
|
raise 'SchedulerJob could not self-schedule. Has `.enqueue` been monkey patched?'
|
84
75
|
end
|
85
76
|
# rubocop:enable Style/GuardClause
|
@@ -17,9 +17,22 @@ module Que
|
|
17
17
|
db_version = Que::Scheduler::Migrations.db_version
|
18
18
|
return if db_version == Que::Scheduler::Migrations::MAX_VERSION
|
19
19
|
|
20
|
+
sync_err =
|
21
|
+
if Que::Scheduler::VersionSupport.running_synchronously? && db_version.zero?
|
22
|
+
code = Que::Scheduler::VersionSupport.running_synchronously_code?
|
23
|
+
<<-ERR_SYNC
|
24
|
+
You currently have Que to run in synchronous mode using
|
25
|
+
#{code}, so it is most likely this error
|
26
|
+
has happened during an initial migration. You should disable synchronous mode and
|
27
|
+
try again. Note, que-scheduler uses "forward time" scheduled jobs, so will not work
|
28
|
+
in synchronous mode.
|
29
|
+
|
30
|
+
ERR_SYNC
|
31
|
+
end
|
32
|
+
|
20
33
|
raise(<<-ERR)
|
21
34
|
The que-scheduler db migration state was found to be #{db_version}. It should be #{Que::Scheduler::Migrations::MAX_VERSION}.
|
22
|
-
|
35
|
+
#{sync_err}
|
23
36
|
que-scheduler adds some tables to the DB to provide an audit history of what was
|
24
37
|
enqueued when, and with what options and arguments. The structure of these tables is
|
25
38
|
versioned, and should match that version required by the gem.
|
@@ -42,6 +55,9 @@ module Que
|
|
42
55
|
Que::Scheduler::Migrations.migrate!(version: #{Que::Scheduler::Migrations::MAX_VERSION})
|
43
56
|
end
|
44
57
|
end
|
58
|
+
|
59
|
+
It is also possible that you are running a migration with Que set up to execute jobs
|
60
|
+
synchronously. This will fail as que-scheduler needs the above tables to work.
|
45
61
|
ERR
|
46
62
|
end
|
47
63
|
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'que'
|
2
|
+
|
3
|
+
# This module uses polymorphic dispatch to centralise the differences between supporting Que::Job
|
4
|
+
# and other job systems.
|
5
|
+
module Que
|
6
|
+
module Scheduler
|
7
|
+
class ToEnqueue < Hashie::Dash
|
8
|
+
property :args, required: true, default: []
|
9
|
+
property :queue
|
10
|
+
property :priority
|
11
|
+
property :run_at, required: true
|
12
|
+
property :job_class, required: true
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def create(options)
|
16
|
+
type_from_job_class(options.fetch(:job_class)).new(
|
17
|
+
options.merge(run_at: Que::Scheduler::Db.now)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_job_class?(job_class)
|
22
|
+
type_from_job_class(job_class).present?
|
23
|
+
end
|
24
|
+
|
25
|
+
def active_job_version
|
26
|
+
Gem.loaded_specs['activejob']&.version
|
27
|
+
end
|
28
|
+
|
29
|
+
def active_job_sufficient_version?
|
30
|
+
# ActiveJob 4.x does not support job_ids correctly
|
31
|
+
# https://github.com/rails/rails/pull/20056/files
|
32
|
+
active_job_version && active_job_version > Gem::Version.create('5')
|
33
|
+
end
|
34
|
+
|
35
|
+
def active_job_version_supports_queues?
|
36
|
+
# Supporting queue name in ActiveJob was removed in Rails 4.2.3
|
37
|
+
# https://github.com/rails/rails/pull/19498
|
38
|
+
# and readded in Rails 6.0.3
|
39
|
+
# https://github.com/rails/rails/pull/38635
|
40
|
+
ToEnqueue.active_job_version && ToEnqueue.active_job_version >=
|
41
|
+
Gem::Version.create('6.0.3')
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def type_from_job_class(job_class)
|
47
|
+
types.each do |type, implementation|
|
48
|
+
return implementation if job_class < type
|
49
|
+
end
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def types
|
54
|
+
@types ||=
|
55
|
+
begin
|
56
|
+
hash = {
|
57
|
+
::Que::Job => QueJobType,
|
58
|
+
}
|
59
|
+
hash[::ActiveJob::Base] = ActiveJobType if ToEnqueue.active_job_sufficient_version?
|
60
|
+
hash
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# For jobs of type Que::Job
|
67
|
+
class QueJobType < ToEnqueue
|
68
|
+
def enqueue
|
69
|
+
job_settings = to_h.slice(:queue, :priority, :run_at).compact
|
70
|
+
job =
|
71
|
+
if args.is_a?(Hash)
|
72
|
+
job_class.enqueue(**args.merge(job_settings))
|
73
|
+
else
|
74
|
+
job_class.enqueue(*args, **job_settings)
|
75
|
+
end
|
76
|
+
|
77
|
+
return nil if job.nil? || !job # nil in Rails < 6.1, false after.
|
78
|
+
|
79
|
+
# Now read the just inserted job back out of the DB to get the actual values that will
|
80
|
+
# be used when the job is worked.
|
81
|
+
values = Que::Scheduler::VersionSupport.job_attributes(job).slice(
|
82
|
+
:args, :queue, :priority, :run_at, :job_class, :job_id
|
83
|
+
)
|
84
|
+
EnqueuedJobType.new(values)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# For jobs of type ActiveJob
|
89
|
+
class ActiveJobType < ToEnqueue
|
90
|
+
def enqueue
|
91
|
+
job = enqueue_active_job
|
92
|
+
|
93
|
+
return nil if job.nil? || !job # nil in Rails < 6.1, false after.
|
94
|
+
|
95
|
+
enqueued_values = calculate_enqueued_values(job)
|
96
|
+
EnqueuedJobType.new(enqueued_values)
|
97
|
+
end
|
98
|
+
|
99
|
+
def calculate_enqueued_values(job)
|
100
|
+
# Now read the just inserted job back out of the DB to get the actual values that will
|
101
|
+
# be used when the job is worked.
|
102
|
+
data = JSON.parse(job.to_json, symbolize_names: true)
|
103
|
+
|
104
|
+
# ActiveJob scheduled_at is returned as a float, where we want a Time for consistency
|
105
|
+
scheduled_at =
|
106
|
+
begin
|
107
|
+
scheduled_at_float = data[:scheduled_at]
|
108
|
+
scheduled_at_float ? Time.zone.at(scheduled_at_float) : nil
|
109
|
+
end
|
110
|
+
|
111
|
+
# Rails didn't support queues for ActiveJob for a while
|
112
|
+
used_queue = data[:queue_name] if ToEnqueue.active_job_version_supports_queues?
|
113
|
+
|
114
|
+
# We can't get the priority out of the DB, as the returned `job` doesn't give us access
|
115
|
+
# to the underlying ActiveJob that was scheduled. We have no option but to assume
|
116
|
+
# it was what we told it to use. If no priority was specified, we must assume it was
|
117
|
+
# the Que default, which is 100 t.ly/1jRK5
|
118
|
+
assume_used_priority = priority.nil? ? 100 : priority
|
119
|
+
|
120
|
+
{
|
121
|
+
args: data.fetch(:arguments),
|
122
|
+
queue: used_queue,
|
123
|
+
priority: assume_used_priority,
|
124
|
+
run_at: scheduled_at,
|
125
|
+
job_class: job_class.to_s,
|
126
|
+
job_id: data.fetch(:provider_job_id),
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
def enqueue_active_job
|
131
|
+
job_settings = {
|
132
|
+
priority: priority,
|
133
|
+
wait_until: run_at,
|
134
|
+
queue: queue || Que::Scheduler::VersionSupport.default_scheduler_queue,
|
135
|
+
}.compact
|
136
|
+
|
137
|
+
job_class_set = job_class.set(**job_settings)
|
138
|
+
if args.is_a?(Hash)
|
139
|
+
job_class_set.perform_later(**args)
|
140
|
+
else
|
141
|
+
job_class_set.perform_later(*args)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# A value object returned after a job has been enqueued. This is necessary as Que (normal) and
|
147
|
+
# ActiveJob return very different objects from the `enqueue` call.
|
148
|
+
class EnqueuedJobType < Hashie::Dash
|
149
|
+
property :args
|
150
|
+
property :queue
|
151
|
+
property :priority
|
152
|
+
property :run_at, required: true
|
153
|
+
property :job_class, required: true
|
154
|
+
property :job_id, required: true
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -5,25 +5,67 @@ require 'que'
|
|
5
5
|
module Que
|
6
6
|
module Scheduler
|
7
7
|
module VersionSupport
|
8
|
+
RETRY_PROC = proc { |count|
|
9
|
+
# Maximum one hour, otherwise use the default backoff
|
10
|
+
count > 7 ? (60 * 60) : ((count**4) + 3)
|
11
|
+
}
|
12
|
+
|
8
13
|
class << self
|
14
|
+
# Ensure que-scheduler runs at the highest priority. This is because its priority is a
|
15
|
+
# the top of all jobs it enqueues.
|
9
16
|
def set_priority(context, priority)
|
10
|
-
|
17
|
+
if zero_major?
|
18
|
+
context.instance_variable_set('@priority', priority)
|
19
|
+
else
|
20
|
+
context.priority = priority
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Ensure the job runs at least once an hour when it is backing off due to errors
|
25
|
+
def apply_retry_semantics(context)
|
26
|
+
if zero_major?
|
27
|
+
context.instance_variable_set('@retry_interval', RETRY_PROC)
|
28
|
+
else
|
29
|
+
context.maximum_retry_count = 1 << 128 # Heat death of universe
|
30
|
+
context.retry_interval = RETRY_PROC
|
31
|
+
end
|
11
32
|
end
|
12
33
|
|
13
34
|
def job_attributes(enqueued_job)
|
14
|
-
|
35
|
+
if zero_major?
|
36
|
+
enqueued_job.attrs.transform_keys(&:to_sym)
|
37
|
+
else
|
38
|
+
enqueued_job.que_attrs.transform_keys(&:to_sym).tap do |hash|
|
39
|
+
hash[:job_id] = hash.delete(:id)
|
40
|
+
end
|
41
|
+
end
|
15
42
|
end
|
16
43
|
|
17
|
-
# Between Que 0.x and 1.x the result of
|
44
|
+
# Between Que 0.x and 1.x the result of Que execute changed keys from strings to symbols.
|
18
45
|
# Here we wrap the concept and make sure either way produces symbols
|
19
46
|
def execute(str, args = [])
|
20
47
|
normalise_array_of_hashes(Que.execute(str, args))
|
21
48
|
end
|
22
49
|
|
23
50
|
def default_scheduler_queue
|
24
|
-
''
|
51
|
+
zero_major? ? '' : Que::DEFAULT_QUEUE
|
52
|
+
end
|
53
|
+
|
54
|
+
def running_synchronously?
|
55
|
+
zero_major? ? (Que.mode == :sync) : Que.run_synchronously
|
25
56
|
end
|
26
57
|
|
58
|
+
def running_synchronously_code?
|
59
|
+
zero_major? ? 'Que.mode == :sync' : 'Que.run_synchronously = true'
|
60
|
+
end
|
61
|
+
|
62
|
+
def zero_major?
|
63
|
+
# This is the only way to handle beta releases too
|
64
|
+
@zero_major ||= Gem.loaded_specs['que'].version.to_s.split('.').first.to_i.zero?
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
27
69
|
def normalise_array_of_hashes(array)
|
28
70
|
array.map { |row| row.transform_keys(&:to_sym) }
|
29
71
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: que-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Harry Lascelles
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: backports
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '3.10'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '3.10'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: fugit
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,30 +48,42 @@ dependencies:
|
|
62
48
|
name: hashie
|
63
49
|
requirement: !ruby/object:Gem::Requirement
|
64
50
|
requirements:
|
65
|
-
- - "
|
51
|
+
- - ">="
|
66
52
|
- !ruby/object:Gem::Version
|
67
53
|
version: '3'
|
54
|
+
- - "<"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '5'
|
68
57
|
type: :runtime
|
69
58
|
prerelease: false
|
70
59
|
version_requirements: !ruby/object:Gem::Requirement
|
71
60
|
requirements:
|
72
|
-
- - "
|
61
|
+
- - ">="
|
73
62
|
- !ruby/object:Gem::Version
|
74
63
|
version: '3'
|
64
|
+
- - "<"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '5'
|
75
67
|
- !ruby/object:Gem::Dependency
|
76
68
|
name: que
|
77
69
|
requirement: !ruby/object:Gem::Requirement
|
78
70
|
requirements:
|
79
|
-
- - "
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0.12'
|
74
|
+
- - "<="
|
80
75
|
- !ruby/object:Gem::Version
|
81
|
-
version:
|
76
|
+
version: 1.0.0.beta4
|
82
77
|
type: :runtime
|
83
78
|
prerelease: false
|
84
79
|
version_requirements: !ruby/object:Gem::Requirement
|
85
80
|
requirements:
|
86
|
-
- - "
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.12'
|
84
|
+
- - "<="
|
87
85
|
- !ruby/object:Gem::Version
|
88
|
-
version:
|
86
|
+
version: 1.0.0.beta4
|
89
87
|
- !ruby/object:Gem::Dependency
|
90
88
|
name: activerecord
|
91
89
|
requirement: !ruby/object:Gem::Requirement
|
@@ -199,21 +197,21 @@ dependencies:
|
|
199
197
|
- !ruby/object:Gem::Version
|
200
198
|
version: '0'
|
201
199
|
- !ruby/object:Gem::Dependency
|
202
|
-
name:
|
200
|
+
name: rake
|
203
201
|
requirement: !ruby/object:Gem::Requirement
|
204
202
|
requirements:
|
205
|
-
- - "
|
203
|
+
- - ">="
|
206
204
|
- !ruby/object:Gem::Version
|
207
|
-
version:
|
205
|
+
version: '0'
|
208
206
|
type: :development
|
209
207
|
prerelease: false
|
210
208
|
version_requirements: !ruby/object:Gem::Requirement
|
211
209
|
requirements:
|
212
|
-
- - "
|
210
|
+
- - ">="
|
213
211
|
- !ruby/object:Gem::Version
|
214
|
-
version:
|
212
|
+
version: '0'
|
215
213
|
- !ruby/object:Gem::Dependency
|
216
|
-
name:
|
214
|
+
name: reek
|
217
215
|
requirement: !ruby/object:Gem::Requirement
|
218
216
|
requirements:
|
219
217
|
- - ">="
|
@@ -227,47 +225,47 @@ dependencies:
|
|
227
225
|
- !ruby/object:Gem::Version
|
228
226
|
version: '0'
|
229
227
|
- !ruby/object:Gem::Dependency
|
230
|
-
name:
|
228
|
+
name: rspec
|
231
229
|
requirement: !ruby/object:Gem::Requirement
|
232
230
|
requirements:
|
233
|
-
- -
|
231
|
+
- - ">="
|
234
232
|
- !ruby/object:Gem::Version
|
235
|
-
version:
|
233
|
+
version: '0'
|
236
234
|
type: :development
|
237
235
|
prerelease: false
|
238
236
|
version_requirements: !ruby/object:Gem::Requirement
|
239
237
|
requirements:
|
240
|
-
- -
|
238
|
+
- - ">="
|
241
239
|
- !ruby/object:Gem::Version
|
242
|
-
version:
|
240
|
+
version: '0'
|
243
241
|
- !ruby/object:Gem::Dependency
|
244
|
-
name:
|
242
|
+
name: rubocop
|
245
243
|
requirement: !ruby/object:Gem::Requirement
|
246
244
|
requirements:
|
247
|
-
- -
|
245
|
+
- - '='
|
248
246
|
- !ruby/object:Gem::Version
|
249
|
-
version:
|
247
|
+
version: 0.84.0
|
250
248
|
type: :development
|
251
249
|
prerelease: false
|
252
250
|
version_requirements: !ruby/object:Gem::Requirement
|
253
251
|
requirements:
|
254
|
-
- -
|
252
|
+
- - '='
|
255
253
|
- !ruby/object:Gem::Version
|
256
|
-
version:
|
254
|
+
version: 0.84.0
|
257
255
|
- !ruby/object:Gem::Dependency
|
258
|
-
name: rubocop
|
256
|
+
name: rubocop-rspec
|
259
257
|
requirement: !ruby/object:Gem::Requirement
|
260
258
|
requirements:
|
261
|
-
- - "
|
259
|
+
- - ">="
|
262
260
|
- !ruby/object:Gem::Version
|
263
|
-
version: 0
|
261
|
+
version: '0'
|
264
262
|
type: :development
|
265
263
|
prerelease: false
|
266
264
|
version_requirements: !ruby/object:Gem::Requirement
|
267
265
|
requirements:
|
268
|
-
- - "
|
266
|
+
- - ">="
|
269
267
|
- !ruby/object:Gem::Version
|
270
|
-
version: 0
|
268
|
+
version: '0'
|
271
269
|
- !ruby/object:Gem::Dependency
|
272
270
|
name: sqlite3
|
273
271
|
requirement: !ruby/object:Gem::Requirement
|
@@ -325,6 +323,7 @@ files:
|
|
325
323
|
- lib/que/scheduler/db.rb
|
326
324
|
- lib/que/scheduler/defined_job.rb
|
327
325
|
- lib/que/scheduler/enqueueing_calculator.rb
|
326
|
+
- lib/que/scheduler/jobs/que_scheduler_audit_clear_down_job.rb
|
328
327
|
- lib/que/scheduler/migrations.rb
|
329
328
|
- lib/que/scheduler/migrations/1/down.sql
|
330
329
|
- lib/que/scheduler/migrations/1/up.sql
|
@@ -334,10 +333,13 @@ files:
|
|
334
333
|
- lib/que/scheduler/migrations/3/up.sql
|
335
334
|
- lib/que/scheduler/migrations/4/down.sql
|
336
335
|
- lib/que/scheduler/migrations/4/up.sql
|
336
|
+
- lib/que/scheduler/migrations/5/down.sql
|
337
|
+
- lib/que/scheduler/migrations/5/up.sql
|
337
338
|
- lib/que/scheduler/schedule.rb
|
338
339
|
- lib/que/scheduler/scheduler_job.rb
|
339
340
|
- lib/que/scheduler/scheduler_job_args.rb
|
340
341
|
- lib/que/scheduler/state_checks.rb
|
342
|
+
- lib/que/scheduler/to_enqueue.rb
|
341
343
|
- lib/que/scheduler/version.rb
|
342
344
|
- lib/que/scheduler/version_support.rb
|
343
345
|
homepage: https://github.com/hlascelles/que-scheduler
|
@@ -349,7 +351,7 @@ metadata:
|
|
349
351
|
changelog_uri: https://github.com/hlascelles/que-scheduler/blob/master/CHANGELOG.md
|
350
352
|
source_code_uri: https://github.com/hlascelles/que-scheduler/
|
351
353
|
bug_tracker_uri: https://github.com/hlascelles/que-scheduler/issues
|
352
|
-
post_install_message:
|
354
|
+
post_install_message:
|
353
355
|
rdoc_options: []
|
354
356
|
require_paths:
|
355
357
|
- lib
|
@@ -364,9 +366,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
364
366
|
- !ruby/object:Gem::Version
|
365
367
|
version: '0'
|
366
368
|
requirements: []
|
367
|
-
|
368
|
-
|
369
|
-
signing_key:
|
369
|
+
rubygems_version: 3.0.3
|
370
|
+
signing_key:
|
370
371
|
specification_version: 4
|
371
372
|
summary: A cron scheduler for Que
|
372
373
|
test_files: []
|