delayed_job_memento 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +1 -0
- data/delayed_job_memento.gemspec +25 -0
- data/lib/delayed_job_memento.rb +5 -0
- data/lib/delayed_job_memento/delayed/memento.rb +26 -0
- data/lib/delayed_job_memento/delayed/quota.rb +59 -0
- data/lib/delayed_job_memento/delayed/worker.rb +65 -0
- data/lib/delayed_job_memento/version.rb +3 -0
- data/lib/generators/delayed_job_memento/install_generator.rb +23 -0
- data/lib/generators/delayed_job_memento/next_migration_version.rb +12 -0
- data/lib/generators/delayed_job_memento/templates/migration.rb +23 -0
- data/spec/memento_spec.rb +9 -0
- data/spec/quota_spec.rb +3 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 288931fe637b0eba836c6403575b0440c8a2a16b
|
4
|
+
data.tar.gz: ec5b88eb644bf4e01bf22ba92c6ee82d8ee22c6e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3607f7798b3a30b9a128ed0f51f3b7390cf5c061df4303d3b7a3f52b9502ed15dc5c3c1ca6edc5758f145272d280b713dbd74ebb0187337452960e6aa24c2745
|
7
|
+
data.tar.gz: fcbf4e84387eb8e440f7befbe81161ad03469b23a134b5ca075cbf58d2c0291fd595f82a7a3929f4579eb2432a5cdc1f71cf1d356eb9a02ca7edb82737147a1c
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Joseph Quadrino
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Delayed Job Memento [](http://badge.fury.io/rb/delayed_job_memento)
|
2
|
+
|
3
|
+
Delayed Jobs can be sentimental, save them as mementos so you don't have to forget.
|
4
|
+
|
5
|
+
Also, set quotas for delayed jobs.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your Gemfile:
|
10
|
+
|
11
|
+
gem 'delayed_job_memento'
|
12
|
+
|
13
|
+
and run `bundle`
|
14
|
+
|
15
|
+
or without bundler:
|
16
|
+
|
17
|
+
$ gem install delayed_job_memento
|
18
|
+
|
19
|
+
####Then
|
20
|
+
|
21
|
+
Create migration for mementos:
|
22
|
+
|
23
|
+
$ rails g delayed_job_memento:install
|
24
|
+
$ rake db:migrate
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
####You probably shouldn't hold onto mementos forever:
|
29
|
+
|
30
|
+
# config/initializers
|
31
|
+
|
32
|
+
Delayed::Memento.expiration = 1.week
|
33
|
+
|
34
|
+
## keep quotas in mind, see below
|
35
|
+
|
36
|
+
####To set Quotas:
|
37
|
+
|
38
|
+
# config/initializers
|
39
|
+
|
40
|
+
Delayed::Quota.new(queue: 'cat emails', interval: 1.day, size: 100) # Don't send more than 100 cat emails per day
|
41
|
+
...
|
42
|
+
|
43
|
+
## Don't let any of your quota intervals exceed memento expiration time or that quota wont work
|
44
|
+
When a Quota is reached its queue is rebalanced so that remaining queue jobs run at the end of the quotas current lifetime.
|
45
|
+
|
46
|
+
|
47
|
+
####Get mementos
|
48
|
+
|
49
|
+
Delayed::Memento.all
|
50
|
+
|
51
|
+
### Note
|
52
|
+
|
53
|
+
Only works with ActiveRecord as your delayed job backend.
|
54
|
+
|
55
|
+
### TODO
|
56
|
+
|
57
|
+
1. Tests
|
58
|
+
2. More Backends
|
59
|
+
3. More things to do with Mementos
|
60
|
+
|
61
|
+
## Contributing
|
62
|
+
|
63
|
+
1. Fork it ( http://github.com/<my-github-username>/delayed_job_memento/fork )
|
64
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
65
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
66
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
67
|
+
5. Create new Pull Request
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
<a href='https://github.com/jquadrin/delayed_job_memento/blob/master/LICENSE.txt'>LICENSE</a>
|
72
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'delayed_job_memento/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.add_dependency 'activerecord', ['>= 3.0', '< 4.1']
|
8
|
+
spec.add_dependency 'delayed_job', ['>= 3.0', '< 4.1']
|
9
|
+
spec.name = "delayed_job_memento"
|
10
|
+
spec.version = DelayedJobMemento::VERSION
|
11
|
+
spec.authors = ["Joseph Quadrino"]
|
12
|
+
spec.email = ["jquadrin@gmail.com"]
|
13
|
+
spec.summary = %q{Delayed jobs can be sentimental, you don't have to let them go.}
|
14
|
+
spec.description = %q{Save mementos of your delayed jobs and also set queue quotas.}
|
15
|
+
spec.homepage = "https://github.com/jquadrin/delayed_job_memento"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_record/version'
|
2
|
+
|
3
|
+
module Delayed
|
4
|
+
class Memento < ActiveRecord::Base
|
5
|
+
|
6
|
+
self.table_name = :delayed_jobs_history
|
7
|
+
|
8
|
+
if ActiveRecord::VERSION::MAJOR < 4 || defined?(ActiveRecord::MassAssignmentSecurity)
|
9
|
+
attr_accessible :run_at, :priority, :handler, :queue, :locked_at,
|
10
|
+
:locked_by, :attempts, :failed_at, :last_error
|
11
|
+
end
|
12
|
+
|
13
|
+
cattr_accessor :expiration, :last_clean_up
|
14
|
+
|
15
|
+
after_save :run_clean_up
|
16
|
+
|
17
|
+
def run_clean_up
|
18
|
+
current_time = Time.zone.now
|
19
|
+
if @@expiration != nil && (@@last_clean_up.nil? || current_time - @@last_clean_up > @@expiration)
|
20
|
+
Delayed::Memento.delete_all(['created_at < ?', current_time - @@expiration])
|
21
|
+
@@last_clean_up = current_time
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Delayed
|
2
|
+
class Quota
|
3
|
+
attr_accessor :queue, :interval, :size, :started, :reached
|
4
|
+
cattr_accessor :instances
|
5
|
+
|
6
|
+
self.instances = {}
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
self.queue = options[:queue]
|
10
|
+
self.interval = options[:interval]
|
11
|
+
self.size = options[:size]
|
12
|
+
self.started, self.reached = nil, nil
|
13
|
+
self.instances[self.queue] = self
|
14
|
+
end
|
15
|
+
|
16
|
+
# fix class variables to instances
|
17
|
+
def quota_reached?
|
18
|
+
current_time = Time.zone.now
|
19
|
+
if self.reached != nil
|
20
|
+
time_left = self.interval - (self.reached - self.started)
|
21
|
+
if self.reached + time_left < current_time
|
22
|
+
self.reached = nil
|
23
|
+
self.started = current_time
|
24
|
+
false
|
25
|
+
else
|
26
|
+
true
|
27
|
+
end
|
28
|
+
else
|
29
|
+
if self.started != nil
|
30
|
+
conditions = {
|
31
|
+
queue: self.queue,
|
32
|
+
created_at: self.started..current_time,
|
33
|
+
locked_at: Time.new(0)..current_time
|
34
|
+
}
|
35
|
+
finished_jobs = Delayed::Memento.where(queue: self.queue,created_at: self.started..current_time,locked_at: Time.new(0)..current_time).count
|
36
|
+
if finished_jobs >= self.size
|
37
|
+
self.reached = current_time
|
38
|
+
true
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
else
|
43
|
+
self.started = current_time
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def rebalance_queue
|
50
|
+
run_at = self.interval - (self.reached - self.started)
|
51
|
+
conditions = {queue: self.queue, locked_at: nil}
|
52
|
+
Delayed::Job.where(conditions).find_in_batches(batch_size: self.size) do |batch|
|
53
|
+
batch.each { |job| job.run_at = run_at }
|
54
|
+
Delayed::Job.import batch, validate: false, on_duplicate_key_update: [:run_at]
|
55
|
+
run_at= run_at + self.interval
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'delayed_job'
|
2
|
+
module Delayed
|
3
|
+
class Worker
|
4
|
+
extend Delayed
|
5
|
+
|
6
|
+
DEFAULT_LOG_LEVEL = Logger::INFO
|
7
|
+
|
8
|
+
def self.backend=(backend)
|
9
|
+
if backend.is_a? Symbol
|
10
|
+
require "delayed/serialization/#{backend}"
|
11
|
+
require "delayed/backend/#{backend}"
|
12
|
+
backend = "Delayed::Backend::#{backend.to_s.classify}::Job".constantize
|
13
|
+
end
|
14
|
+
@@backend = backend
|
15
|
+
silence_warnings { ::Delayed.const_set(:Job, backend) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def save_as_memento(job)
|
19
|
+
params = job.attributes
|
20
|
+
attrs = ['run_at', 'priority', 'handler', 'queue', 'locked_at', 'locked_by', 'attempts', 'failed_at', 'last_error']
|
21
|
+
params.delete_if { |k| !attrs.include? k }
|
22
|
+
Delayed::Memento.create(params)
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(job)
|
26
|
+
instances = Delayed::Quota.instances
|
27
|
+
if job.queue != nil && instances.has_key?(job.queue) && instances[job.queue].quota_reached?
|
28
|
+
instances[job.queue].rebalance_queue
|
29
|
+
return false
|
30
|
+
else
|
31
|
+
job_say job, 'RUNNING'
|
32
|
+
runtime = Benchmark.realtime do
|
33
|
+
Timeout.timeout(self.class.max_run_time.to_i, WorkerTimeout) { job.invoke_job }
|
34
|
+
save_as_memento(job)
|
35
|
+
job.destroy
|
36
|
+
end
|
37
|
+
job_say job, 'COMPLETED after %.4f' % runtime
|
38
|
+
return true # did work
|
39
|
+
end
|
40
|
+
rescue DeserializationError => error
|
41
|
+
job.last_error = "#{error.message}\n#{error.backtrace.join("\n")}"
|
42
|
+
failed(job)
|
43
|
+
rescue Exception => error
|
44
|
+
self.class.lifecycle.run_callbacks(:error, self, job) { handle_failed_job(job, error) }
|
45
|
+
return false # work failed
|
46
|
+
end
|
47
|
+
|
48
|
+
def failed(job)
|
49
|
+
self.class.lifecycle.run_callbacks(:failure, self, job) do
|
50
|
+
job.hook(:failure)
|
51
|
+
if self.class.destroy_failed_jobs
|
52
|
+
save_as_memento(job)
|
53
|
+
job.destroy
|
54
|
+
else
|
55
|
+
job.fail!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def job_say(job, text, level = DEFAULT_LOG_LEVEL)
|
61
|
+
text = "Job #{job.name} (id=#{job.id}) #{text}"
|
62
|
+
say text, level
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'generators/delayed_job_memento/next_migration_version'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
require 'rails/generators/active_record'
|
4
|
+
|
5
|
+
module DelayedJobMemento
|
6
|
+
module Generators
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
include Rails::Generators::Migration
|
9
|
+
extend DelayedJobMemento::NextMigrationVersion
|
10
|
+
|
11
|
+
desc "Adds delayed job history migration"
|
12
|
+
self.source_paths << File.join(File.dirname(__FILE__), 'templates')
|
13
|
+
|
14
|
+
def create_migration_file
|
15
|
+
migration_template 'migration.rb', 'db/migrate/create_delayed_jobs_history.rb'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.next_migration_number dirname
|
19
|
+
ActiveRecord::Generators::Base.next_migration_number dirname
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module DelayedJobMemento
|
2
|
+
module NextMigrationVersion
|
3
|
+
def next_migration_number(dirname)
|
4
|
+
next_migration_number = current_migration_number(dirname) + 1
|
5
|
+
if ActiveRecord::Base.timestamped_migrations
|
6
|
+
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % next_migration_number].max
|
7
|
+
else
|
8
|
+
"%.3d" % next_migration_number
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class CreateDelayedJobsHistory < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :delayed_jobs_history, force: true do |t|
|
4
|
+
t.datetime :created_at # when job was completed
|
5
|
+
t.text :handler, null: false
|
6
|
+
t.string :queue
|
7
|
+
t.datetime :locked_at
|
8
|
+
t.string :locked_by
|
9
|
+
t.datetime :failed_at
|
10
|
+
t.integer :priority, default: 0, null: false
|
11
|
+
t.datetime :run_at
|
12
|
+
t.integer :attempts, default: 0, null: false
|
13
|
+
t.text :last_error
|
14
|
+
t.datetime :updated_at
|
15
|
+
end
|
16
|
+
|
17
|
+
add_index :delayed_jobs_history, :created_at, name: 'delayed_jobs_completed'
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.down
|
21
|
+
drop_table :delayed_jobs_history
|
22
|
+
end
|
23
|
+
end
|
data/spec/quota_spec.rb
ADDED
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: delayed_job_memento
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joseph Quadrino
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '4.1'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '4.1'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: delayed_job
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.0'
|
40
|
+
- - <
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '4.1'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.0'
|
50
|
+
- - <
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '4.1'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: bundler
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '1.5'
|
60
|
+
type: :development
|
61
|
+
prerelease: false
|
62
|
+
version_requirements: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ~>
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '1.5'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: rake
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
description: Save mementos of your delayed jobs and also set queue quotas.
|
82
|
+
email:
|
83
|
+
- jquadrin@gmail.com
|
84
|
+
executables: []
|
85
|
+
extensions: []
|
86
|
+
extra_rdoc_files: []
|
87
|
+
files:
|
88
|
+
- .gitignore
|
89
|
+
- Gemfile
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- delayed_job_memento.gemspec
|
94
|
+
- lib/delayed_job_memento.rb
|
95
|
+
- lib/delayed_job_memento/delayed/memento.rb
|
96
|
+
- lib/delayed_job_memento/delayed/quota.rb
|
97
|
+
- lib/delayed_job_memento/delayed/worker.rb
|
98
|
+
- lib/delayed_job_memento/version.rb
|
99
|
+
- lib/generators/delayed_job_memento/install_generator.rb
|
100
|
+
- lib/generators/delayed_job_memento/next_migration_version.rb
|
101
|
+
- lib/generators/delayed_job_memento/templates/migration.rb
|
102
|
+
- spec/memento_spec.rb
|
103
|
+
- spec/quota_spec.rb
|
104
|
+
homepage: https://github.com/jquadrin/delayed_job_memento
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
metadata: {}
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options: []
|
110
|
+
require_paths:
|
111
|
+
- lib
|
112
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 2.2.1
|
125
|
+
signing_key:
|
126
|
+
specification_version: 4
|
127
|
+
summary: Delayed jobs can be sentimental, you don't have to let them go.
|
128
|
+
test_files:
|
129
|
+
- spec/memento_spec.rb
|
130
|
+
- spec/quota_spec.rb
|
131
|
+
has_rdoc:
|