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.
@@ -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
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in delayed_job_memento.gemspec
4
+ gemspec
@@ -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.
@@ -0,0 +1,72 @@
1
+ # Delayed Job Memento [![Gem Version](https://badge.fury.io/rb/delayed_job_memento.png)](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
+
@@ -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,5 @@
1
+ require 'active_record'
2
+ require 'delayed_job_memento/version'
3
+ require 'delayed_job_memento/delayed/worker'
4
+ require 'delayed_job_memento/delayed/memento'
5
+ require 'delayed_job_memento/delayed/quota'
@@ -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,3 @@
1
+ module DelayedJobMemento
2
+ VERSION = '0.1.0'
3
+ 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
@@ -0,0 +1,9 @@
1
+ describe Delayed::Memento do
2
+ # TODO a ton of tests
3
+ before :all do
4
+ Delayed::Memento.expiration = 2.seconds
5
+ end
6
+
7
+ Delayed::Memento.create(queue: 'whatever')
8
+
9
+ end
@@ -0,0 +1,3 @@
1
+ describe Delayed::Quota do
2
+ # TODO a ton of tests
3
+ end
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: