que-scheduler 3.4.1 → 4.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf1ee6c62677a8c215d48d990da257e0f4854c8e6e354e1eae3e90fc0685b601
4
- data.tar.gz: dd948edfb90841bc54fc0bb3fc9e725bf4127f1ca31c53654a209ed6d5332e42
3
+ metadata.gz: 198960bfd5797bdc85e0b320170350bb3a752e5c4ed90db31b86cf040b5dbd92
4
+ data.tar.gz: 3f7128c8d159d5007ae42e72eca65059fd5e3f7cc9d60706dd72d7ea5002e686
5
5
  SHA512:
6
- metadata.gz: 7ae4812d01e7ea24300afb6b76a5194f3a25a912852723e30cbc8a1405d1822e8fb543dee955049f87e3063a9ae8b322675e6bbb836c0ab9fe0cc46c119b180c
7
- data.tar.gz: 7246c2709a90c3e2ac7e14a9a8ee2f91792f3cccf0f516ad2dc6f119ddcf025b9eb9324fcf4e3eb6f7b279646066a02693f2f1364773a34c3aed011e041012da
6
+ metadata.gz: db16c38be02a29d413687a3faaffd7749a88ca141bfcee724b0f1c410ddb4094ca6b92c73c878250d088cbaec5242dd8b755d3d3bf44dacd80dd921b710eb9bd
7
+ data.tar.gz: fd46b785a759e3435491e54015d7ffff61bcf8ad5b0bd782a505838c148e9c778d6c994aaad71697cbe815bd47dffa22dcc3e35de8dc312eebbcc85a2752e26a
data/README.md CHANGED
@@ -18,9 +18,9 @@ needs to be run, enqueueing those jobs, then enqueueing itself to check again la
18
18
  ```ruby
19
19
  gem 'que-scheduler'
20
20
  ```
21
- 1. Specify a schedule in a yml file (see below). The default location that que-scheduler will
22
- look for it is `config/que_schedule.yml`. They are essentially the same as resque-scheduler
23
- files, but with additional features.
21
+ 1. Specify a schedule in a yml file or programmatically (see below). The default location that
22
+ que-scheduler will look for it is `config/que_schedule.yml`. The format is essentially the same as
23
+ resque-scheduler files, but with additional features.
24
24
 
25
25
  1. Add a migration to start the job scheduler and prepare the audit table. Note that this migration
26
26
  will fail if Que is set to execute jobs synchronously, i.e. `Que::Job.run_synchronously = true`.
@@ -28,14 +28,18 @@ files, but with additional features.
28
28
  ```ruby
29
29
  class CreateQueSchedulerSchema < ActiveRecord::Migration
30
30
  def change
31
- Que::Scheduler::Migrations.migrate!(version: 5)
31
+ Que::Scheduler::Migrations.migrate!(version: 6)
32
32
  end
33
33
  end
34
34
  ```
35
35
 
36
36
  ## Schedule configuration
37
37
 
