qless-pool 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.
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
+