rocketjob 3.4.3 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +27 -0
- data/bin/rocketjob +1 -1
- data/lib/rocket_job/active_worker.rb +4 -3
- data/lib/rocket_job/cli.rb +13 -12
- data/lib/rocket_job/config.rb +17 -13
- data/lib/rocket_job/dirmon_entry.rb +88 -91
- data/lib/rocket_job/extensions/mongo/logging.rb +8 -4
- data/lib/rocket_job/extensions/mongoid/factory.rb +8 -6
- data/lib/rocket_job/extensions/rocket_job_adapter.rb +2 -4
- data/lib/rocket_job/heartbeat.rb +7 -8
- data/lib/rocket_job/job_exception.rb +6 -5
- data/lib/rocket_job/jobs/dirmon_job.rb +5 -7
- data/lib/rocket_job/jobs/housekeeping_job.rb +3 -2
- data/lib/rocket_job/jobs/on_demand_job.rb +104 -0
- data/lib/rocket_job/jobs/simple_job.rb +0 -2
- data/lib/rocket_job/jobs/upload_file_job.rb +100 -0
- data/lib/rocket_job/performance.rb +15 -10
- data/lib/rocket_job/plugins/cron.rb +7 -124
- data/lib/rocket_job/plugins/document.rb +8 -10
- data/lib/rocket_job/plugins/job/callbacks.rb +0 -1
- data/lib/rocket_job/plugins/job/logger.rb +0 -1
- data/lib/rocket_job/plugins/job/model.rb +15 -20
- data/lib/rocket_job/plugins/job/persistence.rb +3 -13
- data/lib/rocket_job/plugins/job/state_machine.rb +1 -2
- data/lib/rocket_job/plugins/job/throttle.rb +16 -12
- data/lib/rocket_job/plugins/job/worker.rb +15 -19
- data/lib/rocket_job/plugins/processing_window.rb +2 -2
- data/lib/rocket_job/plugins/restart.rb +3 -4
- data/lib/rocket_job/plugins/retry.rb +2 -3
- data/lib/rocket_job/plugins/singleton.rb +2 -3
- data/lib/rocket_job/plugins/state_machine.rb +19 -23
- data/lib/rocket_job/rocket_job.rb +4 -5
- data/lib/rocket_job/server.rb +35 -41
- data/lib/rocket_job/version.rb +2 -2
- data/lib/rocket_job/worker.rb +22 -21
- data/lib/rocketjob.rb +2 -0
- data/test/config/mongoid.yml +2 -2
- data/test/config_test.rb +0 -2
- data/test/dirmon_entry_test.rb +161 -134
- data/test/dirmon_job_test.rb +80 -78
- data/test/job_test.rb +0 -2
- data/test/jobs/housekeeping_job_test.rb +0 -1
- data/test/jobs/on_demand_job_test.rb +59 -0
- data/test/jobs/upload_file_job_test.rb +99 -0
- data/test/plugins/cron_test.rb +1 -3
- data/test/plugins/job/callbacks_test.rb +8 -13
- data/test/plugins/job/defaults_test.rb +0 -1
- data/test/plugins/job/logger_test.rb +2 -4
- data/test/plugins/job/model_test.rb +1 -2
- data/test/plugins/job/persistence_test.rb +0 -2
- data/test/plugins/job/state_machine_test.rb +0 -2
- data/test/plugins/job/throttle_test.rb +6 -8
- data/test/plugins/job/worker_test.rb +1 -2
- data/test/plugins/processing_window_test.rb +0 -2
- data/test/plugins/restart_test.rb +0 -1
- data/test/plugins/retry_test.rb +1 -2
- data/test/plugins/singleton_test.rb +0 -2
- data/test/plugins/state_machine_event_callbacks_test.rb +1 -2
- data/test/plugins/state_machine_test.rb +0 -2
- data/test/plugins/transaction_test.rb +5 -7
- data/test/test_db.sqlite3 +0 -0
- data/test/test_helper.rb +2 -1
- metadata +42 -36
@@ -7,7 +7,7 @@ module RocketJob
|
|
7
7
|
# specific time window. If the time window is already active the job is able to be processed
|
8
8
|
# immediately.
|
9
9
|
#
|
10
|
-
# Example: Process this job on Monday
|
10
|
+
# Example: Process this job on Monday's between 8am and 10am.
|
11
11
|
#
|
12
12
|
# Example: Run this job on the 1st of every month from midnight for the entire day.
|
13
13
|
#
|
@@ -70,7 +70,7 @@ module RocketJob
|
|
70
70
|
return if rocket_job_processing_window_active?
|
71
71
|
logger.warn("Processing window closed before job was processed. Job is re-scheduled to run at: #{rocket_job_processing_schedule.next_time}")
|
72
72
|
self.worker_name ||= 'inline'
|
73
|
-
|
73
|
+
requeue!(worker_name)
|
74
74
|
end
|
75
75
|
|
76
76
|
def rocket_job_processing_window_set_run_at
|
@@ -23,7 +23,7 @@ module RocketJob
|
|
23
23
|
# class RestartableJob < RocketJob::Job
|
24
24
|
# include RocketJob::Plugins::Restart
|
25
25
|
#
|
26
|
-
# # Retain the completed job under the completed tab in Rocket Job
|
26
|
+
# # Retain the completed job under the completed tab in Rocket Job Web Interface.
|
27
27
|
# self.destroy_on_complete = false
|
28
28
|
#
|
29
29
|
# # Will be copied to the new job on restart.
|
@@ -91,8 +91,8 @@ module RocketJob
|
|
91
91
|
logger.info('Job has expired. Not creating a new instance.')
|
92
92
|
return
|
93
93
|
end
|
94
|
-
|
95
|
-
rocket_job_restart_create(
|
94
|
+
attributes = rocket_job_restart_attributes.each_with_object({}) { |attr, attrs| attrs[attr] = send(attr) }
|
95
|
+
rocket_job_restart_create(attributes)
|
96
96
|
end
|
97
97
|
|
98
98
|
def rocket_job_restart_abort
|
@@ -117,7 +117,6 @@ module RocketJob
|
|
117
117
|
logger.warn("New job instance not started: #{job.errors.messages.inspect}")
|
118
118
|
false
|
119
119
|
end
|
120
|
-
|
121
120
|
end
|
122
121
|
end
|
123
122
|
end
|
@@ -77,8 +77,8 @@ module RocketJob
|
|
77
77
|
|
78
78
|
now = Time.now
|
79
79
|
self.run_at = now + delay_seconds
|
80
|
-
|
81
|
-
new_record? ? self.retry :
|
80
|
+
failed_at_list << now
|
81
|
+
new_record? ? self.retry : retry!
|
82
82
|
end
|
83
83
|
|
84
84
|
# Prevent exception from being cleared on retry
|
@@ -93,7 +93,6 @@ module RocketJob
|
|
93
93
|
def rocket_job_retry_seconds_to_delay
|
94
94
|
(rocket_job_failure_count ** 4) + 15 + (rand(30) * (rocket_job_failure_count + 1))
|
95
95
|
end
|
96
|
-
|
97
96
|
end
|
98
97
|
end
|
99
98
|
end
|
@@ -8,7 +8,7 @@ module RocketJob
|
|
8
8
|
|
9
9
|
included do
|
10
10
|
# Validation prevents a new job from being saved while one is already running
|
11
|
-
validates_each :state do |record, attr,
|
11
|
+
validates_each :state do |record, attr, _value|
|
12
12
|
if (record.running? || record.queued? || record.paused?) && record.rocket_job_singleton_active?
|
13
13
|
record.errors.add(attr, "Another instance of #{record.class.name} is already queued or running")
|
14
14
|
end
|
@@ -16,10 +16,9 @@ module RocketJob
|
|
16
16
|
|
17
17
|
# Returns [true|false] whether another instance of this job is already active
|
18
18
|
def rocket_job_singleton_active?
|
19
|
-
self.class.where(:state.in => [
|
19
|
+
self.class.where(:state.in => %i[running queued], :id.ne => id).exists?
|
20
20
|
end
|
21
21
|
end
|
22
|
-
|
23
22
|
end
|
24
23
|
end
|
25
24
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'thread'
|
2
1
|
require 'active_support/concern'
|
3
2
|
require 'aasm'
|
4
3
|
|
@@ -34,32 +33,31 @@ module RocketJob
|
|
34
33
|
# Adds a :before or :after callback to an event
|
35
34
|
# state_machine_add_event_callback(:start, :before, :my_method)
|
36
35
|
def self.state_machine_add_event_callback(event_name, action, *methods, &block)
|
37
|
-
raise(ArgumentError, 'Cannot supply both a method name and a block') if
|
38
|
-
raise(ArgumentError, 'Must supply either a method name or a block') unless
|
36
|
+
raise(ArgumentError, 'Cannot supply both a method name and a block') if methods.size.positive? && block
|
37
|
+
raise(ArgumentError, 'Must supply either a method name or a block') unless methods.size.positive? || block
|
39
38
|
|
40
|
-
# TODO Somehow get AASM to support options such as :if and :unless to be consistent with other callbacks
|
39
|
+
# TODO: Somehow get AASM to support options such as :if and :unless to be consistent with other callbacks
|
41
40
|
# For example:
|
42
41
|
# before_start :my_callback, unless: :encrypted?
|
43
42
|
# before_start :my_callback, if: :encrypted?
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
43
|
+
event = aasm.state_machine.events[event_name]
|
44
|
+
raise(ArgumentError, "Unknown event: #{event_name.inspect}") unless event
|
45
|
+
|
46
|
+
values = Array(event.options[action])
|
47
|
+
code =
|
48
|
+
if block
|
49
|
+
block
|
50
|
+
else
|
51
|
+
# Validate methods are any of Symbol String Proc
|
52
|
+
methods.each do |method|
|
53
|
+
unless method.is_a?(Symbol) || method.is_a?(String)
|
54
|
+
raise(ArgumentError, "#{action}_#{event_name} currently does not support any options. Only Symbol and String method names can be supplied.")
|
55
55
|
end
|
56
|
-
methods
|
57
56
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
57
|
+
methods
|
58
|
+
end
|
59
|
+
action == :before ? values.push(code) : values.unshift(code)
|
60
|
+
event.options[action] = values.flatten.uniq
|
63
61
|
end
|
64
62
|
|
65
63
|
def self.state_machine_define_event_callbacks(*event_names)
|
@@ -75,9 +73,7 @@ module RocketJob
|
|
75
73
|
RUBY
|
76
74
|
end
|
77
75
|
end
|
78
|
-
|
79
76
|
end
|
80
|
-
|
81
77
|
end
|
82
78
|
end
|
83
79
|
end
|
@@ -19,20 +19,20 @@ module RocketJob
|
|
19
19
|
# Returns a human readable duration from the supplied [Float] number of seconds
|
20
20
|
def self.seconds_as_duration(seconds)
|
21
21
|
return nil unless seconds
|
22
|
-
if seconds >=
|
23
|
-
"#{(seconds /
|
22
|
+
if seconds >= 86_400.0 # 1 day
|
23
|
+
"#{(seconds / 86_400).to_i}d #{Time.at(seconds).strftime('%-Hh %-Mm')}"
|
24
24
|
elsif seconds >= 3600.0 # 1 hour
|
25
25
|
Time.at(seconds).strftime('%-Hh %-Mm')
|
26
26
|
elsif seconds >= 60.0 # 1 minute
|
27
27
|
Time.at(seconds).strftime('%-Mm %-Ss')
|
28
28
|
elsif seconds >= 1.0 # 1 second
|
29
|
-
|
29
|
+
format('%.3fs', seconds)
|
30
30
|
else
|
31
31
|
duration = seconds * 1000
|
32
32
|
if defined? JRuby
|
33
33
|
"#{duration.to_i}ms"
|
34
34
|
else
|
35
|
-
duration < 10.0 ?
|
35
|
+
duration < 10.0 ? format('%.3fms', duration) : format('%.1fms', duration)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
end
|
@@ -49,5 +49,4 @@ module RocketJob
|
|
49
49
|
def self.rails!
|
50
50
|
@rails = true
|
51
51
|
end
|
52
|
-
|
53
52
|
end
|
data/lib/rocket_job/server.rb
CHANGED
@@ -107,7 +107,7 @@ module RocketJob
|
|
107
107
|
|
108
108
|
# Stop all running, paused, or starting servers
|
109
109
|
def self.stop_all
|
110
|
-
where(:state.in => [
|
110
|
+
where(:state.in => %i[running paused starting]).each(&:stop!)
|
111
111
|
end
|
112
112
|
|
113
113
|
# Pause all running servers
|
@@ -194,7 +194,7 @@ module RocketJob
|
|
194
194
|
server = create!(attrs)
|
195
195
|
server.send(:run)
|
196
196
|
ensure
|
197
|
-
server
|
197
|
+
server&.destroy
|
198
198
|
end
|
199
199
|
|
200
200
|
# Returns [Boolean] whether the server is shutting down
|
@@ -207,10 +207,10 @@ module RocketJob
|
|
207
207
|
dead_seconds = Config.instance.heartbeat_seconds * missed
|
208
208
|
last_heartbeat_time = Time.now - dead_seconds
|
209
209
|
where(
|
210
|
-
:state.in => [
|
210
|
+
:state.in => %i[stopping running paused],
|
211
211
|
'$or' => [
|
212
|
-
{
|
213
|
-
{
|
212
|
+
{'heartbeat.updated_at' => {'$exists' => false}},
|
213
|
+
{'heartbeat.updated_at' => {'$lte' => last_heartbeat_time}}
|
214
214
|
]
|
215
215
|
)
|
216
216
|
end
|
@@ -230,8 +230,6 @@ module RocketJob
|
|
230
230
|
|
231
231
|
private
|
232
232
|
|
233
|
-
attr_reader :workers
|
234
|
-
|
235
233
|
# Returns [Array<Worker>] collection of workers
|
236
234
|
def workers
|
237
235
|
@workers ||= []
|
@@ -242,7 +240,7 @@ module RocketJob
|
|
242
240
|
logger.info "Using MongoDB Database: #{RocketJob::Job.collection.database.name}"
|
243
241
|
build_heartbeat(updated_at: Time.now, workers: 0)
|
244
242
|
started!
|
245
|
-
logger.info '
|
243
|
+
logger.info 'Rocket Job Server started'
|
246
244
|
|
247
245
|
run_workers
|
248
246
|
|
@@ -250,7 +248,7 @@ module RocketJob
|
|
250
248
|
# Tell each worker to shutdown cleanly
|
251
249
|
workers.each(&:shutdown!)
|
252
250
|
|
253
|
-
while worker = workers.first
|
251
|
+
while (worker = workers.first)
|
254
252
|
if worker.join(5)
|
255
253
|
# Worker thread is dead
|
256
254
|
workers.shift
|
@@ -270,9 +268,7 @@ module RocketJob
|
|
270
268
|
logger.error('RocketJob::Server is stopping due to an exception', exc)
|
271
269
|
ensure
|
272
270
|
# Logs the backtrace for each running worker
|
273
|
-
if
|
274
|
-
workers.each { |worker| logger.backtrace(thread: worker.thread) if worker.thread && worker.alive? }
|
275
|
-
end
|
271
|
+
workers.each { |worker| logger.backtrace(thread: worker.thread) if worker.thread && worker.alive? }
|
276
272
|
end
|
277
273
|
|
278
274
|
def run_workers
|
@@ -332,18 +328,18 @@ module RocketJob
|
|
332
328
|
return unless running?
|
333
329
|
|
334
330
|
# Need to add more workers?
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
331
|
+
return unless count < max_workers
|
332
|
+
|
333
|
+
worker_count = max_workers - count
|
334
|
+
logger.info "Starting #{worker_count} workers"
|
335
|
+
worker_count.times.each do
|
336
|
+
sleep(Config.instance.max_poll_seconds.to_f / max_workers) if stagger_workers
|
337
|
+
return if shutdown?
|
338
|
+
# Start worker
|
339
|
+
begin
|
340
|
+
workers << Worker.new(id: next_worker_id, server_name: name, filter: filter)
|
341
|
+
rescue Exception => exc
|
342
|
+
logger.fatal('Cannot start worker', exc)
|
347
343
|
end
|
348
344
|
end
|
349
345
|
end
|
@@ -353,30 +349,28 @@ module RocketJob
|
|
353
349
|
# Perform clean shutdown
|
354
350
|
#
|
355
351
|
def self.register_signal_handlers
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
end
|
352
|
+
Signal.trap 'SIGTERM' do
|
353
|
+
shutdown!
|
354
|
+
message = 'Shutdown signal (SIGTERM) received. Will shutdown as soon as active jobs/slices have completed.'
|
355
|
+
# Logging uses a mutex to access Queue on MRI/CRuby
|
356
|
+
defined?(JRuby) ? logger.warn(message) : puts(message)
|
357
|
+
end
|
363
358
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
end
|
370
|
-
rescue StandardError
|
371
|
-
logger.warn 'SIGTERM handler not installed. Not able to shutdown gracefully'
|
359
|
+
Signal.trap 'INT' do
|
360
|
+
shutdown!
|
361
|
+
message = 'Shutdown signal (INT) received. Will shutdown as soon as active jobs/slices have completed.'
|
362
|
+
# Logging uses a mutex to access Queue on MRI/CRuby
|
363
|
+
defined?(JRuby) ? logger.warn(message) : puts(message)
|
372
364
|
end
|
365
|
+
rescue StandardError
|
366
|
+
logger.warn 'SIGTERM handler not installed. Not able to shutdown gracefully'
|
373
367
|
end
|
374
368
|
|
369
|
+
private_class_method :register_signal_handlers
|
370
|
+
|
375
371
|
# Requeue any jobs assigned to this server when it is destroyed
|
376
372
|
def requeue_jobs
|
377
373
|
RocketJob::Job.requeue_dead_server(name)
|
378
374
|
end
|
379
|
-
|
380
375
|
end
|
381
376
|
end
|
382
|
-
|
data/lib/rocket_job/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module RocketJob
|
2
|
-
VERSION = '3.
|
1
|
+
module RocketJob
|
2
|
+
VERSION = '3.5.0'.freeze
|
3
3
|
end
|
data/lib/rocket_job/worker.rb
CHANGED
@@ -4,7 +4,7 @@ module RocketJob
|
|
4
4
|
# Worker
|
5
5
|
#
|
6
6
|
# A worker runs on a single operating system thread
|
7
|
-
# Is usually started under a
|
7
|
+
# Is usually started under a Rocket Job server process.
|
8
8
|
class Worker
|
9
9
|
include SemanticLogger::Loggable
|
10
10
|
include ActiveSupport::Callbacks
|
@@ -31,14 +31,19 @@ module RocketJob
|
|
31
31
|
set_callback(:running, :around, *filters, &blk)
|
32
32
|
end
|
33
33
|
|
34
|
-
def initialize(id: 0,
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
34
|
+
def initialize(id: 0,
|
35
|
+
server_name: 'inline:0',
|
36
|
+
inline: false,
|
37
|
+
re_check_seconds: Config.instance.re_check_seconds,
|
38
|
+
filter: nil)
|
39
|
+
@id = id
|
40
|
+
@server_name = server_name
|
41
|
+
@shutdown =
|
42
|
+
if defined?(Concurrent::JavaAtomicBoolean) || defined?(Concurrent::CAtomicBoolean)
|
43
|
+
Concurrent::AtomicBoolean.new(false)
|
44
|
+
else
|
45
|
+
false
|
46
|
+
end
|
42
47
|
@name = "#{server_name}:#{id}"
|
43
48
|
@re_check_seconds = (re_check_seconds || 60).to_f
|
44
49
|
@re_check_start = Time.now
|
@@ -74,9 +79,9 @@ module RocketJob
|
|
74
79
|
# worker_id [Integer]
|
75
80
|
# The number of this worker for logging purposes
|
76
81
|
def run
|
77
|
-
Thread.current.name = 'rocketjob %03i'
|
82
|
+
Thread.current.name = format('rocketjob %03i', id)
|
78
83
|
logger.info 'Started'
|
79
|
-
|
84
|
+
until shutdown?
|
80
85
|
if process_available_jobs
|
81
86
|
# Keeps workers staggered across the poll interval so that
|
82
87
|
# all workers don't poll at the same time
|
@@ -97,15 +102,13 @@ module RocketJob
|
|
97
102
|
# Returns [Boolean] whether any job was actually processed
|
98
103
|
def process_available_jobs
|
99
104
|
processed = false
|
100
|
-
|
105
|
+
until shutdown?
|
101
106
|
reset_filter_if_expired
|
102
107
|
job = Job.rocket_job_next_job(name, current_filter)
|
103
108
|
break unless job
|
104
109
|
|
105
110
|
SemanticLogger.named_tagged(job: job.id.to_s) do
|
106
|
-
unless job.rocket_job_work(self, false, current_filter)
|
107
|
-
processed = true
|
108
|
-
end
|
111
|
+
processed = true unless job.rocket_job_work(self, false, current_filter)
|
109
112
|
end
|
110
113
|
end
|
111
114
|
processed
|
@@ -115,12 +118,10 @@ module RocketJob
|
|
115
118
|
def reset_filter_if_expired
|
116
119
|
# Only clear out the current_filter after every `re_check_seconds`
|
117
120
|
time = Time.now
|
118
|
-
|
119
|
-
@re_check_start = time
|
120
|
-
self.current_filter = filter.dup
|
121
|
-
end
|
122
|
-
end
|
121
|
+
return unless (time - @re_check_start) > re_check_seconds
|
123
122
|
|
123
|
+
@re_check_start = time
|
124
|
+
self.current_filter = filter.dup
|
125
|
+
end
|
124
126
|
end
|
125
127
|
end
|
126
|
-
|
data/lib/rocketjob.rb
CHANGED
@@ -47,8 +47,10 @@ module RocketJob
|
|
47
47
|
module Jobs
|
48
48
|
autoload :ActiveJob, 'rocket_job/jobs/active_job'
|
49
49
|
autoload :DirmonJob, 'rocket_job/jobs/dirmon_job'
|
50
|
+
autoload :OnDemandJob, 'rocket_job/jobs/on_demand_job'
|
50
51
|
autoload :HousekeepingJob, 'rocket_job/jobs/housekeeping_job'
|
51
52
|
autoload :SimpleJob, 'rocket_job/jobs/simple_job'
|
53
|
+
autoload :UploadFileJob, 'rocket_job/jobs/upload_file_job'
|
52
54
|
end
|
53
55
|
end
|
54
56
|
|
data/test/config/mongoid.yml
CHANGED
@@ -38,7 +38,7 @@ mongoid_options: &mongoid_options
|
|
38
38
|
development:
|
39
39
|
clients:
|
40
40
|
default: &default_development
|
41
|
-
uri: mongodb://
|
41
|
+
uri: mongodb://127.0.0.1:27017/rocketjob_development
|
42
42
|
options:
|
43
43
|
<<: *client_options
|
44
44
|
write:
|
@@ -55,7 +55,7 @@ development:
|
|
55
55
|
test:
|
56
56
|
clients:
|
57
57
|
default: &default_test
|
58
|
-
uri: mongodb://
|
58
|
+
uri: mongodb://127.0.0.1:27017/rocketjob_test
|
59
59
|
options:
|
60
60
|
<<: *client_options
|
61
61
|
write:
|