rocketjob 3.4.3 → 3.5.0

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