38
- The schedule file is a list of que job classes with arguments and a schedule frequency (in crontab
38
+ The schedule file should be placed here: `config/que_schedule.yml`. Alternatively if you
39
+ wish to generate the configuration dynamically, you can set it directly using an initializer
40
+ (see "Gem configuration" below).
41
+
42
+ The file is a list of que job classes with arguments and a schedule frequency (in crontab
39
43
  syntax). The format is similar to the resque-scheduler format, though priorities must be supplied as
40
44
  integers, and job classes must be migrated from Resque to Que. Cron syntax can be anything
41
45
  understood by [fugit](https://github.com/floraison/fugit#fugitcron).
@@ -131,19 +135,34 @@ A job can have a `schedule_type` assigned to it. Valid values are:
131
135
 
132
136
  ## Gem configuration
133
137
 
134
- You can configure some aspects of the gem with an initializer. The default is given below.
138
+ You can configure some aspects of the gem with a config block (eg in a Rails initializer).
139
+ The default is given below. You can omit any configuration sections you are not intending to change.
140
+ It is quite likely you won't have to create this config at all.
135
141
 
136
142
  ```ruby
137
143
  Que::Scheduler.configure do |config|
138
144
  # The location of the schedule yaml file.
139
- config.schedule_location = ENV.fetch('QUE_SCHEDULER_CONFIG_LOCATION', 'config/que_schedule.yml')
145
+ config.schedule_location = ENV.fetch("QUE_SCHEDULER_CONFIG_LOCATION", "config/que_schedule.yml")
146
+
147
+ # The schedule as a hash. You can use this if you want to build the schedule yourself at runtime.
148
+ # This will override the above value if provided.
149
+ config.schedule = {
150
+ SpecifiedByHashTestJob: {
151
+ cron: "02 11 * * *"
152
+ }
153
+ }
140
154
 
141
- # Specify a transaction block adapter. By default, que-scheduler uses the one supplied by que.
142
- # However, if, for example you rely on listeners to ActiveRecord's exact `transaction` method, or
155
+ # The transaction block adapter. By default, que-scheduler uses the one supplied by que.
156
+ # However if, for example, you rely on listeners to ActiveRecord's exact `transaction` method, or
143
157
  # Sequel's DB.after_commit helper, then you can supply it here.
144
158
  config.transaction_adapter = ::Que.method(:transaction)
145
- end
146
159
 
160
+ # Which queue name the que-scheduler job should self-schedule on. Typically this is the default
161
+ # queue of que, which has a different name in Que 0.x ("") and 1.x ("default").
162
+ # It *must* be the "highest throughput" queue - do not work the scheduler on a "long
163
+ # running jobs" queue. It is very unlikely you will want to change this.
164
+ config.que_scheduler_queue = ENV.fetch("QUE_SCHEDULER_QUEUE", "" or "default")
165
+ end
147
166
  ```
148
167
 
149
168
  ## Scheduler Audit
@@ -155,20 +174,40 @@ migration tasks.
155
174
  Additionally, there is the audit table `que_scheduler_audit_enqueued`. This logs every job that
156
175
  the scheduler enqueues.
157
176
 
177
+ que-scheduler comes with the `QueSchedulerAuditClearDownJob` job built in that you can optionally
178
+ schedule to clear down audit rows if you don't need to retain them indefinitely. You should add this
179
+ to your own scheduler config yaml.
180
+
181
+ For example:
182
+
183
+ ```yaml
184
+ # This will clear down the oldest que-scheduler audit rows. Since que-scheduler
185
+ # runs approximately every minute, 129600 is 90 days.
186
+ Que::Scheduler::Jobs::QueSchedulerAuditClearDownJob:
187
+ cron: "0 0 * * *"
188
+ args:
189
+ retain_row_count: 129600
190
+ ```
191
+
192
+ ## Required migrations
193
+
158
194
  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.
195
+ latest migration proceeds at a faster rate than the version of the gem. eg If the gem is on version
196
+ 3 then the migrations may be on version 6).
197
+
198
+ To run in all the migrations required up to a number, just migrate to that number with one line, and
199
+ it will perform all the intermediary steps.
161
200
 
162
201
  ie, This will perform all migrations necessary up to the latest version, skipping any already
163
202
  performed.
164
203
 
165
- ```ruby
166
- class CreateQueSchedulerSchema < ActiveRecord::Migration
167
- def change
168
- Que::Scheduler::Migrations.migrate!(version: 5)
169
- end
170
- end
171
- ```
204
+ ```ruby
205
+ class CreateQueSchedulerSchema < ActiveRecord::Migration
206
+ def change
207
+ Que::Scheduler::Migrations.migrate!(version: 6)
208
+ end
209
+ end
210
+ ```
172
211
 
173
212
  The changes in past migrations were:
174
213
 
@@ -179,22 +218,15 @@ The changes in past migrations were:
179
218
  | 3 | Added the audit table `que_scheduler_audit_enqueued`. |
180
219
  | 4 | Updated the the audit tables to use bigints |
181
220
  | 5 | Dropped an unnecessary index |
221
+ | 6 | Enforced single scheduler job at the trigger level |
182
222
 
183
- ## Built in optional job for audit clear down
223
+ The changes to the DB ([DDL](https://en.wikipedia.org/wiki/Data_definition_language)) are all
224
+ captured in the structure.sql so will be re-run in correctly if squashed - except for the actual
225
+ scheduling of the job itself (as that is [DML](https://en.wikipedia.org/wiki/Data_manipulation_language)).
226
+ If you squash your migrations make sure this is added as the final line:
184
227
 
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
228
+ ```ruby
229
+ Que::Scheduler::Migrations.reenqueue_scheduler_if_missing
198
230
  ```
199
231
 
200
232
  ## HA Redundancy and DB restores
@@ -210,8 +242,8 @@ in a coherent state with the rest of your database.
210
242
  ## Concurrent scheduler detection
211
243
 
212
244
  No matter how many tasks you have defined in your schedule, you will only ever need one que-scheduler
213
- job enqueued. que-scheduler knows this, and it will check before performing any operations that
214
- there is only one of itself present.
245
+ job enqueued. que-scheduler knows this, and there are DB constraints in place to ensure there is
246
+ only ever exactly one scheduler job.
215
247
 
216
248
  It also follows que job design [best practices](https://github.com/chanks/que/blob/master/docs/writing_reliable_jobs.md),
217
249
  using ACID guarantees, to ensure that it will never run multiple times. If the scheduler crashes for any reason,
@@ -263,6 +295,10 @@ The scheduler will then continue to retry indefinitely.
263
295
  que-scheduler uses [semantic versioning](https://semver.org/), so major version changes will usually
264
296
  require additional actions to be taken upgrading from one major version to another.
265
297
 
298
+ ## Changelog
299
+
300
+ A full changelog can be found here: [CHANGELOG.md](https://github.com/hlascelles/que-scheduler/blob/master/CHANGELOG.md)
301
+
266
302
  ## System requirements
267
303
 
268
304
  Your [postgres](https://www.postgresql.org/) database must be at least version 9.4.0.
@@ -273,8 +309,9 @@ This gem was inspired by the makers of the excellent [Que](https://github.com/ch
273
309
 
274
310
  ## Contributors
275
311
 
312
+ * @bnauta
313
+ * @JackDanger
276
314
  * @jish
277
315
  * @joehorsnell
278
- * @bnauta
279
- * @papodaca
280
316
  * @krzyzak
317
+ * @papodaca
@@ -1,6 +1,6 @@
1
1
  require "que/scheduler/version"
2
2
  require "que/scheduler/version_support"
3
- require "que/scheduler/config"
3
+ require "que/scheduler/configuration"
4
4
  require "que/scheduler/scheduler_job"
5
5
  require "que/scheduler/db"
6
6
  require "que/scheduler/audit"
@@ -0,0 +1,35 @@
1
+ require "que"
2
+ require_relative "version_support"
3
+
4
+ module Que
5
+ module Scheduler
6
+ class Configuration
7
+ attr_accessor :schedule_location
8
+ attr_accessor :schedule
9
+ attr_accessor :transaction_adapter
10
+ attr_accessor :que_scheduler_queue
11
+ end
12
+
13
+ class << self
14
+ attr_accessor :configuration
15
+
16
+ def configure
17
+ self.configuration ||= Configuration.new
18
+ yield(configuration)
19
+ end
20
+
21
+ def apply_defaults
22
+ configure do |config|
23
+ config.schedule_location =
24
+ ENV.fetch("QUE_SCHEDULER_CONFIG_LOCATION", "config/que_schedule.yml")
25
+ config.transaction_adapter = ::Que.method(:transaction)
26
+ config.que_scheduler_queue =
27
+ ENV.fetch("QUE_SCHEDULER_QUEUE", Que::Scheduler::VersionSupport.default_scheduler_queue)
28
+ config.schedule = nil
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ Que::Scheduler.apply_defaults
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "config"
3
+ require_relative "configuration"
4
4
 
5
5
  module Que
6
6
  module Scheduler
@@ -41,6 +41,11 @@ module Que
41
41
  result.any?
42
42
  end
43
43
 
44
+ # This method is only intended for use in squashed migrations
45
+ def reenqueue_scheduler_if_missing
46
+ Que::Scheduler::SchedulerJob.enqueue if Que::Scheduler::Db.count_schedulers.zero?
47
+ end
48
+
44
49
  private
45
50
 
46
51
  def migrate_up(current, version)
@@ -0,0 +1,7 @@
1
+ DROP TRIGGER que_scheduler_prevent_job_deletion_trigger ON que_jobs;
2
+
3
+ DROP FUNCTION que_scheduler_prevent_job_deletion();
4
+
5
+ DROP FUNCTION que_scheduler_check_job_exists();
6
+
7
+ DROP INDEX que_scheduler_job_in_que_jobs_unique_index;
@@ -0,0 +1,26 @@
1
+ -- Ensure there is no more than one scheduler
2
+ CREATE UNIQUE INDEX que_scheduler_job_in_que_jobs_unique_index ON que_jobs(job_class)
3
+ WHERE job_class = 'Que::Scheduler::SchedulerJob';
4
+
5
+ -- Ensure there is at least one scheduler
6
+ CREATE OR REPLACE FUNCTION que_scheduler_check_job_exists() RETURNS bool AS $$
7
+ SELECT EXISTS(SELECT * FROM que_jobs WHERE job_class = 'Que::Scheduler::SchedulerJob');
8
+ $$ LANGUAGE SQL;
9
+
10
+ CREATE OR REPLACE FUNCTION que_scheduler_prevent_job_deletion() RETURNS TRIGGER AS
11
+ $BODY$
12
+ DECLARE
13
+ BEGIN
14
+ IF OLD.job_class = 'Que::Scheduler::SchedulerJob' THEN
15
+ IF NOT que_scheduler_check_job_exists() THEN
16
+ raise exception 'Deletion of que_scheduler job % prevented. Deleting the que_scheduler job is almost certainly a mistake.', OLD.job_id;
17
+ END IF;
18
+ END IF;
19
+ RETURN OLD;
20
+ END;
21
+ $BODY$
22
+ LANGUAGE 'plpgsql';
23
+
24
+ CREATE CONSTRAINT TRIGGER que_scheduler_prevent_job_deletion_trigger AFTER UPDATE OR DELETE ON que_jobs
25
+ DEFERRABLE INITIALLY DEFERRED
26
+ FOR EACH ROW EXECUTE PROCEDURE que_scheduler_prevent_job_deletion();
@@ -1,20 +1,37 @@
1
+ require_relative "configuration"
1
2
  require_relative "defined_job"
2
3
 
3
4
  module Que
4
5
  module Scheduler
5
6
  class Schedule
6
7
  class << self
8
+ # The main method for determining the schedule. It has to evaluate the schedule as late as
9
+ # possible (ie just as it is about to be used) as we cannot guarantee we are in a Rails
10
+ # app with initializers. In a future release this may change to "fast fail" in Rails by
11
+ # checking the config up front.
7
12
  def schedule
8
13
  @schedule ||=
9
14
  begin
10
- location = Que::Scheduler.configuration.schedule_location
11
- from_file(location)
15
+ configuration = Que::Scheduler.configuration
16
+ if !configuration.schedule.nil?
17
+ # If an explicit schedule as a hash has been defined, use that.
18
+ from_hash(configuration.schedule)
19
+ elsif File.exist?(configuration.schedule_location)
20
+ # If the schedule is defined as a file location, then load it and return it.
21
+ from_file(configuration.schedule_location)
22
+ else
23
+ raise "No que-scheduler config set, or file found " \
24
+ "at #{configuration.schedule_location}"
25
+ end
12
26
  end
13
27
  end
14
28
 
15
29
  def from_file(location)
16
- yml = IO.read(location)
17
- config_hash = YAML.safe_load(yml)
30
+ from_yaml(IO.read(location))
31
+ end
32
+
33
+ def from_yaml(config)
34
+ config_hash = YAML.safe_load(config)
18
35
  from_hash(config_hash)
19
36
  end
20
37
 
@@ -1,4 +1,6 @@
1
1
  require "que"
2
+ require "active_support"
3
+ require "active_support/core_ext/time"
2
4
 
3
5
  require_relative "schedule"
4
6
  require_relative "enqueueing_calculator"
@@ -14,8 +16,8 @@ module Que
14
16
  class SchedulerJob < Que::Job
15
17
  SCHEDULER_FREQUENCY = 60
16
18
 
17
- Que::Scheduler::VersionSupport.set_priority(self, 0)
18
- Que::Scheduler::VersionSupport.apply_retry_semantics(self)
19
+ VersionSupport.set_priority(self, 0)
20
+ VersionSupport.apply_retry_semantics(self)
19
21
 
20
22
  def run(options = nil)
21
23
  Que::Scheduler::Db.transaction do
@@ -25,12 +27,13 @@ module Que
25
27
  logs = ["que-scheduler last ran at #{scheduler_job_args.last_run_time}."]
26
28
  result = EnqueueingCalculator.parse(Scheduler.schedule.values, scheduler_job_args)
27
29
  enqueued_jobs = enqueue_required_jobs(result, logs)
30
+ # Remove this job and schedule self again
31
+ destroy
28
32
  enqueue_self_again(
29
33
  scheduler_job_args, scheduler_job_args.as_time, result.job_dictionary, enqueued_jobs
30
34
  )
31
35
  # Only now we're sure nothing errored, log the results
32
36
  logs.each { |str| ::Que.log(event: "que-scheduler".to_sym, message: str) }
33
- destroy
34
37
  end
35
38
  end
36
39
 
@@ -57,7 +60,7 @@ module Que
57
60
 
58
61
  def enqueue_self_again(scheduler_job_args, last_full_execution, job_dictionary, enqueued_jobs)
59
62
  # Log last run...
60
- job_id = Que::Scheduler::VersionSupport.job_attributes(self).fetch(:job_id)
63
+ job_id = VersionSupport.job_attributes(self).fetch(:job_id)
61
64
  Audit.append(job_id, scheduler_job_args.as_time, enqueued_jobs)
62
65
 
63
66
  # And rerun...
@@ -70,7 +73,7 @@ module Que
70
73
  )
71
74
 
72
75
  # rubocop:disable Style/GuardClause This reads better as a conditional
73
- unless Que::Scheduler::VersionSupport.job_attributes(enqueued_job).fetch(:job_id)
76
+ unless enqueued_job && VersionSupport.job_attributes(enqueued_job).fetch(:job_id)
74
77
  raise "SchedulerJob could not self-schedule. Has `.enqueue` been monkey patched?"
75
78
  end
76
79
  # rubocop:enable Style/GuardClause
@@ -8,7 +8,6 @@ module Que
8
8
  class << self
9
9
  def check
10
10
  assert_db_migrated
11
- assert_one_scheduler_job
12
11
  end
13
12
 
14
13
  private
@@ -60,21 +59,6 @@ module Que
60
59
  synchronously. This will fail as que-scheduler needs the above tables to work.
61
60
  ERR
62
61
  end
63
-
64
- def assert_one_scheduler_job
65
- schedulers = Que::Scheduler::Db.count_schedulers
66
- return if schedulers == 1
67
-
68
- raise(<<-ERR)
69
- Only one #{Que::Scheduler::SchedulerJob.name} should be enqueued. #{schedulers} were found.
70
-
71
- que-scheduler works by running a self-enqueueing version of itself that determines which
72
- jobs should be enqueued based on the provided config. If two or more que-schedulers were
73
- to run at once, then duplicate jobs would occur.
74
-
75
- To resolve this problem, please remove any duplicate scheduler jobs from the que_jobs table.
76
- ERR
77
- end
78
62
  end
79
63
  end
80
64
  end
@@ -1,5 +1,5 @@
1
1
  module Que
2
2
  module Scheduler
3
- VERSION = "3.4.1".freeze
3
+ VERSION = "4.0.2".freeze
4
4
  end
5
5
  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.1
4
+ version: 4.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harry Lascelles
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-11 00:00:00.000000000 Z
11
+ date: 2020-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -112,6 +112,20 @@ dependencies:
112
112
  - - ">="
113
113
  - !ruby/object:Gem::Version
114
114
  version: '0'
115
+ - !ruby/object:Gem::Dependency
116
+ name: climate_control
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
115
129
  - !ruby/object:Gem::Dependency
116
130
  name: combustion
117
131
  requirement: !ruby/object:Gem::Requirement
@@ -319,7 +333,7 @@ files:
319
333
  - lib/que-scheduler.rb
320
334
  - lib/que/scheduler.rb
321
335
  - lib/que/scheduler/audit.rb
322
- - lib/que/scheduler/config.rb
336
+ - lib/que/scheduler/configuration.rb
323
337
  - lib/que/scheduler/db.rb
324
338
  - lib/que/scheduler/defined_job.rb
325
339
  - lib/que/scheduler/enqueueing_calculator.rb
@@ -335,6 +349,8 @@ files:
335
349
  - lib/que/scheduler/migrations/4/up.sql
336
350
  - lib/que/scheduler/migrations/5/down.sql
337
351
  - lib/que/scheduler/migrations/5/up.sql
352
+ - lib/que/scheduler/migrations/6/down.sql
353
+ - lib/que/scheduler/migrations/6/up.sql
338
354
  - lib/que/scheduler/schedule.rb
339
355
  - lib/que/scheduler/scheduler_job.rb
340
356
  - lib/que/scheduler/scheduler_job_args.rb
@@ -1,27 +0,0 @@
1
- require "que"
2
- require_relative "version_support"
3
-
4
- module Que
5
- module Scheduler
6
- class << self
7
- attr_accessor :configuration
8
- end
9
-
10
- def self.configure
11
- self.configuration ||= Configuration.new
12
- yield(configuration)
13
- end
14
-
15
- class Configuration
16
- attr_accessor :schedule_location
17
- attr_accessor :transaction_adapter
18
- attr_accessor :que_scheduler_queue
19
- end
20
- end
21
- end
22
-
23
- Que::Scheduler.configure do |config|
24
- config.schedule_location = ENV.fetch("QUE_SCHEDULER_CONFIG_LOCATION", "config/que_schedule.yml")
25
- config.transaction_adapter = ::Que.method(:transaction)
26
- config.que_scheduler_queue = Que::Scheduler::VersionSupport.default_scheduler_queue
27
- end