delayed_job_worker_pool 0.1.0

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: 79d4a1eeecbbf23ff19a1811faf6a8d35d9616c5
4
+ data.tar.gz: 99b6e576ac2ef89486b12614f171dd2db7d4588c
5
+ SHA512:
6
+ metadata.gz: 303a786f4bbe3c5e96a23e6cd63582f321289b4b8e63489071b9502059d503cf12f2667f8891c11a2955947bf2a426831840bdd28377a7e57dec5afac4179205
7
+ data.tar.gz: 824d5d7e04193c7a4ed6fbddec598f378dc6f5f2ad57e39bc32700f3eb0a000e1881778aaf0fedf371607012812b10859adafd7b0606723154066ce1fc73a7c0
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .rspec
11
+ *.sqlite3
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Joel Turkel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Delayed Job Worker Pool
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/delayed_job_worker_pool.png)][gem]
4
+ [![Build Status](https://secure.travis-ci.org/salsify/delayed_job_worker_pool.png?branch=master)][travis]
5
+ [![Code Climate](https://codeclimate.com/github/salsify/delayed_job_worker_pool.png)][codeclimate]
6
+
7
+ [gem]: https://rubygems.org/gems/delayed_job_worker_pool
8
+ [travis]: http://travis-ci.org/salsify/delayed_job_worker_pool
9
+ [codeclimate]: https://codeclimate.com/github/salsify/delayed_job_worker_pool
10
+
11
+ [Delayed Job's](https://github.com/collectiveidea/delayed_job) built-in worker pooling daemonizes all worker processes. This is great for certain environments but not so great for environments like Heroku that really want your processes to run in the foreground. Delayed Job Worker Pool runs a pool of Delayed Job workers **without** daemonizing them. [Salsify](http://salsify.com) is currently using this to run multiple Delayed Job workers on a single Heroku PX dyno.
12
+
13
+ This gem only works with MRI on Linux/MacOS X.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'delayed_job_worker_pool'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install delayed_job_worker_pool
30
+
31
+ ## Usage
32
+
33
+ From your Rails root directory run:
34
+
35
+ ```
36
+ delayed_job_worker_pool <config file>
37
+ ```
38
+
39
+ The config file is a Ruby DSL inspired by the [Puma](https://github.com/puma/puma) configuration DSL. Here's an example:
40
+
41
+ ```
42
+ workers Integer(ENV['NUM_WORKERS'] || 1)
43
+ queues (ENV['QUEUES'] || ENV['QUEUE'] || '').split(',')
44
+ sleep_delay ENV['WORKER_SLEEP_DELAY']
45
+
46
+ preload_app
47
+
48
+ before_worker_boot do
49
+ puts 'Master about to start forking children'
50
+ end
51
+
52
+ before on_worker_boot do
53
+ puts "Worker #{Process.pid} started"
54
+
55
+ # Reconnect to the database
56
+ ActiveRecord::Base.establish_connection
57
+ end
58
+
59
+ after_worker_boot do
60
+ puts 'Master booted children'
61
+
62
+ # Don't hang on to database connections from the master after we've completed initialization
63
+ ActiveRecord::Base.connection_pool.disconnect!
64
+ end
65
+ ```
66
+
67
+ Here's more information on each setting:
68
+
69
+ * `workers` - The number of Delayed Job worker processes to fork. The master process will relaunch workers that fail.
70
+ * Delayed Job worker settings (`queues`, `min_priority`, `max_priority`, `sleep_delay`, `read_ahead`) - These are passed through to the Delayed Job worker.
71
+ * `preload_app` - This forces the master process to load Rails before forking worker processes causing the memory consumed by the code to be shared between workers. **If you use this setting make sure you re-establish any necessary connections in the on_worker_boot callback.**
72
+ * `before_worker_boot` - A callback that runs in the master process before forking any workers.
73
+ * `on_worker_boot` - A callback that runs in the worker process after it has been forked.
74
+ * `after_worker_boot` - A callback that runs in the master process after the initial set of workers have been forked.
75
+
76
+ All settings are optional and nil values are ignored.
77
+
78
+ ## Contributing
79
+
80
+ Bug reports and pull requests are welcome on GitHub at https://github.com/salsify/delayed_job_worker_pool.
81
+
82
+
83
+ ## License
84
+
85
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
86
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ unless ARGV.size == 1
4
+ puts "usage: #{File.basename(__FILE__)} <config>"
5
+ exit(1)
6
+ end
7
+
8
+ require 'delayed_job_worker_pool'
9
+
10
+ options = DelayedJobWorkerPool::DSL.load(ARGV[0])
11
+ DelayedJobWorkerPool::WorkerPool.new(options).run
@@ -0,0 +1,28 @@
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_worker_pool/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'delayed_job_worker_pool'
8
+ spec.version = DelayedJobWorkerPool::VERSION
9
+ spec.authors = ['Joel Turkel']
10
+ spec.email = ['jturkel@salsify.com']
11
+
12
+ spec.summary = 'Worker process pooling for Delayed Job'
13
+ spec.homepage = 'https://github.com/salsify/delayed_job_worker_pool'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.executables = ['delayed_job_worker_pool']
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'delayed_job', ['>= 3.0', '< 4.1']
21
+
22
+ spec.add_development_dependency 'delayed_job_active_record'
23
+ spec.add_development_dependency 'bundler', '~> 1.10'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '>= 3.3'
26
+ spec.add_development_dependency 'sqlite3', '>= 1.3'
27
+ spec.add_development_dependency 'rails', '>= 4.2'
28
+ end
@@ -0,0 +1,5 @@
1
+ require 'delayed_job_worker_pool/application'
2
+ require 'delayed_job_worker_pool/dsl'
3
+ require 'delayed_job_worker_pool/worker'
4
+ require 'delayed_job_worker_pool/worker_pool'
5
+ require 'delayed_job_worker_pool/version'
@@ -0,0 +1,22 @@
1
+ module DelayedJobWorkerPool
2
+ module Application
3
+ extend self
4
+
5
+ def load
6
+ require(base_application_filename)
7
+ rescue LoadError
8
+ raise "Could not find Rails initialization file #{full_application_filename}. " \
9
+ "Make sure delayed_job_worker_pool is run from the Rails root directory."
10
+ end
11
+
12
+ private
13
+
14
+ def base_application_filename
15
+ "#{Dir.pwd}/config/environment"
16
+ end
17
+
18
+ def full_application_filename
19
+ "#{base_application_filename}.rb"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,35 @@
1
+ module DelayedJobWorkerPool
2
+ class DSL
3
+ SIMPLE_SETTINGS = [:workers, :queues, :min_priority, :max_priority, :sleep_delay, :read_ahead].freeze
4
+ CALLBACK_SETTINGS = [:before_worker_boot, :on_worker_boot, :after_worker_boot].freeze
5
+
6
+ def self.load(path)
7
+ options = {}
8
+
9
+ dsl = new(options)
10
+ dsl.instance_eval(File.read(path), path, 1)
11
+
12
+ options
13
+ end
14
+
15
+ def initialize(options)
16
+ @options = options
17
+ end
18
+
19
+ SIMPLE_SETTINGS.each do |option_name|
20
+ define_method(option_name) do |option_value|
21
+ @options[option_name] = option_value unless option_value.nil?
22
+ end
23
+ end
24
+
25
+ def preload_app(preload_app = true)
26
+ @options[:preload_app] = preload_app
27
+ end
28
+
29
+ CALLBACK_SETTINGS.each do |option_name|
30
+ define_method(option_name) do |&block|
31
+ @options[option_name] = block
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module DelayedJobWorkerPool
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,10 @@
1
+ module DelayedJobWorkerPool
2
+ module Worker
3
+ extend self
4
+
5
+ def run(options = {})
6
+ dj_worker = Delayed::Worker.new(options)
7
+ dj_worker.start
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,133 @@
1
+ module DelayedJobWorkerPool
2
+ class WorkerPool
3
+ def initialize(options = {})
4
+ @options = options
5
+ @worker_pids = []
6
+ self.shutting_down = false
7
+ end
8
+
9
+ def run
10
+ log("Starting master #{Process.pid}")
11
+
12
+ install_signal_handlers
13
+
14
+ load_app if preload_app?
15
+
16
+ invoke_callback(:before_worker_boot)
17
+
18
+ log_uninheritable_threads
19
+
20
+ create_master_alive_pipe
21
+ num_workers.times { fork_worker }
22
+
23
+ invoke_callback(:after_worker_boot)
24
+
25
+ monitor_workers
26
+
27
+ exit
28
+ ensure
29
+ master_alive_write_pipe.close if master_alive_write_pipe
30
+ master_alive_read_pipe.close if master_alive_read_pipe
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :options, :worker_pids, :master_alive_read_pipe, :master_alive_write_pipe
36
+ attr_accessor :shutting_down
37
+
38
+ def install_signal_handlers
39
+ trap('TERM') do
40
+ shutdown('TERM')
41
+ end
42
+
43
+ trap('INT') do
44
+ shutdown('INT')
45
+ end
46
+ end
47
+
48
+ def log_uninheritable_threads
49
+ Thread.list.each do |t|
50
+ next if t == Thread.current
51
+ if t.respond_to?(:backtrace)
52
+ log("WARNING: Thread will not be inherited by workers: #{t.inspect} - #{t.backtrace ? t.backtrace.first : ''}")
53
+ else
54
+ log("WARNING: Thread will not be inherited by workers: #{t.inspect}")
55
+ end
56
+ end
57
+ end
58
+
59
+ def create_master_alive_pipe
60
+ @master_alive_read_pipe, @master_alive_write_pipe = IO.pipe
61
+ end
62
+
63
+ def load_app
64
+ DelayedJobWorkerPool::Application.load
65
+ end
66
+
67
+ def shutdown(signal)
68
+ log("Shutting down master #{Process.pid} with signal #{signal}")
69
+ self.shutting_down = true
70
+ worker_pids.each do |child_pid|
71
+ log("Telling worker #{child_pid} to shutdown with signal #{signal}")
72
+ Process.kill(signal, child_pid)
73
+ end
74
+ end
75
+
76
+ def monitor_workers
77
+ until worker_pids.empty?
78
+ worker_pid, status = Process.wait2
79
+
80
+ next unless worker_pids.include?(worker_pid)
81
+
82
+ log("Worker #{worker_pid} exited with status #{status.to_i}")
83
+ worker_pids.delete(worker_pid)
84
+ fork_worker unless shutting_down
85
+ end
86
+ end
87
+
88
+ def invoke_callback(callback_name)
89
+ options[callback_name].call if options[callback_name]
90
+ end
91
+
92
+ def fork_worker
93
+ worker_pid = Kernel.fork { run_worker }
94
+ worker_pids << worker_pid
95
+ log("Started worker #{worker_pid}")
96
+ end
97
+
98
+ def run_worker
99
+ master_alive_write_pipe.close
100
+
101
+ Thread.new do
102
+ IO.select([master_alive_read_pipe])
103
+ log('Detected dead master. Shutting down worker.')
104
+ exit(1)
105
+ end
106
+
107
+ load_app unless preload_app?
108
+
109
+ invoke_callback(:on_worker_boot)
110
+
111
+ DelayedJobWorkerPool::Worker.run(worker_options)
112
+ rescue => e
113
+ log("Worker failed with error: #{e.message}\n#{e.backtrace.join("\n")}")
114
+ exit(1)
115
+ end
116
+
117
+ def num_workers
118
+ options.fetch(:workers, 1)
119
+ end
120
+
121
+ def preload_app?
122
+ options.fetch(:preload_app, false)
123
+ end
124
+
125
+ def worker_options
126
+ options.except(:workers, :preload_app, :before_worker_boot, :on_worker_boot, :after_worker_boot)
127
+ end
128
+
129
+ def log(message)
130
+ puts(message)
131
+ end
132
+ end
133
+ end
metadata ADDED
@@ -0,0 +1,163 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: delayed_job_worker_pool
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Joel Turkel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: delayed_job
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_active_record
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.10'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.10'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '10.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '10.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '3.3'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '3.3'
89
+ - !ruby/object:Gem::Dependency
90
+ name: sqlite3
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '1.3'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '1.3'
103
+ - !ruby/object:Gem::Dependency
104
+ name: rails
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '4.2'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '4.2'
117
+ description:
118
+ email:
119
+ - jturkel@salsify.com
120
+ executables:
121
+ - delayed_job_worker_pool
122
+ extensions: []
123
+ extra_rdoc_files: []
124
+ files:
125
+ - ".gitignore"
126
+ - ".travis.yml"
127
+ - Gemfile
128
+ - LICENSE.txt
129
+ - README.md
130
+ - Rakefile
131
+ - bin/delayed_job_worker_pool
132
+ - delayed_job_worker_pool.gemspec
133
+ - lib/delayed_job_worker_pool.rb
134
+ - lib/delayed_job_worker_pool/application.rb
135
+ - lib/delayed_job_worker_pool/dsl.rb
136
+ - lib/delayed_job_worker_pool/version.rb
137
+ - lib/delayed_job_worker_pool/worker.rb
138
+ - lib/delayed_job_worker_pool/worker_pool.rb
139
+ homepage: https://github.com/salsify/delayed_job_worker_pool
140
+ licenses:
141
+ - MIT
142
+ metadata: {}
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.4.7
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: Worker process pooling for Delayed Job
163
+ test_files: []