qless-pool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Changelog.md ADDED
@@ -0,0 +1,3 @@
1
+ ## 0.1.0 (2013-06-21)
2
+
3
+ * initial version forked from [resque-pool](https://github.com/nevans/resque-pool) (specifically from a [backupify fork of it](https://github.com/backupify/resque-pool/tree/maintain_count)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (C) 2010 by Nicholas Evans <nick@ekenosen.net>, et al.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
20
+
data/README.md ADDED
@@ -0,0 +1,159 @@
1
+ Qless Pool
2
+ ===========
3
+
4
+ [![Build Status](https://secure.travis-ci.org/wr0ngway/qless-pool.png)](http://travis-ci.org/wr0ngway/qless-pool)
5
+
6
+ Qless pool is a simple library for managing a pool of
7
+ [qless](http://github.com/seomoz/qless) workers. Given a a config file, it
8
+ manages your workers for you, starting up the appropriate number of workers for
9
+ each worker type. This is a fork of [resque-pool](https://github.com/nevans/resque-pool) with some commits from a [backupify fork of it](https://github.com/backupify/resque-pool/tree/maintain_count)
10
+
11
+ Benefits
12
+ ---------
13
+
14
+ * Less config - With a simple YAML file, you can start up a pool daemon, and it
15
+ will monitor your workers for you. An example init.d script, monit config,
16
+ and chef cookbook are provided.
17
+ * Less memory - If you are using Ruby Enterprise Edition, or any ruby with
18
+ copy-on-write safe garbage collection, this should save you a *lot* of memory
19
+ when you are managing many workers.
20
+ * Faster startup - when you start many workers at once, they would normally
21
+ compete for CPU as they load their environments. Qless-pool can load the
22
+ environment once and fork all of the workers almost instantly.
23
+
24
+ Upgrading?
25
+ -----------
26
+
27
+ See
28
+ [Changelog.md](https://github.com/wr0ngway/qless-pool/blob/master/Changelog.md)
29
+ in case there are important or helpful changes.
30
+
31
+ How to use
32
+ -----------
33
+
34
+ ### YAML file config
35
+
36
+ Create a `config/qless-pool.yml` (or `qless-pool.yml`) with your worker
37
+ counts. The YAML file supports both using root level defaults as well as
38
+ environment specific overrides (`RACK_ENV`, `RAILS_ENV`, and `QLESS_ENV`
39
+ environment variables can be used to determine environment). For example in
40
+ `config/qless-pool.yml`:
41
+
42
+ foo: 1
43
+ bar: 2
44
+ "foo,bar,baz": 1
45
+
46
+ production:
47
+ "foo,bar,baz": 4
48
+
49
+ ### Rake task config
50
+
51
+ Require the rake tasks (`qless/pool/tasks`) in your `Rakefile`, load your
52
+ application environment, configure Qless as necessary, and configure
53
+ `qless:pool:setup` to disconnect all open files and sockets in the pool
54
+ manager and reconnect in the workers. For example, with rails you should put
55
+ the following into `lib/tasks/qless.rake`:
56
+
57
+ require 'qless/pool/tasks'
58
+ # this task will get called before qless:pool:setup
59
+ # and preload the rails environment in the pool manager
60
+ task "qless:setup" => :environment do
61
+ # generic worker setup, e.g. Hoptoad for failed jobs
62
+ end
63
+ task "qless:pool:setup" do
64
+ # close any sockets or files in pool manager
65
+ ActiveRecord::Base.connection.disconnect!
66
+ # and re-open them in the qless worker parent
67
+ Qless::Pool.after_prefork do |job|
68
+ ActiveRecord::Base.establish_connection
69
+ end
70
+ end
71
+
72
+ ### Start the pool manager
73
+
74
+ Then you can start the queues via:
75
+
76
+ qless-pool --daemon --environment production
77
+
78
+ This will start up seven worker processes, one exclusively for the foo queue,
79
+ two exclusively for the bar queue, and four workers looking at all queues in
80
+ priority. With the config above, this is similar to if you ran the following:
81
+
82
+ rake qless:work RAILS_ENV=production QUEUES=foo &
83
+ rake qless:work RAILS_ENV=production QUEUES=bar &
84
+ rake qless:work RAILS_ENV=production QUEUES=bar &
85
+ rake qless:work RAILS_ENV=production QUEUES=foo,bar,baz &
86
+ rake qless:work RAILS_ENV=production QUEUES=foo,bar,baz &
87
+ rake qless:work RAILS_ENV=production QUEUES=foo,bar,baz &
88
+ rake qless:work RAILS_ENV=production QUEUES=foo,bar,baz &
89
+
90
+ The pool manager will stay around monitoring the qless worker parents, giving
91
+ three levels: a single pool manager, many worker parents, and one worker child
92
+ per worker (when the actual job is being processed). For example, `ps -ef f |
93
+ grep [r]esque` (in Linux) might return something like the following:
94
+
95
+ qless 13858 1 0 13:44 ? S 0:02 qless-pool-manager: managing [13867, 13875, 13871, 13872, 13868, 13870, 13876]
96
+ qless 13867 13858 0 13:44 ? S 0:00 \_ qless-1.9.9: Waiting for foo
97
+ qless 13868 13858 0 13:44 ? S 0:00 \_ qless-1.9.9: Waiting for bar
98
+ qless 13870 13858 0 13:44 ? S 0:00 \_ qless-1.9.9: Waiting for bar
99
+ qless 13871 13858 0 13:44 ? S 0:00 \_ qless-1.9.9: Waiting for foo,bar,baz
100
+ qless 13872 13858 0 13:44 ? S 0:00 \_ qless-1.9.9: Forked 7481 at 1280343254
101
+ qless 7481 13872 0 14:54 ? S 0:00 \_ qless-1.9.9: Processing foo since 1280343254
102
+ qless 13875 13858 0 13:44 ? S 0:00 \_ qless-1.9.9: Waiting for foo,bar,baz
103
+ qless 13876 13858 0 13:44 ? S 0:00 \_ qless-1.9.9: Forked 7485 at 1280343255
104
+ qless 7485 13876 0 14:54 ? S 0:00 \_ qless-1.9.9: Processing bar since 1280343254
105
+
106
+ Running as a daemon will default to placing the pidfile and logfiles in the
107
+ conventional rails locations, although you can configure that. See
108
+ `qless-pool --help` for more options.
109
+
110
+ SIGNALS
111
+ -------
112
+
113
+ The pool manager responds to the following signals:
114
+
115
+ * `HUP` - reload the config file, reload logfiles, restart all workers.
116
+ * `QUIT` - gracefully shut down workers (via `QUIT`) and shutdown the manager
117
+ after all workers are done.
118
+ * `INT` - gracefully shut down workers (via `QUIT`) and immediately shutdown manager
119
+ * `TERM` - immediately shut down workers (via `INT`) and immediately shutdown manager
120
+ _(configurable via command line options)_
121
+ * `WINCH` - _(only when running as a daemon)_ send `QUIT` to each worker, but
122
+ keep manager running (send `HUP` to reload config and restart workers)
123
+ * `USR1`/`USR2`/`CONT` - pass the signal on to all worker parents (see Qless docs).
124
+
125
+ Use `HUP` to help logrotate run smoothly and to change the number of workers
126
+ per worker type. Signals can be sent via the `kill` command, e.g.
127
+ `kill -HUP $master_pid`
128
+
129
+ Other Features
130
+ --------------
131
+
132
+ An example chef cookbook is provided (and should Just Work at Engine Yard as
133
+ is; just provide a `/data/#{app_name}/shared/config/qless-pool.yml` on your
134
+ utility instances). Even if you don't use chef you can use the example init.d
135
+ and monitrc erb templates in `examples/chef_cookbook/templates/default`.
136
+
137
+ You can also start a pool manager via `rake qless:pool` or from a plain old
138
+ ruby script by calling `Qless::Pool.run`.
139
+
140
+ Workers will watch the pool manager, and gracefully shutdown (after completing
141
+ their current job) if the manager process disappears before them.
142
+
143
+ You can specify an alternate config file by setting the `QLESS_POOL_CONFIG` or
144
+ with the `--config` command line option.
145
+
146
+ TODO
147
+ -----
148
+
149
+ See [the TODO list](https://github.com/wr0ngway/qless-pool/issues) at github issues.
150
+
151
+ Contributors
152
+ -------------
153
+
154
+ * Nicholas Evans (Author of resque-pool which this is a fork of)
155
+ * Jason Haruska (from resque-pool)
156
+ * John Schult (from resque-pool)
157
+ * Stephen Celis (from resque-pool)
158
+ * Vincent Agnello, Robert Kamunyori, Paul Kauders (from resque-pool)
159
+
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ # for loading the example config file in config/qless-pool.yml
5
+ require 'qless/pool/tasks'
6
+
7
+ require 'rspec/core/rake_task'
8
+ RSpec::Core::RakeTask.new(:spec) do |t|
9
+ t.rspec_opts = ["-c", "-f progress"]
10
+ end
11
+
12
+ require 'cucumber/rake/task'
13
+ Cucumber::Rake::Task.new(:features) do |c|
14
+ c.profile = "rake"
15
+ end
16
+
17
+ task :default => [:spec, :features]
18
+
19
+ rule(/\.[1-9]$/ => [proc { |tn| "#{tn}.ronn" }]) do |t|
20
+ name = Qless::Pool.name.sub('::','-').upcase
21
+ version = "%s %s" % [name, Qless::Pool::VERSION.upcase]
22
+
23
+ manual = '--manual "%s"' % name
24
+ organization = '--organization "%s"' % version
25
+ sh "ronn #{manual} #{organization} <#{t.source} >#{t.name}"
26
+ end
27
+
28
+ file 'man/qless-pool.1'
29
+ file 'man/qless-pool.yml.5'
30
+ task :manpages => ['man/qless-pool.1','man/qless-pool.yml.5']
data/bin/qless-pool ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
3
+
4
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
5
+
6
+ require 'qless/pool/cli'
7
+ Qless::Pool::CLI.run
@@ -0,0 +1,68 @@
1
+ Feature: Basic qless-pool daemon configuration and operation
2
+ To easily manage a pool of qless workers, qless-pool provides a daemon with
3
+ simple configuration. Static configuration is handled in the
4
+ config/config.yml file and dynamic configuration is handled in the Rakefile.
5
+
6
+ Background:
7
+ Given a file named "Rakefile" with:
8
+ """
9
+ require 'qless/pool/tasks'
10
+ """
11
+
12
+ Scenario: no config file
13
+ When I run the pool manager as "qless-pool"
14
+ Then the pool manager should report that it has started up
15
+ And the pool manager should report that the pool is empty
16
+ And the pool manager should have no child processes
17
+ When I send the pool manager the "QUIT" signal
18
+ Then the pool manager should finish
19
+ And the pool manager should report that it is finished
20
+
21
+ @slow_exit
22
+ Scenario: basic config file
23
+ Given a file named "config/qless-pool.yml" with:
24
+ """
25
+ foo: 1
26
+ bar: 2
27
+ "bar,baz": 3
28
+ """
29
+ When I run the pool manager as "qless-pool"
30
+ Then the pool manager should report that it has started up
31
+ And the pool manager should report that 6 workers are in the pool
32
+ And the pool manager should have 1 "foo" worker child processes
33
+ And the pool manager should have 2 "bar" worker child processes
34
+ And the pool manager should have 3 "bar, baz" worker child processes
35
+ When I send the pool manager the "QUIT" signal
36
+ Then the qless workers should all shutdown
37
+ And the pool manager should finish
38
+ And the pool manager should report that a "foo" worker has been reaped
39
+ And the pool manager should report that a "bar" worker has been reaped
40
+ And the pool manager should report that a "bar,baz" worker has been reaped
41
+ And the pool manager should report that it is finished
42
+
43
+ Scenario: daemonized
44
+ Given a directory named "log"
45
+ And a directory named "tmp/pids"
46
+ And a file named "config/qless-pool.yml" with:
47
+ """
48
+ foo: 2
49
+ bar: 4
50
+ "baz,quux": 4
51
+ """
52
+ When I run the pool manager as "qless-pool -d"
53
+ Then the pool manager should record its pid in "tmp/pids/qless-pool.pid"
54
+ And the pool manager should daemonize
55
+ And a file named "log/qless-pool.stdout.log" should exist
56
+ And a file named "log/qless-pool.stderr.log" should exist
57
+ And the pool manager should log that it has started up
58
+ And the pool manager should log that 10 workers are in the pool
59
+ And the pool manager should have 2 "foo" worker child processes
60
+ And the pool manager should have 4 "bar" worker child processes
61
+ And the pool manager should have 4 "baz, quux" worker child processes
62
+ When I send the pool manager the "QUIT" signal
63
+ Then the qless workers should all shutdown
64
+ And the pool manager daemon should finish
65
+ And the pool manager should log that a "foo" worker has been reaped
66
+ And the pool manager should log that a "bar" worker has been reaped
67
+ And the pool manager should log that a "baz,quux" worker has been reaped
68
+ And the pool manager should log that it is finished
@@ -0,0 +1,33 @@
1
+ # syntactic sugar, and separate ivar. daemons aren't interactive
2
+ When /^I run "([^"]*)" in the background$/ do |cmd|
3
+ run_background(unescape(cmd))
4
+ end
5
+
6
+ Then /^the (output|logfiles) should contain the following lines \(with interpolated \$PID\):$/ do |output_logfiles, partial_output|
7
+ interpolate_background_pid(partial_output).split("\n").each do |line|
8
+ output_or_log(output_logfiles).should include(line)
9
+ end
10
+ end
11
+
12
+ When /^I send "([^"]*)" the "([^"]*)" signal$/ do |cmd, signal|
13
+ send_signal(cmd, signal)
14
+ end
15
+
16
+ Then /^the "([^"]*)" process should finish$/ do |cmd|
17
+ # doesn't actually stop... just polls for exit
18
+ processes[cmd].stop
19
+ end
20
+
21
+ Before("@slow_exit") do
22
+ @aruba_timeout_seconds = 10
23
+ end
24
+
25
+ After do
26
+ kill_all_processes!
27
+ # now kill the daemon!
28
+ begin
29
+ Process.kill(9, @pid_from_pidfile) if @pid_from_pidfile
30
+ rescue Errno::ESRCH
31
+ end
32
+ #`pkill -9 qless-pool`
33
+ end
@@ -0,0 +1,156 @@
1
+ def process_should_exist(pid)
2
+ lambda { Process.kill(0, pid) }.should_not raise_error(Errno::ESRCH)
3
+ end
4
+
5
+ def process_should_not_exist(pid)
6
+ lambda { Process.kill(0, pid) }.should raise_error(Errno::ESRCH)
7
+ end
8
+
9
+ def grab_worker_pids(count, str)
10
+ puts "TODO: check output_or_log for #{count} worker started messages"
11
+ pid_regex = (1..count).map { '(\d+)' }.join ', '
12
+ full_regex = /qless-pool-manager\[aruba\]\[\d+\]: Pool contains worker PIDs: \[#{pid_regex}\]/m
13
+ str.should =~ full_regex
14
+ @worker_pids = full_regex.match(str).captures.map {|pid| pid.to_i }
15
+ end
16
+
17
+ def output_or_logfiles_string(report_log)
18
+ case report_log
19
+ when "report", "output"
20
+ "output"
21
+ when "log", "logfiles"
22
+ "logfiles"
23
+ else
24
+ raise ArgumentError
25
+ end
26
+ end
27
+
28
+ def output_or_log(report_log)
29
+ case report_log
30
+ when "report", "output"
31
+ interactive_output
32
+ when "log", "logfiles"
33
+ in_current_dir do
34
+ File.read("log/qless-pool.stdout.log") << File.read("log/qless-pool.stderr.log")
35
+ end
36
+ else
37
+ raise ArgumentError
38
+ end
39
+ end
40
+
41
+ class NotFinishedStarting < StandardError; end
42
+ def worker_processes_for(queues)
43
+ children_of(background_pid).select do |pid, cmd|
44
+ raise NotFinishedStarting if cmd =~ /Starting$/
45
+ cmd =~ /^Qless-\d+.\d+.\d+: Waiting for #{queues} \(ordered\) at/
46
+ end
47
+ rescue NotFinishedStarting
48
+ retry
49
+ end
50
+
51
+ def children_of(ppid)
52
+ osx = RUBY_PLATFORM =~ /darwin/i
53
+ ps = `ps -eo ppid,pid,#{osx ? 'args' : 'cmd'} | grep '^ *#{ppid} '`
54
+ ps.split(/\s*\n/).map do |line|
55
+ _, pid, cmd = line.strip.split(/\s+/, 3)
56
+ [pid, cmd]
57
+ end
58
+ end
59
+
60
+ When /^I run the pool manager as "([^"]*)"$/ do |cmd|
61
+ @pool_manager_process = run_background(unescape(cmd))
62
+ end
63
+
64
+ When /^I send the pool manager the "([^"]*)" signal$/ do |signal|
65
+ Process.kill signal, background_pid
66
+ output_logfiles = @pid_from_pidfile ? "logfiles" : "output"
67
+ case signal
68
+ when "QUIT"
69
+ keep_trying do
70
+ step "the #{output_logfiles} should contain the following lines (with interpolated $PID):", <<-EOF
71
+ qless-pool-manager[aruba][$PID]: QUIT: graceful shutdown, waiting for children
72
+ EOF
73
+ end
74
+ else
75
+ raise ArgumentError
76
+ end
77
+ end
78
+
79
+ Then /^the pool manager should record its pid in "([^"]*)"$/ do |pidfile|
80
+ in_current_dir do
81
+ keep_trying do
82
+ File.should be_file(pidfile)
83
+ @pid_from_pidfile = File.read(pidfile).to_i
84
+ @pid_from_pidfile.should_not == 0
85
+ process_should_exist(@pid_from_pidfile)
86
+ end
87
+ end
88
+ end
89
+
90
+ Then /^the pool manager should daemonize$/ do
91
+ stop_processes!
92
+ end
93
+
94
+ Then /^the pool manager daemon should finish$/ do
95
+ keep_trying do
96
+ process_should_not_exist(@pid_from_pidfile)
97
+ end
98
+ end
99
+
100
+ # nomenclature: "report" => output to stdout/stderr
101
+ # "log" => output to default logfile
102
+
103
+ Then /^the pool manager should (report|log) that it has started up$/ do |report_log|
104
+ keep_trying do
105
+ step "the #{output_or_logfiles_string(report_log)} should contain the following lines (with interpolated $PID):", <<-EOF
106
+ qless-pool-manager[aruba][$PID]: Qless Pool running in test environment
107
+ qless-pool-manager[aruba][$PID]: started manager
108
+ EOF
109
+ end
110
+ end
111
+
112
+ Then /^the pool manager should (report|log) that the pool is empty$/ do |report_log|
113
+ step "the #{output_or_logfiles_string(report_log)} should contain the following lines (with interpolated $PID):", <<-EOF
114
+ qless-pool-manager[aruba][$PID]: Pool is empty
115
+ EOF
116
+ end
117
+
118
+ Then /^the pool manager should (report|log) that (\d+) workers are in the pool$/ do |report_log, count|
119
+ grab_worker_pids Integer(count), output_or_log(report_log)
120
+ end
121
+
122
+ Then /^the qless workers should all shutdown$/ do
123
+ @worker_pids.each do |pid|
124
+ keep_trying do
125
+ process_should_not_exist(pid)
126
+ end
127
+ end
128
+ end
129
+
130
+ Then "the pool manager should have no child processes" do
131
+ children_of(background_pid).should have(:no).keys
132
+ end
133
+
134
+ Then /^the pool manager should have (\d+) "([^"]*)" worker child processes$/ do |count, queues|
135
+ worker_processes_for(queues).should have(Integer(count)).members
136
+ end
137
+
138
+ Then "the pool manager should finish" do
139
+ # assuming there will not be multiple processes running
140
+ stop_processes!
141
+ end
142
+
143
+ Then /^the pool manager should (report|log) that it is finished$/ do |report_log|
144
+ step "the #{output_or_logfiles_string(report_log)} should contain the following lines (with interpolated $PID):", <<-EOF
145
+ qless-pool-manager[aruba][$PID]: manager finished
146
+ EOF
147
+ end
148
+
149
+ Then /^the pool manager should (report|log) that a "([^"]*)" worker has been reaped$/ do |report_log, worker_type|
150
+ step 'the '+ output_or_logfiles_string(report_log) +' should match /Reaped qless worker\[\d+\] \(status: 0\) queues: '+ worker_type + '/'
151
+ end
152
+
153
+ Then /^the logfiles should match \/([^\/]*)\/$/ do |partial_output|
154
+ output_or_log("log").should =~ /#{partial_output}/
155
+ end
156
+