workless_revived 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 90ea3d00f5b27151452d15ba4e6f2a07785c92dc
4
+ data.tar.gz: de0e6eecd3fcfffbf9615035d892d0345b1a3107
5
+ SHA512:
6
+ metadata.gz: cd5bbde2310761f1064b5f2e9ef3544c710e198ef496f7322468b643e59ccc297bfa9188da596d392c32fefc3ed916724db3a523525c4d332fd5a7e6fff744a2
7
+ data.tar.gz: 078ef22d2b35c911ae87477748f8794f4de6eef47e0ff8cf8d7d911939a206e808bebfada3e21b9d8b70ec2fc511ee3edd012e61f6dc33c29b9158c3e8dd9869
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 lostboy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ [![Build Status](https://secure.travis-ci.org/davidakachaos/workless_revived.png?branch=master)](http://travis-ci.org/davidakachaos/workless_revived)
2
+ [![Gem Version](https://badge.fury.io/rb/workless_revived.png)](http://badge.fury.io/rb/workless_revived)
3
+ [![Test Coverage](https://coveralls.io/repos/davidakachaos/workless_revived/badge.png?branch=master)](https://coveralls.io/r/davidakachaos/workless_revived)
4
+
5
+ # Workless Revived
6
+
7
+ This gem was originally created by [lostboy](https://github.com/lostboy). Since he became inactive, I've decided to revive this project under a new(ish) name.
8
+
9
+ This is an addon for delayed_job (> 2.0.0) http://github.com/collectiveidea/delayed_job
10
+ It is designed to be used when you're using Heroku as a host and have the need to do background work with delayed job but you don't want to leave the workers running all the time as it costs money.
11
+
12
+ By adding the gem to your project and configuring our Heroku app with some config variables workless should do the rest.
13
+
14
+ ## Updates
15
+
16
+ * Version 1.2.4 drops support for older versions!
17
+ * Version 1.2.3 replaces multiple commit callback with two callbacks for compatibility by @lostboy
18
+ * Version 1.2.2 includes after_commit fix by @collectiveip
19
+ * Version 1.2.1 includes support for Rails 4 & DJ 4 by @florentmorin
20
+ * Version 1.2.0 includes new support for Sequel by @davidakachaos
21
+ * Version 1.1.3 includes changes by @radanskoric to reduce number of heroku api calls
22
+ * Version 1.1.2 includes a change by @davidakachaos to scale workers using after_commit
23
+ * Version 1.1.1 includes a fix from @filiptepper and @fixr to correctly scale workers
24
+ * Version 1.1.0 has been released, this adds support for scaling using multiple workers thanks to @jaimeiniesta and @davidakachaos.
25
+ * Version 1.0.0 has been released, this brings compatibility with delayed_job 3 and compatibility with Rails 2.3.x and up.
26
+
27
+ ## Compatibility
28
+
29
+ Workless should work correctly with Rubies 2.0.0 and up. It is compatible with Delayed Job since version 2.0.7 up to the latest version 4.1.2, the table below shows tested compatibility with ruby, rails and delayed_job
30
+
31
+ Ruby | Rails | Delayed Job
32
+ ---------- | ------ | -----
33
+ 2.0.0 | 3.2.22 | 2.0.7
34
+ 2.2.4 | 4.2 | 2.1.4
35
+ 2.3.0 | 5.0 | 4.1.2
36
+
37
+ ## Installation
38
+
39
+ Add the workless gem and the delayed_job gem to your project Gemfile and update your bundle. Its is recommended to specify the gem version for delayed_job
40
+
41
+ ### For rails 3.x with latest delayed_job 3.x using active record
42
+
43
+ <pre>
44
+ gem "delayed_job_active_record"
45
+ gem "workless", "~> 1.1.3"
46
+ </pre>
47
+
48
+ ### For rails 4.x with latest delayed_job 3.x using active record
49
+
50
+ <pre>
51
+ gem "delayed_job_active_record"
52
+ gem "workless_revived", "~> 1.2.4"
53
+ </pre>
54
+
55
+ ### For rails 5.x with latest delayed_job 3.x using active record
56
+
57
+ <pre>
58
+ gem "delayed_job_active_record"
59
+ gem "workless_revived", "~> 1.2.4"
60
+ </pre>
61
+
62
+
63
+ If you don't specify delayed_job in your Gemfile workless will bring it in, most likely the latest version (4.1.2)
64
+
65
+ Add your Heroku app name / [API key](https://devcenter.heroku.com/articles/authentication) as config vars to your Heroku instance.
66
+
67
+ <pre>
68
+ heroku config:add HEROKU_API_KEY=yourapikey APP_NAME=yourherokuappname
69
+ </pre>
70
+
71
+ ## Failing Jobs
72
+
73
+ In the case of failed jobs Workless will only shut down the dj worker if all attempts have been tried. By default Delayed Job will try 25 times to process a job with ever increasing time delays between each unsucessful attempt. Because of this Workless configures Delayed Job to try failed jobs only 3 times to reduce the amount of time a worker can be running while trying to process them.
74
+
75
+ ## Configuration
76
+
77
+ Workless can be disabled by using the null scaler that will ignore the workers requests to scale up and down. In an environment file add this in the config block:
78
+
79
+ <pre>
80
+ config.after_initialize do
81
+ Delayed::Job.scaler = :null
82
+ end
83
+ </pre>
84
+
85
+ There are three other scalers included. Note that if you are running on the Aspen or Bamboo stacks on Heroku and you don't explicitly specify the scaler, the heroku scaler will be used automatically.
86
+
87
+ <pre>
88
+ Delayed::Job.scaler = :heroku
89
+ Delayed::Job.scaler = :heroku_cedar
90
+ Delayed::Job.scaler = :local
91
+ </pre>
92
+
93
+ The local scaler uses @adamwiggins rush library http://github.com/adamwiggins/rush to start and stop workers on a local machine. The local scaler also relies on script/delayed_job (which in turn requires the daemon gem). If you have been using foreman to run your workers, go back and see the delayed_job [setup instructions](https://github.com/collectiveidea/delayed_job/blob/master/README.md).
94
+
95
+ The heroku scaler works on the Aspen and Bamboo stacks while the heroku_cedar scaler only works on the new Cedar stack.
96
+
97
+ ## Scaling to multiple workers
98
+
99
+ As an experimental feature for the Cedar stack, Workless can scale to more than 1 worker based on the current work load. You just need to define these config variables on your app, setting the values you want:
100
+
101
+ <pre>
102
+ heroku config:add WORKLESS_MAX_WORKERS=10
103
+ heroku config:add WORKLESS_MIN_WORKERS=0
104
+ heroku config:add WORKLESS_WORKERS_RATIO=50
105
+ </pre>
106
+
107
+ In this example, it will scale up to a maximum of 10 workers, firing up 1 worker for every 50 jobs on the queue. The minimum will be 0 workers, but you could set it to a higher value if you want.
108
+
109
+ ## How does Workless work?
110
+
111
+ - `Delayed::Workless::Scaler` is mixed into the `Delayed::Job` class, which adds a bunch of callbacks to it.
112
+ - When a job is created on the database, a `create` callback starts a worker.
113
+ - The worker runs the job, which removes it from the database.
114
+ - A `destroy` callback stops the worker.
115
+
116
+ ## Note on Patches/Pull Requests
117
+
118
+ * Please fork the project.
119
+ * Make your feature addition or bug fix.
120
+ * Commit, do not mess with rakefile, version, or history.
121
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
122
+ * Send me a pull request.
123
+
124
+ ## Copyright
125
+
126
+ Copyright (c) 2010 lostboy.
127
+ Copyright (c) 2016 davidakachaos.
128
+
129
+ See LICENSE for details.
data/lib/workless.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "heroku-api"
2
+ require File.dirname(__FILE__) + "/workless/scalers/base"
3
+ require File.dirname(__FILE__) + "/workless/scaler"
4
+ require File.dirname(__FILE__) + "/workless/middleware/workless_checker" if defined?(Rails::Railtie)
5
+ require File.dirname(__FILE__) + "/workless/railtie" if defined?(Rails::Railtie)
@@ -0,0 +1,5 @@
1
+ Delayed::Worker.max_attempts = 3
2
+ Delayed::Backend::ActiveRecord::Job.send(:include, Delayed::Workless::Scaler) if defined?(Delayed::Backend::ActiveRecord::Job)
3
+ Delayed::Backend::Mongoid::Job.send(:include, Delayed::Workless::Scaler) if defined?(Delayed::Backend::Mongoid::Job)
4
+ Delayed::Backend::MongoMapper::Job.send(:include, Delayed::Workless::Scaler) if defined?(Delayed::Backend::MongoMapper::Job)
5
+ Delayed::Backend::Sequel::Job.send(:include, Delayed::Workless::Scaler) if defined?(Delayed::Backend::Sequel::Job)
@@ -0,0 +1,37 @@
1
+ class WorklessChecker
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ def call(env)
7
+ status, headers, response = @app.call(env)
8
+ return [status, headers, response] if file?(headers) || empty?(response)
9
+
10
+ Delayed::Job.scaler.up if Delayed::Job.scaler.jobs.size > 0
11
+ response_body = nil
12
+ if status == 200 && !response.body.frozen? && html_request?(headers, response)
13
+ response_body = response.body << "\n<!-- workless jobs: #{Delayed::Job.scaler.jobs.size} -->"
14
+ headers['Content-Length'] = response_body.bytesize.to_s
15
+ end
16
+
17
+ return [status, headers, response_body ? [response_body] : response]
18
+ end
19
+
20
+ # fix issue if response's body is a Proc
21
+ def empty?(response)
22
+ # response may be ["Not Found"], ["Move Permanently"], etc.
23
+ (response.is_a?(Array) && response.size <= 1) ||
24
+ !response.respond_to?(:body) ||
25
+ !response.body.respond_to?(:empty?) ||
26
+ response.body.empty?
27
+ end
28
+
29
+ # if send file?
30
+ def file?(headers)
31
+ headers["Content-Transfer-Encoding"] == "binary"
32
+ end
33
+
34
+ def html_request?(headers, response)
35
+ headers['Content-Type'] && headers['Content-Type'].include?('text/html') && response.body.include?("<html")
36
+ end
37
+ end
@@ -0,0 +1,11 @@
1
+ require 'rails'
2
+ require 'delayed_job'
3
+
4
+ module Delayed
5
+ class Railtie < Rails::Railtie
6
+ initializer :after_initialize do |config|
7
+ require 'workless/initialize'
8
+ config.middleware.use "WorklessChecker"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ module Delayed
2
+ module Workless
3
+ module Scaler
4
+
5
+ autoload :Heroku, "workless/scalers/heroku"
6
+ autoload :HerokuCedar, "workless/scalers/heroku_cedar"
7
+ autoload :Local, "workless/scalers/local"
8
+ autoload :Null, "workless/scalers/null"
9
+
10
+ def self.included(base)
11
+ base.send :extend, ClassMethods
12
+ if base.to_s =~ /ActiveRecord/
13
+ base.class_eval do
14
+ after_commit "self.class.scaler.down", :on => :update, :if => Proc.new {|r| !r.failed_at.nil? }
15
+ after_commit "self.class.scaler.down", :on => :destroy, :if => Proc.new {|r| r.destroyed? or !r.failed_at.nil? }
16
+ after_commit "self.class.scaler.up", :on => :create
17
+ end
18
+ elsif base.to_s =~ /Sequel/
19
+ base.send(:define_method, 'after_destroy') do
20
+ super
21
+ self.class.scaler.down
22
+ end
23
+ base.send(:define_method, 'after_create') do
24
+ super
25
+ self.class.scaler.up
26
+ end
27
+ base.send(:define_method, 'after_update') do
28
+ super
29
+ self.class.scaler.down
30
+ end
31
+ else
32
+ base.class_eval do
33
+ after_destroy "self.class.scaler.down"
34
+ after_create "self.class.scaler.up"
35
+ after_update "self.class.scaler.down", :unless => Proc.new {|r| r.failed_at.nil? }
36
+ end
37
+ end
38
+ end
39
+
40
+ module ClassMethods
41
+ def scaler
42
+ @scaler ||= if ENV.include?("HEROKU_API_KEY")
43
+ Scaler::HerokuCedar
44
+ else
45
+ Scaler::Local
46
+ end
47
+ end
48
+
49
+ def scaler=(scaler)
50
+ @scaler = "Delayed::Workless::Scaler::#{scaler.to_s.camelize}".constantize
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,27 @@
1
+ require 'delayed_job'
2
+
3
+ module Delayed
4
+ module Workless
5
+ module Scaler
6
+
7
+ class Base
8
+ def self.jobs
9
+ if Rails.version >= "3.0.0"
10
+ Delayed::Job.where(:failed_at => nil)
11
+ else
12
+ Delayed::Job.all(:conditions => { :failed_at => nil })
13
+ end
14
+ end
15
+ end
16
+
17
+ module HerokuClient
18
+
19
+ def client
20
+ @client ||= ::Heroku::API.new(:api_key => ENV['HEROKU_API_KEY'])
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,27 @@
1
+ require 'heroku-api'
2
+
3
+ module Delayed
4
+ module Workless
5
+ module Scaler
6
+
7
+ class Heroku < Base
8
+
9
+ extend Delayed::Workless::Scaler::HerokuClient
10
+
11
+ def self.up
12
+ client.put_workers(ENV['APP_NAME'], 1) if self.workers == 0
13
+ end
14
+
15
+ def self.down
16
+ client.put_workers(ENV['APP_NAME'], 0) unless self.jobs.count > 0 or self.workers == 0
17
+ end
18
+
19
+ def self.workers
20
+ client.get_ps(ENV['APP_NAME']).body.count { |p| p["process"] =~ /worker\.\d?/ }
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ require 'heroku-api'
2
+
3
+ module Delayed
4
+ module Workless
5
+ module Scaler
6
+ class HerokuCedar < Base
7
+ extend Delayed::Workless::Scaler::HerokuClient
8
+
9
+ def self.up
10
+ client.post_ps_scale(ENV['APP_NAME'], 'worker', self.workers_needed) if self.workers_needed > self.min_workers and self.workers < self.workers_needed
11
+ end
12
+
13
+ def self.down
14
+ client.post_ps_scale(ENV['APP_NAME'], 'worker', self.min_workers) unless self.jobs.count > 0 or self.workers == self.min_workers
15
+ end
16
+
17
+ def self.workers
18
+ client.get_ps(ENV['APP_NAME']).body.count { |p| p["process"] =~ /worker\.\d?/ }
19
+ end
20
+
21
+ # Returns the number of workers needed based on the current number of pending jobs and the settings defined by:
22
+ #
23
+ # ENV['WORKLESS_WORKERS_RATIO']
24
+ # ENV['WORKLESS_MAX_WORKERS']
25
+ # ENV['WORKLESS_MIN_WORKERS']
26
+ #
27
+ def self.workers_needed
28
+ [[(self.jobs.count.to_f / self.workers_ratio).ceil, self.max_workers].min, self.min_workers].max
29
+ end
30
+
31
+ def self.workers_ratio
32
+ if ENV['WORKLESS_WORKERS_RATIO'].present? && (ENV['WORKLESS_WORKERS_RATIO'].to_i != 0)
33
+ ENV['WORKLESS_WORKERS_RATIO'].to_i
34
+ else
35
+ 100
36
+ end
37
+ end
38
+
39
+ def self.max_workers
40
+ ENV['WORKLESS_MAX_WORKERS'].present? ? ENV['WORKLESS_MAX_WORKERS'].to_i : 1
41
+ end
42
+
43
+ def self.min_workers
44
+ ENV['WORKLESS_MIN_WORKERS'].present? ? ENV['WORKLESS_MIN_WORKERS'].to_i : 0
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ require 'rush'
2
+
3
+ module Delayed
4
+ module Workless
5
+ module Scaler
6
+ class Local < Base
7
+
8
+ def self.executable_prefix
9
+ if defined? Delayed::Compatibility.executable_prefix
10
+ Delayed::Compatibility.executable_prefix
11
+ else
12
+ 'script'
13
+ end
14
+ end
15
+
16
+ def self.up
17
+ if self.workers == 0
18
+ Rush::Box.new[Rails.root].bash("#{executable_prefix}/delayed_job start -i workless", :background => true)
19
+ sleep 1
20
+ end
21
+ true
22
+ end
23
+
24
+ def self.down
25
+ if self.workers > 0 and jobs.count == 0
26
+ Rush::Box.new[Rails.root].bash("#{executable_prefix}/delayed_job stop -i workless", :background => true)
27
+ end
28
+ true
29
+ end
30
+
31
+ def self.workers
32
+ Rush::Box.new.processes.filter(:cmdline => /delayed_job start -i workless|delayed_job.workless/).size
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ module Delayed
2
+ module Workless
3
+ module Scaler
4
+
5
+ class Null < Base
6
+
7
+ def self.up
8
+ end
9
+
10
+ def self.down
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+ end
17
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/../lib/workless/initialize"
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: workless_revived
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.4
5
+ platform: ruby
6
+ authors:
7
+ - davidakachaos
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-08-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.0.0.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.0.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: heroku-api
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rush
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: delayed_job
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.7
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.7
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Extension to Delayed Job to enable workers to scale up when needed
84
+ email: davidakachaos@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - LICENSE
90
+ - README.md
91
+ - lib/workless.rb
92
+ - lib/workless/initialize.rb
93
+ - lib/workless/middleware/workless_checker.rb
94
+ - lib/workless/railtie.rb
95
+ - lib/workless/scaler.rb
96
+ - lib/workless/scalers/base.rb
97
+ - lib/workless/scalers/heroku.rb
98
+ - lib/workless/scalers/heroku_cedar.rb
99
+ - lib/workless/scalers/local.rb
100
+ - lib/workless/scalers/null.rb
101
+ - rails/init.rb
102
+ homepage: http://github.com/davidakachaos/workless_revived
103
+ licenses: []
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: 2.0.0
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 1.3.6
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 2.4.5.1
122
+ signing_key:
123
+ specification_version: 4
124
+ summary: Use delayed job workers only when theyre needed on Heroku
125
+ test_files: []