rocketjob 3.4.3 → 3.5.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 +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:
|