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.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +27 -0
  3. data/bin/rocketjob +1 -1
  4. data/lib/rocket_job/active_worker.rb +4 -3
  5. data/lib/rocket_job/cli.rb +13 -12
  6. data/lib/rocket_job/config.rb +17 -13
  7. data/lib/rocket_job/dirmon_entry.rb +88 -91
  8. data/lib/rocket_job/extensions/mongo/logging.rb +8 -4
  9. data/lib/rocket_job/extensions/mongoid/factory.rb +8 -6
  10. data/lib/rocket_job/extensions/rocket_job_adapter.rb +2 -4
  11. data/lib/rocket_job/heartbeat.rb +7 -8
  12. data/lib/rocket_job/job_exception.rb +6 -5
  13. data/lib/rocket_job/jobs/dirmon_job.rb +5 -7
  14. data/lib/rocket_job/jobs/housekeeping_job.rb +3 -2
  15. data/lib/rocket_job/jobs/on_demand_job.rb +104 -0
  16. data/lib/rocket_job/jobs/simple_job.rb +0 -2
  17. data/lib/rocket_job/jobs/upload_file_job.rb +100 -0
  18. data/lib/rocket_job/performance.rb +15 -10
  19. data/lib/rocket_job/plugins/cron.rb +7 -124
  20. data/lib/rocket_job/plugins/document.rb +8 -10
  21. data/lib/rocket_job/plugins/job/callbacks.rb +0 -1
  22. data/lib/rocket_job/plugins/job/logger.rb +0 -1
  23. data/lib/rocket_job/plugins/job/model.rb +15 -20
  24. data/lib/rocket_job/plugins/job/persistence.rb +3 -13
  25. data/lib/rocket_job/plugins/job/state_machine.rb +1 -2
  26. data/lib/rocket_job/plugins/job/throttle.rb +16 -12
  27. data/lib/rocket_job/plugins/job/worker.rb +15 -19
  28. data/lib/rocket_job/plugins/processing_window.rb +2 -2
  29. data/lib/rocket_job/plugins/restart.rb +3 -4
  30. data/lib/rocket_job/plugins/retry.rb +2 -3
  31. data/lib/rocket_job/plugins/singleton.rb +2 -3
  32. data/lib/rocket_job/plugins/state_machine.rb +19 -23
  33. data/lib/rocket_job/rocket_job.rb +4 -5
  34. data/lib/rocket_job/server.rb +35 -41
  35. data/lib/rocket_job/version.rb +2 -2
  36. data/lib/rocket_job/worker.rb +22 -21
  37. data/lib/rocketjob.rb +2 -0
  38. data/test/config/mongoid.yml +2 -2
  39. data/test/config_test.rb +0 -2
  40. data/test/dirmon_entry_test.rb +161 -134
  41. data/test/dirmon_job_test.rb +80 -78
  42. data/test/job_test.rb +0 -2
  43. data/test/jobs/housekeeping_job_test.rb +0 -1
  44. data/test/jobs/on_demand_job_test.rb +59 -0
  45. data/test/jobs/upload_file_job_test.rb +99 -0
  46. data/test/plugins/cron_test.rb +1 -3
  47. data/test/plugins/job/callbacks_test.rb +8 -13
  48. data/test/plugins/job/defaults_test.rb +0 -1
  49. data/test/plugins/job/logger_test.rb +2 -4
  50. data/test/plugins/job/model_test.rb +1 -2
  51. data/test/plugins/job/persistence_test.rb +0 -2
  52. data/test/plugins/job/state_machine_test.rb +0 -2
  53. data/test/plugins/job/throttle_test.rb +6 -8
  54. data/test/plugins/job/worker_test.rb +1 -2
  55. data/test/plugins/processing_window_test.rb +0 -2
  56. data/test/plugins/restart_test.rb +0 -1
  57. data/test/plugins/retry_test.rb +1 -2
  58. data/test/plugins/singleton_test.rb +0 -2
  59. data/test/plugins/state_machine_event_callbacks_test.rb +1 -2
  60. data/test/plugins/state_machine_test.rb +0 -2
  61. data/test/plugins/transaction_test.rb +5 -7
  62. data/test/test_db.sqlite3 +0 -0
  63. data/test/test_helper.rb +2 -1
  64. 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 Mondays between 8am and 10am.
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
- self.requeue!(worker_name)
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 Mission Control.
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
- attrs = rocket_job_restart_attributes.reduce({}) { |attrs, attr| attrs[attr] = send(attr); attrs }
95
- rocket_job_restart_create(attrs)
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
- self.failed_at_list << now
81
- new_record? ? self.retry : 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, value|
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 => [:running, :queued], :id.ne => id).exists?
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 (methods.size > 0) && block
38
- raise(ArgumentError, 'Must supply either a method name or a block') unless (methods.size > 0) || block
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
- if event = aasm.state_machine.events[event_name]
45
- values = Array(event.options[action])
46
- code =
47
- if block
48
- block
49
- else
50
- # Validate methods are any of Symbol String Proc
51
- methods.each do |method|
52
- unless method.is_a?(Symbol) || method.is_a?(String)
53
- raise(ArgumentError, "#{action}_#{event_name} currently does not support any options. Only Symbol and String method names can be supplied.")
54
- end
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
- action == :before ? values.push(code) : values.unshift(code)
59
- event.options[action] = values.flatten.uniq
60
- else
61
- raise(ArgumentError, "Unknown event: #{event_name.inspect}")
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 >= 86400.0 # 1 day
23
- "#{(seconds / 86400).to_i}d #{Time.at(seconds).strftime('%-Hh %-Mm')}"
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
- "#{'%.3f' % seconds}s"
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 ? "#{'%.3f' % duration}ms" : "#{'%.1f' % duration}ms"
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
@@ -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 => [:running, :paused, :starting]).each(&:stop!)
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.destroy if 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 => [:stopping, :running, :paused],
210
+ :state.in => %i[stopping running paused],
211
211
  '$or' => [
212
- {"heartbeat.updated_at" => {'$exists' => false}},
213
- {"heartbeat.updated_at" => {'$lte' => last_heartbeat_time}}
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 'RocketJob Server started'
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 SemanticLogger::VERSION.to_i >= 4
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
- if count < max_workers
336
- worker_count = max_workers - count
337
- logger.info "Starting #{worker_count} workers"
338
- worker_count.times.each do
339
- sleep (Config.instance.max_poll_seconds.to_f / max_workers) if stagger_workers
340
- return if shutdown?
341
- # Start worker
342
- begin
343
- workers << Worker.new(id: next_worker_id, server_name: name, filter: filter)
344
- rescue Exception => exc
345
- logger.fatal('Cannot start worker', exc)
346
- end
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
- begin
357
- Signal.trap 'SIGTERM' do
358
- shutdown!
359
- message = 'Shutdown signal (SIGTERM) received. Will shutdown as soon as active jobs/slices have completed.'
360
- # Logging uses a mutex to access Queue on MRI/CRuby
361
- defined?(JRuby) ? logger.warn(message) : puts(message)
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
- Signal.trap 'INT' do
365
- shutdown!
366
- message = 'Shutdown signal (INT) received. Will shutdown as soon as active jobs/slices have completed.'
367
- # Logging uses a mutex to access Queue on MRI/CRuby
368
- defined?(JRuby) ? logger.warn(message) : puts(message)
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
-
@@ -1,3 +1,3 @@
1
- module RocketJob #:nodoc
2
- VERSION = '3.4.3'
1
+ module RocketJob
2
+ VERSION = '3.5.0'.freeze
3
3
  end
@@ -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 RocketJob server process.
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, server_name: 'inline:0', inline: false, re_check_seconds: Config.instance.re_check_seconds, filter: nil)
35
- @id = id
36
- @server_name = server_name
37
- if defined?(Concurrent::JavaAtomicBoolean) || defined?(Concurrent::CAtomicBoolean)
38
- @shutdown = Concurrent::AtomicBoolean.new(false)
39
- else
40
- @shutdown = false
41
- end
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' % id
82
+ Thread.current.name = format('rocketjob %03i', id)
78
83
  logger.info 'Started'
79
- while !shutdown?
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
- while !shutdown?
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
- if (time - @re_check_start) > re_check_seconds
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
 
@@ -38,7 +38,7 @@ mongoid_options: &mongoid_options
38
38
  development:
39
39
  clients:
40
40
  default: &default_development
41
- uri: mongodb://localhost:27017/rocketjob_development
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://localhost:27017/rocketjob_test
58
+ uri: mongodb://127.0.0.1:27017/rocketjob_test
59
59
  options:
60
60
  <<: *client_options
61
61
  write: