diff --git a/.reek b/.reek index 7b2790d1..bcb2b253 100644 --- a/.reek +++ b/.reek @@ -1,7 +1,8 @@ --- Attribute: enabled: true - exclude: [] + exclude: + - Que::Scheduler BooleanParameter: enabled: false exclude: [] diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ddded8..4a62a528 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -## Unreleased +## 3.1.0 (2018-06-01) +* Addition of a gem config initializer [#29](https://github.com/hlascelles/que-scheduler/pull/29) +* Allow configuration of the transaction block adapter [#29](https://github.com/hlascelles/que-scheduler/pull/29) * Handling middle overriding enqueue that prevents a job from being enqueued [#28](https://github.com/hlascelles/que-scheduler/pull/28) ## 3.0.0 (2018-05-23) diff --git a/README.md b/README.md index 828843c9..df289066 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ needs to be run, enqueueing those jobs, then enqueueing itself to check again la ```ruby gem 'que-scheduler' ``` -1. Specify a schedule config in a yml file (see below). The default location that que-scheduler will -look for it is `config/que_schedule.yml`. They are essentially the same as resque-scheduler config +1. Specify a schedule in a yml file (see below). The default location that que-scheduler will +look for it is `config/que_schedule.yml`. They are essentially the same as resque-scheduler files, but with additional features. 1. Add a migration to start the job scheduler and prepare the audit table. @@ -35,7 +35,7 @@ files, but with additional features. ## Schedule configuration The schedule file is a list of que job classes with arguments and a schedule frequency (in crontab -syntax). The format is similar to the resque-scheduler config format, though priorities must be supplied as +syntax). The format is similar to the resque-scheduler format, though priorities must be supplied as integers, and job classes must be migrated from Resque to Que. Cron syntax can be anything understood by [fugit](https://github.com/floraison/fugit#fugitcron). @@ -111,11 +111,22 @@ A job can have a `schedule_type` assigned to it. Valid values are: This feature ensures that jobs which *must run* for a certain cron match will always eventually execute, even after a total system crash, or even a DB backup restore. -## Environment Variables +## Gem configuration -You can configure some aspects of the gem with environment variables. +You can configure some aspects of the gem with an initializer. The default is given below. -* `QUE_SCHEDULER_CONFIG_LOCATION` - The location of the schedule configuration (default `config/que_schedule.yml`) +```ruby +Que::Scheduler.configure do |config| + # The location of the schedule yaml file. + config.schedule_location = ENV.fetch('QUE_SCHEDULER_CONFIG_LOCATION', 'config/que_schedule.yml') + + # Specify a transaction block adapter. By default, que-scheduler uses the one supplied by que. + # However, if, for example you rely on listeners to ActiveRecord's exact `transaction` method, or + # Sequel's DB.after_commit helper, then you can supply it here. + config.transaction_adapter = ::Que.method(:transaction) +end + +``` ## Scheduler Audit @@ -134,7 +145,7 @@ in a coherent state with the rest of your database. ## Concurrent scheduler detection -No matter how many tasks you have defined in your config, you will only ever need one que-scheduler +No matter how many tasks you have defined in your schedule, you will only ever need one que-scheduler job enqueued. que-scheduler knows this, and it will check before performing any operations that there is only one of itself present. @@ -144,15 +155,15 @@ it will rollback correctly and try again. It won't schedule jobs twice for a cro ## How it works -que-scheduler is a job that reads a config file, enqueues any jobs it determines that need to be run, +que-scheduler is a job that reads a schedule file, enqueues any jobs it determines that need to be run, then reschedules itself. The flow is as follows: 1. The que-scheduler job runs for the very first time. -1. que-scheduler loads the schedule config file. It will not schedule any other jobs, except itself, +1. que-scheduler loads the schedule file. It will not schedule any other jobs, except itself, as it has never run before. 1. Some time later it runs again. It knows what jobs it should be monitoring, and notices that some have are due. It enqueues those jobs and then itself. Repeat. -1. After a deploy that changes the config, the job notices any new jobs to schedule, and knows which +1. After a deploy that changes the schedule, the job notices any new jobs to schedule, and knows which ones to forget. It does not need to be re-enqueued or restarted. ## DB Migrations @@ -182,6 +193,7 @@ Major feature changes are listed below. The full the root of the project. #### Versions 3.x + - Addition of a config initializer. - Addition of numerous extra columns to the audit table. - Required cumulative migration: `Que::Scheduler::Migrations.migrate!(version: 4)` #### Versions 2.x diff --git a/lib/que-scheduler.rb b/lib/que-scheduler.rb new file mode 100644 index 00000000..89806c08 --- /dev/null +++ b/lib/que-scheduler.rb @@ -0,0 +1,3 @@ +# rubocop:disable Naming/FileName +require 'que/scheduler' +# rubocop:enable Naming/FileName diff --git a/lib/que/scheduler.rb b/lib/que/scheduler.rb index 80aee3d4..ffa02400 100644 --- a/lib/que/scheduler.rb +++ b/lib/que/scheduler.rb @@ -1,4 +1,5 @@ require 'que/scheduler/version' +require 'que/scheduler/config' require 'que/scheduler/scheduler_job' require 'que/scheduler/db' require 'que/scheduler/audit' diff --git a/lib/que/scheduler/config.rb b/lib/que/scheduler/config.rb new file mode 100644 index 00000000..ba4838cb --- /dev/null +++ b/lib/que/scheduler/config.rb @@ -0,0 +1,24 @@ +require 'que' + +module Que + module Scheduler + class << self + attr_accessor :configuration + end + + def self.configure + self.configuration ||= Configuration.new + yield(configuration) + end + + class Configuration + attr_accessor :schedule_location + attr_accessor :transaction_adapter + end + end +end + +Que::Scheduler.configure do |config| + config.schedule_location = ENV.fetch('QUE_SCHEDULER_CONFIG_LOCATION', 'config/que_schedule.yml') + config.transaction_adapter = ::Que.method(:transaction) +end diff --git a/lib/que/scheduler/db.rb b/lib/que/scheduler/db.rb index 8d0fcc7d..9a02fe31 100644 --- a/lib/que/scheduler/db.rb +++ b/lib/que/scheduler/db.rb @@ -15,6 +15,10 @@ def count_schedulers def now Que.execute(NOW_SQL).first.values.first end + + def transaction + Que::Scheduler.configuration.transaction_adapter.call { yield } + end end end end diff --git a/lib/que/scheduler/defined_job.rb b/lib/que/scheduler/defined_job.rb index 7eaa70ad..64618663 100644 --- a/lib/que/scheduler/defined_job.rb +++ b/lib/que/scheduler/defined_job.rb @@ -6,10 +6,6 @@ module Que module Scheduler class DefinedJob < Hashie::Dash - QUE_SCHEDULER_CONFIG_LOCATION = - ENV.fetch('QUE_SCHEDULER_CONFIG_LOCATION', 'config/que_schedule.yml') - ERROR_MESSAGE_SUFFIX = "in que-scheduler config #{QUE_SCHEDULER_CONFIG_LOCATION}".freeze - include Hashie::Extensions::Dash::PropertyTranslation SCHEDULE_TYPES = [ @@ -55,7 +51,8 @@ def calculate_missed_runs(last_run_time, as_time) class << self def defined_jobs - @defined_jobs ||= YAML.safe_load(IO.read(QUE_SCHEDULER_CONFIG_LOCATION)).map do |k, v| + schedule_string = IO.read(Que::Scheduler.configuration.schedule_location) + @defined_jobs ||= YAML.safe_load(schedule_string).map do |k, v| Que::Scheduler::DefinedJob.new( { name: k, @@ -73,7 +70,8 @@ def defined_jobs private def err_field(field, value) - raise "Invalid #{field} '#{value}' #{ERROR_MESSAGE_SUFFIX}" + schedule = Que::Scheduler.configuration.schedule_location + raise "Invalid #{field} '#{value}' in que-scheduler schedule #{schedule}" end end diff --git a/lib/que/scheduler/migrations.rb b/lib/que/scheduler/migrations.rb index 482511a2..4a723d39 100644 --- a/lib/que/scheduler/migrations.rb +++ b/lib/que/scheduler/migrations.rb @@ -13,7 +13,7 @@ module Migrations class << self def migrate!(version:) - ::Que.transaction do + Que::Scheduler::Db.transaction do current = db_version if current < version migrate_up(current, version) diff --git a/lib/que/scheduler/scheduler_job.rb b/lib/que/scheduler/scheduler_job.rb index d159f3f8..59726441 100644 --- a/lib/que/scheduler/scheduler_job.rb +++ b/lib/que/scheduler/scheduler_job.rb @@ -15,7 +15,7 @@ class SchedulerJob < Que::Job @priority = 0 def run(options = nil) - ::Que.transaction do + Que::Scheduler::Db.transaction do assert_one_scheduler_job scheduler_job_args = SchedulerJobArgs.build(options) logs = ["que-scheduler last ran at #{scheduler_job_args.last_run_time}."] diff --git a/lib/que/scheduler/version.rb b/lib/que/scheduler/version.rb index b16130e2..d6ec77ac 100644 --- a/lib/que/scheduler/version.rb +++ b/lib/que/scheduler/version.rb @@ -1,5 +1,5 @@ module Que module Scheduler - VERSION = '3.0.0'.freeze + VERSION = '3.1.0'.freeze end end diff --git a/spec/que/scheduler/db_spec.rb b/spec/que/scheduler/db_spec.rb index 7c03f366..2153cafb 100644 --- a/spec/que/scheduler/db_spec.rb +++ b/spec/que/scheduler/db_spec.rb @@ -34,5 +34,10 @@ def check(str) it 'Sequel is not used explicitly' do check('Sequel') end + + # Check Que.transaction is not used, as the config transaction proc should be used instead + it 'Que.transaction is not used explicitly' do + check('Que.transaction') + end end end diff --git a/spec/que/scheduler/defined_job_spec.rb b/spec/que/scheduler/defined_job_spec.rb index be6ac734..d15f97d4 100644 --- a/spec/que/scheduler/defined_job_spec.rb +++ b/spec/que/scheduler/defined_job_spec.rb @@ -40,7 +40,7 @@ def expect_time(from, to, exp) job_class: 'HalfHourlyTestJob', cron: 'foo 17 * * *' ) - end.to raise_error(/Invalid cron 'foo 17 \* \* \*' in que-scheduler config/) + end.to raise_error(/Invalid cron 'foo 17 \* \* \*' in que-scheduler schedule/) end it 'allows crons with fugit compatible english words' do @@ -77,7 +77,7 @@ def expect_time(from, to, exp) job_class: 'HalfHourlyTestJob', queue: 3_214_214 ) - end.to raise_error(/Invalid queue '3214214' in que-scheduler config/) + end.to raise_error(/Invalid queue '3214214' in que-scheduler schedule/) end it 'checks the priority is an integer' do @@ -87,7 +87,7 @@ def expect_time(from, to, exp) job_class: 'HalfHourlyTestJob', priority: 'foo' ) - end.to raise_error(/Invalid priority 'foo' in que-scheduler config/) + end.to raise_error(/Invalid priority 'foo' in que-scheduler schedule/) end it 'checks the job_class is a Que::Job from job_class' do @@ -96,7 +96,7 @@ def expect_time(from, to, exp) name: 'checking_job_types', job_class: 'NotAQueJob' ) - end.to raise_error(/Invalid job_class 'NotAQueJob' in que-scheduler config/) + end.to raise_error(/Invalid job_class 'NotAQueJob' in que-scheduler schedule/) end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2990dca4..674ff078 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,8 +3,6 @@ require 'coveralls' Coveralls.wear! -ENV['QUE_SCHEDULER_CONFIG_LOCATION'] = "#{__dir__}/config/que_schedule.yml" - # By default, que-scheduler specs run in different timezones with every execution, thanks to # zonebie. If you want to force one particular timezone, you can use the following: # ENV['ZONEBIE_TZ'] = 'International Date Line West' @@ -17,6 +15,10 @@ Dir["#{__dir__}/../spec/support/**/*.rb"].each { |f| require f } +Que::Scheduler.configure do |config| + config.schedule_location = "#{__dir__}/config/que_schedule.yml" +end + RSpec.configure do |config| config.example_status_persistence_file_path = '.rspec_status' config.disable_monkey_patching!