rocketjob 2.0.0.rc3 → 2.0.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/bin/rocketjob +1 -1
- data/bin/rocketjob_perf +2 -83
- data/lib/rocket_job/cli.rb +13 -3
- data/lib/rocket_job/dirmon_entry.rb +2 -2
- data/lib/rocket_job/extensions/mongo.rb +1 -1
- data/lib/rocket_job/performance.rb +82 -0
- data/lib/rocket_job/plugins/cron.rb +130 -0
- data/lib/rocket_job/plugins/job/logger.rb +1 -1
- data/lib/rocket_job/plugins/processing_window.rb +88 -0
- data/lib/rocket_job/plugins/rufus/cron_line.rb +492 -0
- data/lib/rocket_job/plugins/rufus/zo_time.rb +291 -0
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocket_job/worker.rb +1 -1
- data/lib/rocketjob.rb +26 -20
- data/test/plugins/cron_test.rb +78 -0
- data/test/plugins/job/logger_test.rb +4 -4
- data/test/plugins/processing_window_test.rb +111 -0
- data/test/test_helper.rb +1 -1
- metadata +33 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bf52fdcaa423858a798fdd0da28cf9df7448170
|
4
|
+
data.tar.gz: a8cd888f37215dbe633dad2f13941e629c6463dc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99b6ff7af94d554ff2107deaaade0b639703cf29cd714fcdbe0763f1e4217ae8b22e87a790fd87101a849d1172684a670fb2c6b80c6ec5689ca3991795a0bb53
|
7
|
+
data.tar.gz: 1d477d2acc562df2a16ebccf500e71d77c570feb486c38a01f8bdcabacc99ebcdf56394b05049b893b5237754633920643c8e8387058666bc0f710984f9058f1
|
data/bin/rocketjob
CHANGED
@@ -8,7 +8,7 @@ begin
|
|
8
8
|
RocketJob::CLI.new(ARGV).run
|
9
9
|
rescue => exc
|
10
10
|
# Failsafe logger that writes to STDERR
|
11
|
-
SemanticLogger.add_appender(STDERR, :error,
|
11
|
+
SemanticLogger.add_appender(io: STDERR, level: :error, formatter: :color)
|
12
12
|
SemanticLogger['RocketJob'].error('Rocket Job shutting down due to exception', exc)
|
13
13
|
SemanticLogger.flush
|
14
14
|
exit 1
|
data/bin/rocketjob_perf
CHANGED
@@ -1,91 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require 'csv'
|
3
|
-
require 'yaml'
|
4
2
|
require 'rocketjob'
|
5
3
|
|
6
4
|
# Log to console
|
7
|
-
SemanticLogger.add_appender(STDOUT,
|
5
|
+
SemanticLogger.add_appender(io: STDOUT, formatter: :color)
|
8
6
|
|
9
|
-
|
10
|
-
attr_accessor :count, :worker_processes, :worker_threads, :version, :ruby, :environment, :mongo_config
|
11
|
-
|
12
|
-
def initialize
|
13
|
-
@version = RocketJob::VERSION
|
14
|
-
@ruby = defined?(JRuby) ? "jruby_#{JRUBY_VERSION}" : "ruby_#{RUBY_VERSION}"
|
15
|
-
@count = 100_000
|
16
|
-
@worker_processes = 0
|
17
|
-
@worker_threads = 0
|
18
|
-
@environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
19
|
-
@mongo_config = 'config/mongo.yml'
|
20
|
-
end
|
21
|
-
|
22
|
-
def run_test_case(count = self.count)
|
23
|
-
self.worker_processes = RocketJob::Worker.count
|
24
|
-
raise 'Please start workers before starting the performance test' if worker_processes == 0
|
25
|
-
|
26
|
-
self.worker_processes = 0
|
27
|
-
self.worker_threads = 0
|
28
|
-
RocketJob::Worker.where(state: :running).each do |worker_process|
|
29
|
-
unless worker_process.zombie?
|
30
|
-
self.worker_processes += 1
|
31
|
-
self.worker_threads += worker_process.heartbeat.current_threads
|
32
|
-
end
|
33
|
-
end
|
34
|
-
puts "Running: #{worker_threads} workers, distributed across #{worker_processes} processes"
|
35
|
-
|
36
|
-
puts 'Waiting for workers to pause'
|
37
|
-
RocketJob::Worker.pause_all
|
38
|
-
RocketJob::Jobs::SimpleJob.delete_all
|
39
|
-
sleep 15
|
40
|
-
|
41
|
-
puts 'Enqueuing jobs'
|
42
|
-
first = RocketJob::Jobs::SimpleJob.create(priority: 1, destroy_on_complete: false)
|
43
|
-
(count - 2).times { |i| RocketJob::Jobs::SimpleJob.create! }
|
44
|
-
last = RocketJob::Jobs::SimpleJob.create(priority: 100, destroy_on_complete: false)
|
45
|
-
|
46
|
-
puts 'Resuming workers'
|
47
|
-
RocketJob::Worker.resume_all
|
48
|
-
|
49
|
-
while (!last.reload.completed?)
|
50
|
-
sleep 3
|
51
|
-
end
|
52
|
-
|
53
|
-
duration = last.reload.completed_at - first.reload.started_at
|
54
|
-
{count: count, duration: duration, jobs_per_second: (count.to_f / duration).to_i}
|
55
|
-
end
|
56
|
-
|
57
|
-
# Export the Results hash to a CSV file
|
58
|
-
def export_results(results)
|
59
|
-
CSV.open("job_results_#{ruby}_#{worker_processes}p_#{threads}t_v#{version}.csv", 'wb') do |csv|
|
60
|
-
csv << results.first.keys
|
61
|
-
results.each { |result| csv << result.values }
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# Parse command line options
|
66
|
-
def parse(argv)
|
67
|
-
parser = OptionParser.new do |o|
|
68
|
-
o.on('-c', '--count COUNT', 'Count of jobs to enqueue') do |arg|
|
69
|
-
self.count = arg.to_i
|
70
|
-
end
|
71
|
-
o.on('-m', '--mongo MONGO_CONFIG_FILE_NAME', 'Location of mongo.yml config file') do |arg|
|
72
|
-
self.mongo_config = arg
|
73
|
-
end
|
74
|
-
o.on('-e', '--environment ENVIRONMENT', 'The environment to run the app on (Default: RAILS_ENV || RACK_ENV || development)') do |arg|
|
75
|
-
self.environment = arg
|
76
|
-
end
|
77
|
-
end
|
78
|
-
parser.banner = 'rocketjob_perf <options>'
|
79
|
-
parser.on_tail '-h', '--help', 'Show help' do
|
80
|
-
puts parser
|
81
|
-
exit 1
|
82
|
-
end
|
83
|
-
parser.parse! argv
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
perf = RocketJobPerf.new
|
7
|
+
perf = RocketJob::Performance.new
|
89
8
|
perf.parse(ARGV)
|
90
9
|
RocketJob::Config.load!(perf.environment, perf.mongo_config)
|
91
10
|
results = perf.run_test_case
|
data/lib/rocket_job/cli.rb
CHANGED
@@ -25,6 +25,7 @@ module RocketJob
|
|
25
25
|
setup_environment
|
26
26
|
setup_logger
|
27
27
|
rails? ? boot_rails : boot_standalone
|
28
|
+
# setup_metrics
|
28
29
|
write_pidfile
|
29
30
|
|
30
31
|
opts = {}
|
@@ -33,6 +34,15 @@ module RocketJob
|
|
33
34
|
Worker.run(opts)
|
34
35
|
end
|
35
36
|
|
37
|
+
def setup_metrics
|
38
|
+
SemanticLogger.on_metric do |log|
|
39
|
+
if log.metric.start_with?('rocketjob/')
|
40
|
+
ap log
|
41
|
+
RocketJob::Stats::ShortTerm.increment_metric(log.time, log.name, log.duration, log.metric_amount)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
36
46
|
def rails?
|
37
47
|
@rails ||= begin
|
38
48
|
boot_file = Pathname.new(directory).join('config/environment.rb').expand_path
|
@@ -58,7 +68,7 @@ module RocketJob
|
|
58
68
|
SemanticLogger.default_level = log_level.to_sym if log_level
|
59
69
|
|
60
70
|
if Rails.configuration.eager_load
|
61
|
-
RocketJob::Worker.logger.
|
71
|
+
RocketJob::Worker.logger.measure_info('Eager loaded Rails and all Engines') do
|
62
72
|
Rails.application.eager_load!
|
63
73
|
Rails::Engine.subclasses.each(&:eager_load!)
|
64
74
|
end
|
@@ -83,7 +93,7 @@ module RocketJob
|
|
83
93
|
# Log to file except when booting rails, when it will add the log file path
|
84
94
|
path = log_file ? Pathname.new(log_file) : Pathname.pwd.join("log/#{environment}.log")
|
85
95
|
path.dirname.mkpath
|
86
|
-
SemanticLogger.add_appender(path.to_s,
|
96
|
+
SemanticLogger.add_appender(file_name: path.to_s, formatter: :color)
|
87
97
|
|
88
98
|
logger.info "Rails not detected. Running standalone: #{environment}"
|
89
99
|
RocketJob::Config.load!(environment)
|
@@ -112,7 +122,7 @@ module RocketJob
|
|
112
122
|
end
|
113
123
|
|
114
124
|
def setup_logger
|
115
|
-
SemanticLogger.add_appender(STDOUT,
|
125
|
+
SemanticLogger.add_appender(io: STDOUT, formatter: :color) unless quiet
|
116
126
|
SemanticLogger.default_level = log_level.to_sym if log_level
|
117
127
|
|
118
128
|
# Enable SemanticLogger signal handling for this process
|
@@ -251,7 +251,7 @@ module RocketJob
|
|
251
251
|
|
252
252
|
# Passes each filename [Pathname] found that matches the pattern into the supplied block
|
253
253
|
def each(&block)
|
254
|
-
logger.fast_tag("
|
254
|
+
logger.fast_tag("dirmon_entry:#{id}") do
|
255
255
|
# Case insensitive filename matching
|
256
256
|
Pathname.glob(pattern, File::FNM_CASEFOLD).each do |pathname|
|
257
257
|
next if pathname.directory?
|
@@ -305,7 +305,7 @@ module RocketJob
|
|
305
305
|
# Queues the job for the supplied pathname
|
306
306
|
def later(pathname)
|
307
307
|
if klass = job_class
|
308
|
-
logger.
|
308
|
+
logger.measure_info "Enqueued: #{name}, Job class: #{job_class_name}" do
|
309
309
|
job = klass.new(properties.merge(arguments: arguments))
|
310
310
|
upload_file(job, pathname)
|
311
311
|
job.save!
|
@@ -16,7 +16,7 @@ module Mongo
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def log_operation(name, payload, duration)
|
19
|
-
MongoClient.logger.
|
19
|
+
MongoClient.logger.measure_trace(name, duration: (duration * 1000), payload: payload)
|
20
20
|
end
|
21
21
|
|
22
22
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'yaml'
|
3
|
+
module RocketJob
|
4
|
+
class Performance
|
5
|
+
attr_accessor :count, :worker_processes, :worker_threads, :version, :ruby, :environment, :mongo_config
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@version = RocketJob::VERSION
|
9
|
+
@ruby = defined?(JRuby) ? "jruby_#{JRUBY_VERSION}" : "ruby_#{RUBY_VERSION}"
|
10
|
+
@count = 100_000
|
11
|
+
@worker_processes = 0
|
12
|
+
@worker_threads = 0
|
13
|
+
@environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
14
|
+
@mongo_config = 'config/mongo.yml'
|
15
|
+
end
|
16
|
+
|
17
|
+
def run_test_case(count = self.count)
|
18
|
+
self.worker_processes = RocketJob::Worker.count
|
19
|
+
raise 'Please start workers before starting the performance test' if worker_processes == 0
|
20
|
+
|
21
|
+
self.worker_processes = 0
|
22
|
+
self.worker_threads = 0
|
23
|
+
RocketJob::Worker.where(state: :running).each do |worker_process|
|
24
|
+
unless worker_process.zombie?
|
25
|
+
self.worker_processes += 1
|
26
|
+
self.worker_threads += worker_process.heartbeat.current_threads
|
27
|
+
end
|
28
|
+
end
|
29
|
+
puts "Running: #{worker_threads} workers, distributed across #{worker_processes} processes"
|
30
|
+
|
31
|
+
puts 'Waiting for workers to pause'
|
32
|
+
RocketJob::Worker.pause_all
|
33
|
+
RocketJob::Jobs::SimpleJob.delete_all
|
34
|
+
sleep 15
|
35
|
+
|
36
|
+
puts 'Enqueuing jobs'
|
37
|
+
first = RocketJob::Jobs::SimpleJob.create(priority: 1, destroy_on_complete: false)
|
38
|
+
(count - 2).times { |i| RocketJob::Jobs::SimpleJob.create! }
|
39
|
+
last = RocketJob::Jobs::SimpleJob.create(priority: 100, destroy_on_complete: false)
|
40
|
+
|
41
|
+
puts 'Resuming workers'
|
42
|
+
RocketJob::Worker.resume_all
|
43
|
+
|
44
|
+
while (!last.reload.completed?)
|
45
|
+
sleep 3
|
46
|
+
end
|
47
|
+
|
48
|
+
duration = last.reload.completed_at - first.reload.started_at
|
49
|
+
{count: count, duration: duration, jobs_per_second: (count.to_f / duration).to_i}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Export the Results hash to a CSV file
|
53
|
+
def export_results(results)
|
54
|
+
CSV.open("job_results_#{ruby}_#{worker_processes}p_#{threads}t_v#{version}.csv", 'wb') do |csv|
|
55
|
+
csv << results.first.keys
|
56
|
+
results.each { |result| csv << result.values }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parse command line options
|
61
|
+
def parse(argv)
|
62
|
+
parser = OptionParser.new do |o|
|
63
|
+
o.on('-c', '--count COUNT', 'Count of jobs to enqueue') do |arg|
|
64
|
+
self.count = arg.to_i
|
65
|
+
end
|
66
|
+
o.on('-m', '--mongo MONGO_CONFIG_FILE_NAME', 'Location of mongo.yml config file') do |arg|
|
67
|
+
self.mongo_config = arg
|
68
|
+
end
|
69
|
+
o.on('-e', '--environment ENVIRONMENT', 'The environment to run the app on (Default: RAILS_ENV || RACK_ENV || development)') do |arg|
|
70
|
+
self.environment = arg
|
71
|
+
end
|
72
|
+
end
|
73
|
+
parser.banner = 'rocketjob_perf <options>'
|
74
|
+
parser.on_tail '-h', '--help', 'Show help' do
|
75
|
+
puts parser
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
parser.parse! argv
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module RocketJob
|
5
|
+
module Plugins
|
6
|
+
# Automatically schedules the job to start based on the supplied `cron_schedule`.
|
7
|
+
# Once started the job will automatically restart on completion and will only run again
|
8
|
+
# according to the `cron_schedule`.
|
9
|
+
# Failed jobs are aborted so that they cannot be restarted since a new instance has already
|
10
|
+
# been enqueued.
|
11
|
+
#
|
12
|
+
# Include RocketJob::Plugins::Singleton to prevent multiple copies of the job from running at
|
13
|
+
# the same time.
|
14
|
+
#
|
15
|
+
# Unlike cron, if a job is already running, another one is not queued when the cron
|
16
|
+
# schedule needs another started, but rather on completion of the current job. This prevents
|
17
|
+
# multiple instances of the same job from running at the same time. The next instance of the
|
18
|
+
# job is only scheduled on completion of the current job instance.
|
19
|
+
#
|
20
|
+
# For example if the job takes 10 minutes to complete, and is scheduled to run every 5 minutes,
|
21
|
+
# it will only be run every 10 minutes.
|
22
|
+
#
|
23
|
+
# Their is no centralized scheduler or need to start schedulers anywhere, since the jobs
|
24
|
+
# can be picked up by any Rocket Job worker. Once processing is complete a new instance is then
|
25
|
+
# automatically scheduled based on the `cron_schedule`.
|
26
|
+
#
|
27
|
+
# A job is only queued to run at the specified `cron_schedule`, it will only run if there are workers
|
28
|
+
# available to run. For example if workers are busy working on higher priority jobs, then the job
|
29
|
+
# will only run once those jobs have completed, or their priority lowered. Additionally, while the
|
30
|
+
# job is queued no additional instances will be enqueued, even if the next cron interval has been reached.
|
31
|
+
#
|
32
|
+
# Note:
|
33
|
+
# - The job will not be restarted if:
|
34
|
+
# - A validation fails after cloning this job.
|
35
|
+
# - The job has expired.
|
36
|
+
#
|
37
|
+
# Example:
|
38
|
+
#
|
39
|
+
# class MyCronJob < RocketJob::Job
|
40
|
+
# include RocketJob::Plugins::Cron
|
41
|
+
#
|
42
|
+
# # Set the default cron_schedule
|
43
|
+
# rocket_job do |job|
|
44
|
+
# job.cron_schedule = "* 1 * * * UTC"
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# def perform
|
48
|
+
# puts "DONE"
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# # Queue the job for processing using the default cron_schedule specified above
|
53
|
+
# MyCronJob.create!
|
54
|
+
#
|
55
|
+
# # Set the cron schedule:
|
56
|
+
# MyCronJob.create!(cron_schedule: '* 1 * * * America/New_York')
|
57
|
+
#
|
58
|
+
#
|
59
|
+
# Note:
|
60
|
+
#
|
61
|
+
# To prevent multiple instances of the job from running at the same time,
|
62
|
+
# add: "include RocketJob::Plugins::Singleton"
|
63
|
+
#
|
64
|
+
# Example: Only allow one instance of the cron job to run at a time:
|
65
|
+
#
|
66
|
+
# class MyCronJob < RocketJob::Job
|
67
|
+
# # Add `cron_schedule`
|
68
|
+
# include RocketJob::Plugins::Cron
|
69
|
+
# # Prevents mutiple instances from being queued or run at the same time
|
70
|
+
# include RocketJob::Plugins::Singleton
|
71
|
+
#
|
72
|
+
# # Set the default cron_schedule
|
73
|
+
# rocket_job do |job|
|
74
|
+
# job.cron_schedule = "* 1 * * * UTC"
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# def perform
|
78
|
+
# puts "DONE"
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# Note: The cron_schedule field is formatted as follows:
|
83
|
+
#
|
84
|
+
# * * * * * *
|
85
|
+
# ┬ ┬ ┬ ┬ ┬ ┬
|
86
|
+
# │ │ │ │ │ │
|
87
|
+
# │ │ │ │ │ └ Optional: Timezone, for example: 'America/New_York', 'UTC'
|
88
|
+
# │ │ │ │ └───── day_of_week (0-7) (0 or 7 is Sun, or use 3-letter names)
|
89
|
+
# │ │ │ └────────── month (1-12, or use 3-letter names)
|
90
|
+
# │ │ └─────────────── day_of_month (1-31)
|
91
|
+
# │ └──────────────────── hour (0-23)
|
92
|
+
# └───────────────────────── minute (0-59)
|
93
|
+
#
|
94
|
+
# * When specifying day of week, both day 0 and day 7 is Sunday.
|
95
|
+
# * Ranges & Lists of numbers are allowed.
|
96
|
+
# * Ranges or lists of names are not allowed.
|
97
|
+
# * Ranges can include 'steps', so `1-9/2` is the same as `1,3,5,7,9`.
|
98
|
+
# * Months or days of the week can be specified by name.
|
99
|
+
# * Use the first three letters of the particular day or month (case doesn't matter).
|
100
|
+
# * The timezone is recommended to prevent any issues with possible default timezone
|
101
|
+
# differences across servers, or environments.
|
102
|
+
module Cron
|
103
|
+
extend ActiveSupport::Concern
|
104
|
+
|
105
|
+
included do
|
106
|
+
include Restart
|
107
|
+
|
108
|
+
key :cron_schedule, String
|
109
|
+
|
110
|
+
before_create :rocket_job_set_run_at
|
111
|
+
|
112
|
+
validates_presence_of :cron_schedule
|
113
|
+
validates_each :cron_schedule do |record, attr, value|
|
114
|
+
begin
|
115
|
+
RocketJob::Plugins::Rufus::CronLine.new(value)
|
116
|
+
rescue ArgumentError => exc
|
117
|
+
record.errors.add(attr, exc.message)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def rocket_job_set_run_at
|
125
|
+
self.run_at = RocketJob::Plugins::Rufus::CronLine.new(cron_schedule).next_time
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -20,7 +20,7 @@ module RocketJob
|
|
20
20
|
# - silence noisy jobs by raising log level
|
21
21
|
def rocket_job_around_logger(&block)
|
22
22
|
logger.info('Start #perform')
|
23
|
-
logger.
|
23
|
+
logger.measure_info(
|
24
24
|
'Completed #perform',
|
25
25
|
metric: "rocketjob/#{self.class.name.underscore}/perform",
|
26
26
|
log_exception: :full,
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module RocketJob
|
5
|
+
module Plugins
|
6
|
+
# Ensure that a job will only run between certain hours of the day, regardless of when it was
|
7
|
+
# created/enqueued. Useful for creating a job now that should only be processed later during a
|
8
|
+
# specific time window. If the time window is already active the job is able to be processed
|
9
|
+
# immediately.
|
10
|
+
#
|
11
|
+
# Example: Process this job on Monday’s between 8am and 10am.
|
12
|
+
#
|
13
|
+
# Example: Run this job on the 1st of every month from midnight for the entire day.
|
14
|
+
#
|
15
|
+
# Since the cron schedule supports time zones it is easy to setup jobs to run at UTC or any other time zone.
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
# # Only run the job between the hours of 8:30am and 8:30pm. If it is after 8:30pm schedule
|
19
|
+
# # it to run at 8:30am the next day.
|
20
|
+
# class BusinessHoursJob < RocketJob::Job
|
21
|
+
# include RocketJob::Plugins::ProcessingWindow
|
22
|
+
#
|
23
|
+
# # Set the default processing_window
|
24
|
+
# rocket_job do |job|
|
25
|
+
# # The start of the processing window
|
26
|
+
# job.processing_schedule = "30 8 * * * America/New_York"
|
27
|
+
# # How long the processing window is:
|
28
|
+
# job.processing_duration = 12.hours
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def perform
|
32
|
+
# # Job will only run between 8:30am and 8:30pm Eastern
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# Note:
|
37
|
+
# If a job is created/enqueued during the processing window, but due to busy/unavailable workers
|
38
|
+
# is not processed during the window, the current job will be re-queued for the beginning
|
39
|
+
# of the next processing window.
|
40
|
+
module ProcessingWindow
|
41
|
+
extend ActiveSupport::Concern
|
42
|
+
|
43
|
+
included do
|
44
|
+
key :processing_schedule, String
|
45
|
+
key :processing_duration, Integer
|
46
|
+
|
47
|
+
before_create :rocket_job_processing_window_set_run_at
|
48
|
+
before_retry :rocket_job_processing_window_set_run_at
|
49
|
+
after_start :rocket_job_processing_window_check
|
50
|
+
|
51
|
+
validates_presence_of :processing_schedule, :processing_duration
|
52
|
+
validates_each :processing_schedule do |record, attr, value|
|
53
|
+
begin
|
54
|
+
RocketJob::Plugins::Rufus::CronLine.new(value)
|
55
|
+
rescue ArgumentError => exc
|
56
|
+
record.errors.add(attr, exc.message)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns [true|false] whether this job is currently inside its processing window
|
62
|
+
def rocket_job_processing_window_active?
|
63
|
+
time = Time.now
|
64
|
+
previous_time = rocket_job_processing_schedule.previous_time(time)
|
65
|
+
# Inside previous processing window?
|
66
|
+
previous_time + processing_duration > time
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Only process this job if it is still in its processing window
|
72
|
+
def rocket_job_processing_window_check
|
73
|
+
return if rocket_job_processing_window_active?
|
74
|
+
logger.warn("Processing window closed before job was processed. Job is re-scheduled to run at: #{rocket_job_processing_schedule.next_time}")
|
75
|
+
self.worker_name ||= 'inline'
|
76
|
+
self.requeue!(worker_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def rocket_job_processing_window_set_run_at
|
80
|
+
self.run_at = rocket_job_processing_schedule.next_time unless rocket_job_processing_window_active?
|
81
|
+
end
|
82
|
+
|
83
|
+
def rocket_job_processing_schedule
|
84
|
+
RocketJob::Plugins::Rufus::CronLine.new(processing_schedule)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|