rocketjob 2.0.0 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Gem Version](https://img.shields.io/gem/v/rocketjob.svg)](https://rubygems.org/gems/rocketjob) [![Build Status](https://travis-ci.org/rocketjob/rocketjob.svg?branch=master)](https://travis-ci.org/rocketjob/rocketjob) [![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](http://opensource.org/licenses/Apache-2.0) ![](https://img.shields.io/badge/status-Production%20Ready-blue.svg) [![
|
2
|
+
[![Gem Version](https://img.shields.io/gem/v/rocketjob.svg)](https://rubygems.org/gems/rocketjob) [![Build Status](https://travis-ci.org/rocketjob/rocketjob.svg?branch=master)](https://travis-ci.org/rocketjob/rocketjob) [![License](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](http://opensource.org/licenses/Apache-2.0) ![](https://img.shields.io/badge/status-Production%20Ready-blue.svg) [![Support](https://img.shields.io/badge/IRC%20(gitter)-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
|