rocketjob 2.0.0 → 2.1.1
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 +4 -4
- data/README.md +8 -8
- data/Rakefile +4 -10
- data/lib/rocket_job/cli.rb +0 -11
- data/lib/rocket_job/config.rb +4 -3
- data/lib/rocket_job/dirmon_entry.rb +1 -1
- data/lib/rocket_job/extensions/aasm.rb +1 -0
- data/lib/rocket_job/extensions/mongo_mapper.rb +30 -0
- data/lib/rocket_job/extensions/rocket_job_adapter.rb +51 -0
- data/lib/rocket_job/jobs/housekeeping_job.rb +57 -0
- data/lib/rocket_job/plugins/cron.rb +3 -0
- data/lib/rocket_job/plugins/document.rb +6 -23
- data/lib/rocket_job/plugins/job/persistence.rb +1 -1
- data/lib/rocket_job/plugins/job/worker.rb +11 -8
- data/lib/rocket_job/plugins/restart.rb +1 -1
- data/lib/rocket_job/plugins/retry.rb +101 -0
- data/lib/rocket_job/version.rb +1 -1
- data/lib/rocket_job/worker.rb +22 -8
- data/lib/rocketjob.rb +4 -0
- data/test/dirmon_entry_test.rb +2 -2
- data/test/plugins/job/persistence_test.rb +17 -0
- data/test/plugins/processing_window_test.rb +1 -1
- metadata +16 -28
- data/lib/rocket_job/plugins/document/static.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eca6d0b99611683528bcec8d6db9756b1bb765a4
|
4
|
+
data.tar.gz: 2351fdabe936b770404e0c82d9a052579fac8ae4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fa9812d3413bd0dfb18dfe66d61e1f22e538d743030ed3e02d060d74e141e7128bada92fd8c4b24fea1305fcb99b5acfc70172cd5ffb3a309b20c9a6c39802d
|
7
|
+
data.tar.gz: 14a209e63e6abc3e491fb51da5760cd3df85bb37e5a12bed758630ecbac5ffe156f78ae22a00028591033a5ac29e5cc22f88c4d117844558f34f9d179442538c
|
data/README.md
CHANGED
@@ -1,13 +1,7 @@
|
|
1
1
|
# Rocket Job
|
2
|
-
[](https://rubygems.org/gems/rocketjob) [](https://travis-ci.org/rocketjob/rocketjob) [](http://opensource.org/licenses/Apache-2.0)  [](https://rubygems.org/gems/rocketjob) [](https://travis-ci.org/rocketjob/rocketjob) [](http://opensource.org/licenses/Apache-2.0)  [-Support-brightgreen.svg)](https://gitter.im/rocketjob/support)
|
3
3
|
|
4
|
-
|
5
|
-
Rocket Job makes it easy to reliably process data using jobs written in Ruby.
|
6
|
-
|
7
|
-
Outgrown existing Ruby background job processing solutions?
|
8
|
-
|
9
|
-
Upgrade to Rocket Job.
|
10
|
-
Or, start small with Rocket Job and seamlessly scale up to meet future business demands.
|
4
|
+
Ruby's missing batch system
|
11
5
|
|
12
6
|
Checkout http://rocketjob.io/
|
13
7
|
|
@@ -23,6 +17,12 @@ Checkout http://rocketjob.io/
|
|
23
17
|
* Questions? Join the chat room on Gitter for [rocketjob support](https://gitter.im/rocketjob/support)
|
24
18
|
* [Report bugs](https://github.com/rocketjob/rocketjob/issues)
|
25
19
|
|
20
|
+
## Ruby Support
|
21
|
+
|
22
|
+
Rocket Job is tested and supported on the following Ruby platforms:
|
23
|
+
- Ruby 2.1, 2.2, 2.3, and above
|
24
|
+
- JRuby 1.7.23, 9.0.5 and above
|
25
|
+
|
26
26
|
## Versioning
|
27
27
|
|
28
28
|
This project uses [Semantic Versioning](http://semver.org/).
|
data/Rakefile
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
require 'rake/clean'
|
2
1
|
require 'rake/testtask'
|
3
|
-
|
4
2
|
require_relative 'lib/rocket_job/version'
|
5
3
|
|
6
4
|
task :gem do
|
@@ -14,14 +12,10 @@ task publish: :gem do
|
|
14
12
|
system "rm rocketjob-#{RocketJob::VERSION}.gem"
|
15
13
|
end
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
t.verbose = true
|
22
|
-
end
|
23
|
-
|
24
|
-
Rake::Task['functional'].invoke
|
15
|
+
Rake::TestTask.new(:test) do |t|
|
16
|
+
t.pattern = 'test/**/*_test.rb'
|
17
|
+
t.verbose = true
|
18
|
+
t.warning = false
|
25
19
|
end
|
26
20
|
|
27
21
|
task default: :test
|
data/lib/rocket_job/cli.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'optparse'
|
2
|
-
require 'yaml'
|
3
2
|
require 'semantic_logger'
|
4
3
|
module RocketJob
|
5
4
|
# Command Line Interface parser for RocketJob
|
@@ -25,7 +24,6 @@ module RocketJob
|
|
25
24
|
setup_environment
|
26
25
|
setup_logger
|
27
26
|
rails? ? boot_rails : boot_standalone
|
28
|
-
# setup_metrics
|
29
27
|
write_pidfile
|
30
28
|
|
31
29
|
opts = {}
|
@@ -34,15 +32,6 @@ module RocketJob
|
|
34
32
|
Worker.run(opts)
|
35
33
|
end
|
36
34
|
|
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
|
-
|
46
35
|
def rails?
|
47
36
|
@rails ||= begin
|
48
37
|
boot_file = Pathname.new(directory).join('config/environment.rb').expand_path
|
data/lib/rocket_job/config.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'yaml'
|
1
2
|
# encoding: UTF-8
|
2
3
|
module RocketJob
|
3
4
|
# Centralized Configuration for Rocket Jobs
|
@@ -15,9 +16,9 @@ module RocketJob
|
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
|
-
# Useful for Testing, not recommended elsewhere
|
19
|
-
#
|
20
|
-
#
|
19
|
+
# Useful for Testing, not recommended elsewhere.
|
20
|
+
# When enabled all calls to `perform_later` will be redirected to `perform_now`.
|
21
|
+
# Also, exceptions will be raised instead of failing the job.
|
21
22
|
cattr_accessor(:inline_mode) { false }
|
22
23
|
|
23
24
|
# @formatter:off
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# TODO Delete this file once PR has been accepted
|
2
|
+
# https://github.com/mongomapper/mongomapper/pull/641
|
3
|
+
MongoMapper::Plugins::Keys::Static
|
4
|
+
module MongoMapper
|
5
|
+
module Plugins
|
6
|
+
module Keys
|
7
|
+
module Static
|
8
|
+
module ClassMethods
|
9
|
+
def embedded_keys
|
10
|
+
@embedded_keys ||= embedded_associations.collect(&:as)
|
11
|
+
end
|
12
|
+
|
13
|
+
def embedded_key?(key)
|
14
|
+
embedded_keys.include?(key.to_sym)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def load_from_database(attrs, with_cast = false)
|
21
|
+
return super if !self.class.static_keys || !attrs.respond_to?(:each)
|
22
|
+
|
23
|
+
attrs = attrs.select { |key, _| self.class.key?(key) || self.class.embedded_key?(key) }
|
24
|
+
|
25
|
+
super(attrs, with_cast)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#require 'rocketjob'
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module QueueAdapters
|
5
|
+
# == Rocket Job adapter for Active Job
|
6
|
+
#
|
7
|
+
# Ruby's missing batch system.
|
8
|
+
#
|
9
|
+
# Read more about Rocket Job {here}[http://rocketjob.io].
|
10
|
+
#
|
11
|
+
# To use Rocket Job set the queue_adapter config to +:rocket_job+.
|
12
|
+
#
|
13
|
+
# Rails.application.config.active_job.queue_adapter = :rocket_job
|
14
|
+
class RocketJobAdapter
|
15
|
+
def enqueue(active_job) #:nodoc:
|
16
|
+
rocket_job = JobWrapper.perform_later(active_job.serialize) do |job|
|
17
|
+
job.active_job_id = active_job.job_id
|
18
|
+
job.active_job_class = active_job.class.name
|
19
|
+
job.active_job_queue = active_job.queue_name
|
20
|
+
job.description = active_job.class.name
|
21
|
+
job.priority = active_job.priority if active_job.priority
|
22
|
+
end
|
23
|
+
active_job.provider_job_id = rocket_job.id.to_s
|
24
|
+
rocket_job
|
25
|
+
end
|
26
|
+
|
27
|
+
def enqueue_at(active_job, timestamp) #:nodoc:
|
28
|
+
rocket_job = JobWrapper.perform_later(active_job.serialize) do |job|
|
29
|
+
job.active_job_id = active_job.job_id
|
30
|
+
job.active_job_class = active_job.class.name
|
31
|
+
job.active_job_queue = active_job.queue_name
|
32
|
+
job.description = active_job.class.name
|
33
|
+
job.priority = active_job.priority if active_job.priority
|
34
|
+
job.run_at = Time.at(timestamp).utc
|
35
|
+
end
|
36
|
+
active_job.provider_job_id = rocket_job.id.to_s
|
37
|
+
rocket_job
|
38
|
+
end
|
39
|
+
|
40
|
+
class JobWrapper < RocketJob::Job #:nodoc:
|
41
|
+
key :active_job_id, String
|
42
|
+
key :active_job_class, String
|
43
|
+
key :active_job_queue, String
|
44
|
+
|
45
|
+
def perform(job_data)
|
46
|
+
Base.execute job_data
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RocketJob
|
2
|
+
module Jobs
|
3
|
+
# Applies Retention policies to how long jobs are kept.
|
4
|
+
#
|
5
|
+
# Retentions are specific to each state so that for example completed
|
6
|
+
# jobs can be cleaned up before jobs that are running.
|
7
|
+
#
|
8
|
+
# Only one active instance of this housekeeping job is permitted at a time.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
# RocketJob::Jobs::HousekeepingJob.create!
|
12
|
+
#
|
13
|
+
# Example, with the default values that can be modified:
|
14
|
+
# RocketJob::Jobs::HousekeepingJob.create!(
|
15
|
+
# aborted_retention: 7.days,
|
16
|
+
# completed_retention: 7.days,
|
17
|
+
# failed_retention: 14.days,
|
18
|
+
# paused_retention: 90.days,
|
19
|
+
# queued_retention: nil
|
20
|
+
# )
|
21
|
+
#
|
22
|
+
# Example, overriding defaults and disabling removal of paused jobs:
|
23
|
+
# RocketJob::Jobs::HousekeepingJob.create!(
|
24
|
+
# aborted_retention: 1.day,
|
25
|
+
# completed_retention: 12.hours,
|
26
|
+
# failed_retention: 7.days,
|
27
|
+
# paused_retention: nil
|
28
|
+
# )
|
29
|
+
class HousekeepingJob < RocketJob::Job
|
30
|
+
include RocketJob::Plugins::Cron
|
31
|
+
include RocketJob::Plugins::Singleton
|
32
|
+
|
33
|
+
rocket_job do |job|
|
34
|
+
job.priority = 50
|
35
|
+
job.description = 'Cleans out historical jobs'
|
36
|
+
job.cron_schedule = '0 0 * * * America/New_York'
|
37
|
+
end
|
38
|
+
|
39
|
+
# Retention intervals in seconds
|
40
|
+
# Set to nil to not
|
41
|
+
key :aborted_retention, Integer, default: 7.days
|
42
|
+
key :completed_retention, Integer, default: 7.days
|
43
|
+
key :failed_retention, Integer, default: 14.days
|
44
|
+
key :paused_retention, Integer, default: 90.days
|
45
|
+
key :queued_retention, Integer
|
46
|
+
|
47
|
+
def perform
|
48
|
+
RocketJob::Job.where(state: :aborted, created_at: {'$lte' => aborted_retention.ago}).destroy_all if aborted_retention
|
49
|
+
RocketJob::Job.where(state: :completed, created_at: {'$lte' => completed_retention.ago}).destroy_all if completed_retention
|
50
|
+
RocketJob::Job.where(state: :failed, created_at: {'$lte' => failed_retention.ago}).destroy_all if failed_retention
|
51
|
+
RocketJob::Job.where(state: :paused, created_at: {'$lte' => paused_retention.ago}).destroy_all if paused_retention
|
52
|
+
RocketJob::Job.where(state: :queued, created_at: {'$lte' => queued_retention.ago}).destroy_all if queued_retention
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -8,20 +8,18 @@ module RocketJob
|
|
8
8
|
module Plugins
|
9
9
|
# Base class for storing models in MongoDB
|
10
10
|
module Document
|
11
|
-
autoload :Static, 'rocket_job/plugins/document/static'
|
12
|
-
|
13
11
|
extend ActiveSupport::Concern
|
14
12
|
include MongoMapper::Document
|
15
|
-
include RocketJob::Plugins::Document::Static
|
16
13
|
|
17
14
|
included do
|
18
|
-
#
|
19
|
-
define_model_callbacks :initialize, :find, :only => [:after]
|
20
|
-
|
21
|
-
# Prevent data in MongoDB from re-defining the model behavior
|
15
|
+
# Prevent data in MongoDB from re-defining the model behavior.
|
22
16
|
self.static_keys = true
|
23
17
|
|
24
|
-
#
|
18
|
+
# Only save changes to this instance to prevent losing
|
19
|
+
# changes made by other processes or threads.
|
20
|
+
self.partial_updates = true
|
21
|
+
|
22
|
+
# Turn off embedded callbacks. Slow and not used by Jobs.
|
25
23
|
embedded_callbacks_off
|
26
24
|
end
|
27
25
|
|
@@ -38,21 +36,6 @@ module RocketJob
|
|
38
36
|
end
|
39
37
|
end
|
40
38
|
|
41
|
-
# Add after_initialize callbacks
|
42
|
-
# TODO: Remove after new MongoMapper gem is released
|
43
|
-
# Also remove define_model_callbacks above
|
44
|
-
def initialize(*)
|
45
|
-
run_callbacks(:initialize) { super }
|
46
|
-
end
|
47
|
-
|
48
|
-
def initialize_from_database(*)
|
49
|
-
run_callbacks(:initialize) do
|
50
|
-
run_callbacks(:find) do
|
51
|
-
super
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
39
|
private
|
57
40
|
|
58
41
|
def update_attributes_and_reload(attrs)
|
@@ -69,7 +69,10 @@ module RocketJob
|
|
69
69
|
|
70
70
|
# Requeues all jobs that were running on worker that died
|
71
71
|
def self.requeue_dead_worker(worker_name)
|
72
|
-
|
72
|
+
# TODO Need to requeue paused, failed since user may have transitioned job before it finished
|
73
|
+
running.each do |job|
|
74
|
+
job.requeue!(worker_name) if job.may_requeue?(worker_name)
|
75
|
+
end
|
73
76
|
end
|
74
77
|
|
75
78
|
# Turn off embedded callbacks. Slow and not used for Jobs
|
@@ -97,7 +100,7 @@ module RocketJob
|
|
97
100
|
worker = RocketJob::Worker.new(name: 'inline')
|
98
101
|
worker.started
|
99
102
|
start if may_start?
|
100
|
-
# Raise exceptions
|
103
|
+
# Re-Raise exceptions
|
101
104
|
rocket_job_work(worker, true) if running?
|
102
105
|
result
|
103
106
|
end
|
@@ -113,10 +116,10 @@ module RocketJob
|
|
113
116
|
# worker_name: [String]
|
114
117
|
# Name of the worker on which the exception has occurred
|
115
118
|
#
|
116
|
-
#
|
119
|
+
# re_raise_exceptions: [true|false]
|
117
120
|
# Re-raise the exception after updating the job
|
118
|
-
# Default:
|
119
|
-
def rocket_job_fail_on_exception!(worker_name,
|
121
|
+
# Default: false
|
122
|
+
def rocket_job_fail_on_exception!(worker_name, re_raise_exceptions = false)
|
120
123
|
yield
|
121
124
|
rescue Exception => exc
|
122
125
|
if failed? || !may_fail?
|
@@ -130,7 +133,7 @@ module RocketJob
|
|
130
133
|
fail!(worker_name, exc)
|
131
134
|
end
|
132
135
|
end
|
133
|
-
raise exc if
|
136
|
+
raise exc if re_raise_exceptions
|
134
137
|
end
|
135
138
|
|
136
139
|
# Works on this job
|
@@ -141,9 +144,9 @@ module RocketJob
|
|
141
144
|
# is set in the job itself.
|
142
145
|
#
|
143
146
|
# Thread-safe, can be called by multiple threads at the same time
|
144
|
-
def rocket_job_work(worker,
|
147
|
+
def rocket_job_work(worker, re_raise_exceptions = false)
|
145
148
|
raise(ArgumentError, 'Job must be started before calling #rocket_job_work') unless running?
|
146
|
-
rocket_job_fail_on_exception!(worker.name,
|
149
|
+
rocket_job_fail_on_exception!(worker.name, re_raise_exceptions) do
|
147
150
|
run_callbacks :perform do
|
148
151
|
# Allow callbacks to fail, complete or abort the job
|
149
152
|
if running?
|
@@ -18,7 +18,7 @@ module RocketJob
|
|
18
18
|
extend ActiveSupport::Concern
|
19
19
|
|
20
20
|
# Attributes to exclude when copying across the attributes to the new instance
|
21
|
-
RESTART_EXCLUDES = %w(_id state created_at started_at completed_at failure_count worker_name percent_complete exception result run_at)
|
21
|
+
RESTART_EXCLUDES = %w(_id state created_at started_at completed_at failure_count worker_name percent_complete exception result run_at record_count sub_state)
|
22
22
|
|
23
23
|
included do
|
24
24
|
after_abort :rocket_job_restart_new_instance
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module RocketJob
|
5
|
+
module Plugins
|
6
|
+
# Automatically retry the job on failure.
|
7
|
+
#
|
8
|
+
# The following retry algorithm is used to automatically retry a job when it fails:
|
9
|
+
# Failed jobs are aborted so that they cannot be restarted since a new instance has there are workers
|
10
|
+
# available to run. For example if workers are busy working on higher priority jobs, then the job
|
11
|
+
# will only run once those jobs have completed, or their priority lowered. Additionally, while the
|
12
|
+
# job is queued no additional instances will be enqueued, even if the next cron interval has been reached.
|
13
|
+
#
|
14
|
+
# Note:
|
15
|
+
# - After failure the job is scheduled to run again in the future.
|
16
|
+
# - The job will not be retried if:
|
17
|
+
# - The job has expired.
|
18
|
+
# - The job fails validations.
|
19
|
+
# - The number of retry counts has been exceeded.
|
20
|
+
# - To see the number of times a job has failed so far:
|
21
|
+
# job.failure_count
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
#
|
25
|
+
# class MyJob < RocketJob::Job
|
26
|
+
# include RocketJob::Plugins::Retry
|
27
|
+
#
|
28
|
+
# # Set the default retry_count
|
29
|
+
# rocket_job do |job|
|
30
|
+
# job.max_retries = 3
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def perform
|
34
|
+
# puts "DONE"
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# # Queue the job for processing using the default cron_schedule specified above
|
39
|
+
# MyJob.create!
|
40
|
+
#
|
41
|
+
# # Replace the default retry_count
|
42
|
+
# MyCronJob.create!(max_retries: 10)
|
43
|
+
#
|
44
|
+
# # Disable retries for this job instance
|
45
|
+
# MyCronJob.create!(max_retries: 0)
|
46
|
+
#
|
47
|
+
module Retry
|
48
|
+
extend ActiveSupport::Concern
|
49
|
+
|
50
|
+
included do
|
51
|
+
after_fail :rocket_job_retry
|
52
|
+
|
53
|
+
# Maximum number of times to retry this job
|
54
|
+
# 25 is approximately 3 weeks of retries
|
55
|
+
key :max_retries, Integer, default: 25
|
56
|
+
|
57
|
+
# List of times when this job failed
|
58
|
+
key :failed_times, Array
|
59
|
+
|
60
|
+
# Make max_retries editable in Rocket Job Mission Control
|
61
|
+
public_rocket_job_properties :max_retries
|
62
|
+
|
63
|
+
validates_presence_of :max_retries
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns [true|false] whether this job will be automatically retried on failure
|
67
|
+
def rocket_job_retry_on_fail?
|
68
|
+
failure_count > max_retries
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def rocket_job_retry
|
74
|
+
# Failure count is incremented during before_fail
|
75
|
+
return if expired? || !rocket_job_retry_on_fail?
|
76
|
+
|
77
|
+
delay_seconds = rocket_job_retry_seconds_to_delay
|
78
|
+
logger.info "Job failed, automatically retrying in #{delay_seconds} seconds. Retry count: #{failure_count}"
|
79
|
+
|
80
|
+
now = Time.now
|
81
|
+
self.run_at = now + delay_seconds
|
82
|
+
self.failed_times << now
|
83
|
+
new_record? ? retry : retry!
|
84
|
+
end
|
85
|
+
|
86
|
+
# Prevent exception from being cleared on retry
|
87
|
+
def rocket_job_clear_exception
|
88
|
+
self.completed_at = nil
|
89
|
+
self.exception = nil unless rocket_job_retry_on_fail?
|
90
|
+
self.worker_name = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns [Time] when to retry this job at
|
94
|
+
# Same basic formula as Sidekiq and Delayed Job
|
95
|
+
def rocket_job_retry_seconds_to_delay
|
96
|
+
(failure_count ** 4) + 15 + (rand(30)*(failure_count+1))
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/rocket_job/version.rb
CHANGED
data/lib/rocket_job/worker.rb
CHANGED
@@ -178,7 +178,7 @@ module RocketJob
|
|
178
178
|
}
|
179
179
|
]
|
180
180
|
).each do |result|
|
181
|
-
counts[result['_id']] = result['count']
|
181
|
+
counts[result['_id'].to_sym] = result['count']
|
182
182
|
end
|
183
183
|
counts
|
184
184
|
end
|
@@ -195,7 +195,7 @@ module RocketJob
|
|
195
195
|
# - The worker process is "hanging"
|
196
196
|
# - The worker is no longer able to communicate with the MongoDB Server
|
197
197
|
def zombie?(missed = 4)
|
198
|
-
return false unless running?
|
198
|
+
return false unless running? || stopping?
|
199
199
|
return true if heartbeat.nil? || heartbeat.updated_at.nil?
|
200
200
|
dead_seconds = Config.instance.heartbeat_seconds * missed
|
201
201
|
(Time.now - heartbeat.updated_at) >= dead_seconds
|
@@ -260,13 +260,25 @@ module RocketJob
|
|
260
260
|
# Stop worker if shutdown indicator was set
|
261
261
|
stop! if self.class.shutdown? && may_stop?
|
262
262
|
end
|
263
|
+
|
263
264
|
logger.info 'Waiting for worker threads to stop'
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
265
|
+
while thread = worker_threads.first
|
266
|
+
if thread.join(5)
|
267
|
+
# Worker thread is dead
|
268
|
+
worker_threads.shift
|
269
|
+
else
|
270
|
+
# Timeout waiting for thread to stop
|
271
|
+
begin
|
272
|
+
update_attributes_and_reload(
|
273
|
+
'heartbeat.updated_at' => Time.now,
|
274
|
+
'heartbeat.current_threads' => worker_count
|
275
|
+
)
|
276
|
+
rescue MongoMapper::DocumentNotFound
|
277
|
+
logger.error('Worker has been destroyed. Going down hard!')
|
278
|
+
break
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
270
282
|
logger.info 'Shutdown'
|
271
283
|
rescue Exception => exc
|
272
284
|
logger.error('RocketJob::Worker is stopping due to an exception', exc)
|
@@ -336,6 +348,8 @@ module RocketJob
|
|
336
348
|
logger.info "Stopping. Worker state: #{state.inspect}"
|
337
349
|
rescue Exception => exc
|
338
350
|
logger.fatal('Unhandled exception in job processing thread', exc)
|
351
|
+
ensure
|
352
|
+
ActiveRecord::Base.clear_active_connections! if defined?(ActiveRecord::Base)
|
339
353
|
end
|
340
354
|
|
341
355
|
# Process the next available job
|
data/lib/rocketjob.rb
CHANGED
@@ -3,6 +3,7 @@ require 'semantic_logger'
|
|
3
3
|
require 'rocket_job/extensions/mongo'
|
4
4
|
require 'mongo_ha'
|
5
5
|
require 'mongo_mapper'
|
6
|
+
require 'rocket_job/extensions/mongo_mapper'
|
6
7
|
require 'rocket_job/version'
|
7
8
|
|
8
9
|
# @formatter:off
|
@@ -65,3 +66,6 @@ module RocketJob
|
|
65
66
|
end
|
66
67
|
|
67
68
|
end
|
69
|
+
|
70
|
+
# Add Active Job adapter for Rails
|
71
|
+
require 'rocket_job/extensions/rocket_job_adapter' if defined?(ActiveJob)
|
data/test/dirmon_entry_test.rb
CHANGED
@@ -207,7 +207,7 @@ class DirmonEntryTest < Minitest::Test
|
|
207
207
|
@file_name = @file.path
|
208
208
|
@pathname = Pathname.new(@file_name)
|
209
209
|
File.open(@file_name, 'w') { |file| file.write('Hello World') }
|
210
|
-
assert File.
|
210
|
+
assert File.exist?(@file_name)
|
211
211
|
@archive_real_name = @archive_path.join("#{@job.id}_#{File.basename(@file_name)}").to_s
|
212
212
|
end
|
213
213
|
|
@@ -230,7 +230,7 @@ class DirmonEntryTest < Minitest::Test
|
|
230
230
|
describe '#archive_file' do
|
231
231
|
it 'archive file' do
|
232
232
|
assert_equal @archive_real_name, @entry.send(:archive_file, @job, Pathname.new(@file_name))
|
233
|
-
assert File.
|
233
|
+
assert File.exist?(@archive_real_name)
|
234
234
|
end
|
235
235
|
end
|
236
236
|
|
@@ -17,6 +17,7 @@ module Plugins
|
|
17
17
|
|
18
18
|
describe RocketJob::Plugins::Job::Persistence do
|
19
19
|
before do
|
20
|
+
RocketJob::Job.destroy_all
|
20
21
|
@description = 'Hello World'
|
21
22
|
@arguments = [{key: 'value'}]
|
22
23
|
@job = PersistJob.new(
|
@@ -28,6 +29,8 @@ module Plugins
|
|
28
29
|
|
29
30
|
after do
|
30
31
|
@job.destroy if @job && !@job.new_record?
|
32
|
+
@job2.destroy if @job2 && !@job2.new_record?
|
33
|
+
@job3.destroy if @job3 && !@job3.new_record?
|
31
34
|
end
|
32
35
|
|
33
36
|
describe '.config' do
|
@@ -75,6 +78,20 @@ module Plugins
|
|
75
78
|
end
|
76
79
|
end
|
77
80
|
|
81
|
+
describe '.counts_by_state' do
|
82
|
+
it 'returns states as symbols' do
|
83
|
+
@job.start!
|
84
|
+
@job2 = PersistJob.create!(arguments: [{key: 'value'}])
|
85
|
+
@job3 = PersistJob.create!(arguments: [{key: 'value'}], run_at: 1.day.from_now)
|
86
|
+
counts = RocketJob::Job.counts_by_state
|
87
|
+
assert_equal 4, counts.size, counts.ai
|
88
|
+
assert_equal 1, counts[:running]
|
89
|
+
assert_equal 2, counts[:queued]
|
90
|
+
assert_equal 1, counts[:queued_now]
|
91
|
+
assert_equal 1, counts[:scheduled]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
78
95
|
end
|
79
96
|
end
|
80
97
|
end
|
@@ -78,7 +78,7 @@ module Plugins
|
|
78
78
|
@job = ProcessingWindowJob.new
|
79
79
|
refute @job.valid?
|
80
80
|
assert_equal "can't be blank", @job.errors.messages[:processing_schedule].first
|
81
|
-
assert_equal
|
81
|
+
assert_equal 'not a string: nil', @job.errors.messages[:processing_schedule].second
|
82
82
|
assert_equal "can't be blank", @job.errors.messages[:processing_duration].first
|
83
83
|
end
|
84
84
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rocketjob
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-07-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -42,16 +42,16 @@ dependencies:
|
|
42
42
|
name: mongo_mapper
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 0.14.0.rc1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 0.14.0.rc1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: semantic_logger
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,38 +66,23 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '3.1'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: symmetric-encryption
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - ">="
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '3.0'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - ">="
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '3.0'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: aasm
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
|
-
- - "
|
73
|
+
- - "~>"
|
88
74
|
- !ruby/object:Gem::Version
|
89
75
|
version: '4.3'
|
90
76
|
type: :runtime
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
|
-
- - "
|
80
|
+
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
82
|
version: '4.3'
|
97
|
-
description:
|
98
|
-
and visibility of every job in the system.
|
83
|
+
description:
|
99
84
|
email:
|
100
|
-
-
|
85
|
+
- support@rocketjob.io
|
101
86
|
executables:
|
102
87
|
- rocketjob
|
103
88
|
- rocketjob_perf
|
@@ -114,15 +99,17 @@ files:
|
|
114
99
|
- lib/rocket_job/dirmon_entry.rb
|
115
100
|
- lib/rocket_job/extensions/aasm.rb
|
116
101
|
- lib/rocket_job/extensions/mongo.rb
|
102
|
+
- lib/rocket_job/extensions/mongo_mapper.rb
|
103
|
+
- lib/rocket_job/extensions/rocket_job_adapter.rb
|
117
104
|
- lib/rocket_job/heartbeat.rb
|
118
105
|
- lib/rocket_job/job.rb
|
119
106
|
- lib/rocket_job/job_exception.rb
|
120
107
|
- lib/rocket_job/jobs/dirmon_job.rb
|
108
|
+
- lib/rocket_job/jobs/housekeeping_job.rb
|
121
109
|
- lib/rocket_job/jobs/simple_job.rb
|
122
110
|
- lib/rocket_job/performance.rb
|
123
111
|
- lib/rocket_job/plugins/cron.rb
|
124
112
|
- lib/rocket_job/plugins/document.rb
|
125
|
-
- lib/rocket_job/plugins/document/static.rb
|
126
113
|
- lib/rocket_job/plugins/job/callbacks.rb
|
127
114
|
- lib/rocket_job/plugins/job/defaults.rb
|
128
115
|
- lib/rocket_job/plugins/job/logger.rb
|
@@ -132,6 +119,7 @@ files:
|
|
132
119
|
- lib/rocket_job/plugins/job/worker.rb
|
133
120
|
- lib/rocket_job/plugins/processing_window.rb
|
134
121
|
- lib/rocket_job/plugins/restart.rb
|
122
|
+
- lib/rocket_job/plugins/retry.rb
|
135
123
|
- lib/rocket_job/plugins/rufus/cron_line.rb
|
136
124
|
- lib/rocket_job/plugins/rufus/zo_time.rb
|
137
125
|
- lib/rocket_job/plugins/singleton.rb
|
@@ -179,10 +167,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
179
167
|
version: '0'
|
180
168
|
requirements: []
|
181
169
|
rubyforge_project:
|
182
|
-
rubygems_version: 2.
|
170
|
+
rubygems_version: 2.5.1
|
183
171
|
signing_key:
|
184
172
|
specification_version: 4
|
185
|
-
summary:
|
173
|
+
summary: Ruby's missing batch system.
|
186
174
|
test_files:
|
187
175
|
- test/config/mongo.yml
|
188
176
|
- test/config_test.rb
|
@@ -1,53 +0,0 @@
|
|
1
|
-
module RocketJob
|
2
|
-
module Plugins
|
3
|
-
# Extension for Document to implement static keys
|
4
|
-
# Remove when new MongoMapper gem is released
|
5
|
-
module Document
|
6
|
-
module Static
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
attr_writer :static_keys
|
11
|
-
|
12
|
-
def static_keys
|
13
|
-
@static_keys || false
|
14
|
-
end
|
15
|
-
|
16
|
-
def embedded_keys
|
17
|
-
@embedded_keys ||= embedded_associations.collect(&:as)
|
18
|
-
end
|
19
|
-
|
20
|
-
def embedded_key?(key)
|
21
|
-
embedded_keys.include?(key.to_sym)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def read_key(name)
|
26
|
-
if !self.class.static_keys || self.class.key?(name)
|
27
|
-
super
|
28
|
-
else
|
29
|
-
raise MissingKeyError, "Tried to read the key #{name.inspect}, but no key was defined. Either define key :#{name} or set self.static_keys = false"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def write_key(name, value)
|
36
|
-
if !self.class.static_keys || self.class.key?(name)
|
37
|
-
super
|
38
|
-
else
|
39
|
-
raise MissingKeyError, "Tried to write the key #{name.inspect}, but no key was defined. Either define key :#{name} or set self.static_keys = false"
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def load_from_database(attrs, with_cast = false)
|
44
|
-
return super if !self.class.static_keys || !attrs.respond_to?(:each)
|
45
|
-
|
46
|
-
attrs = attrs.select { |key, _| self.class.key?(key) || self.class.embedded_key?(key) }
|
47
|
-
|
48
|
-
super(attrs, with_cast)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|