rocketjob 2.0.0.rc3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|