qless-pool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,136 @@
1
+ require 'trollop'
2
+ require 'qless/pool'
3
+ require 'fileutils'
4
+
5
+ module Qless
6
+ class Pool
7
+ module CLI
8
+ extend self
9
+
10
+ def run
11
+ opts = parse_options
12
+ daemonize if opts[:daemon]
13
+ manage_pidfile opts[:pidfile]
14
+ redirect opts
15
+ setup_environment opts
16
+ set_pool_options opts
17
+ start_pool
18
+ end
19
+
20
+ def parse_options
21
+ opts = Trollop::options do
22
+ version "qless-pool #{VERSION} (c) nicholas a. evans"
23
+ banner <<-EOS
24
+ qless-pool is the best way to manage a group (pool) of qless workers
25
+
26
+ When daemonized, stdout and stderr default to qless-pool.stdxxx.log files in
27
+ the log directory and pidfile defaults to qless-pool.pid in the current dir.
28
+
29
+ Usage:
30
+ qless-pool [options]
31
+ where [options] are:
32
+ EOS
33
+ opt :config, "Alternate path to config file", :type => String, :short => "-c"
34
+ opt :appname, "Alternate appname", :type => String, :short => "-a"
35
+ opt :daemon, "Run as a background daemon", :default => false, :short => "-d"
36
+ opt :stdout, "Redirect stdout to logfile", :type => String, :short => '-o'
37
+ opt :stderr, "Redirect stderr to logfile", :type => String, :short => '-e'
38
+ opt :nosync, "Don't sync logfiles on every write"
39
+ opt :pidfile, "PID file location", :type => String, :short => "-p"
40
+ opt :environment, "Set RAILS_ENV/RACK_ENV/QLESS_ENV", :type => String, :short => "-E"
41
+ opt :term_graceful_wait, "On TERM signal, wait for workers to shut down gracefully"
42
+ opt :term_graceful, "On TERM signal, shut down workers gracefully"
43
+ opt :term_immediate, "On TERM signal, shut down workers immediately (default)"
44
+ end
45
+ if opts[:daemon]
46
+ opts[:stdout] ||= "log/qless-pool.stdout.log"
47
+ opts[:stderr] ||= "log/qless-pool.stderr.log"
48
+ opts[:pidfile] ||= "tmp/pids/qless-pool.pid"
49
+ end
50
+ opts
51
+ end
52
+
53
+ def daemonize
54
+ raise 'First fork failed' if (pid = fork) == -1
55
+ exit unless pid.nil?
56
+ Process.setsid
57
+ raise 'Second fork failed' if (pid = fork) == -1
58
+ exit unless pid.nil?
59
+ end
60
+
61
+ def manage_pidfile(pidfile)
62
+ return unless pidfile
63
+ pid = Process.pid
64
+ if File.exist? pidfile
65
+ if process_still_running? pidfile
66
+ raise "Pidfile already exists at #{pidfile} and process is still running."
67
+ else
68
+ File.delete pidfile
69
+ end
70
+ else
71
+ FileUtils.mkdir_p File.dirname(pidfile)
72
+ end
73
+ File.open pidfile, "w" do |f|
74
+ f.write pid
75
+ end
76
+ at_exit do
77
+ if Process.pid == pid
78
+ File.delete pidfile
79
+ end
80
+ end
81
+ end
82
+
83
+ def process_still_running?(pidfile)
84
+ old_pid = open(pidfile).read.strip.to_i
85
+ Process.kill 0, old_pid
86
+ true
87
+ rescue Errno::ESRCH
88
+ false
89
+ rescue Errno::EPERM
90
+ true
91
+ rescue ::Exception => e
92
+ $stderr.puts "While checking if PID #{old_pid} is running, unexpected #{e.class}: #{e}"
93
+ true
94
+ end
95
+
96
+ def redirect(opts)
97
+ $stdin.reopen '/dev/null' if opts[:daemon]
98
+ # need to reopen as File, or else Qless::Pool::Logging.reopen_logs! won't work
99
+ out = File.new(opts[:stdout], "a") if opts[:stdout] && !opts[:stdout].empty?
100
+ err = File.new(opts[:stderr], "a") if opts[:stderr] && !opts[:stderr].empty?
101
+ $stdout.reopen out if out
102
+ $stderr.reopen err if err
103
+ $stdout.sync = $stderr.sync = true unless opts[:nosync]
104
+ end
105
+
106
+ # TODO: global variables are not the best way
107
+ def set_pool_options(opts)
108
+ if opts[:daemon]
109
+ Qless::Pool.handle_winch = true
110
+ end
111
+ if opts[:term_graceful_wait]
112
+ Qless::Pool.term_behavior = "graceful_worker_shutdown_and_wait"
113
+ elsif opts[:term_graceful]
114
+ Qless::Pool.term_behavior = "graceful_worker_shutdown"
115
+ end
116
+ end
117
+
118
+ def setup_environment(opts)
119
+ Qless::Pool.app_name = opts[:appname] if opts[:appname]
120
+ ENV["RACK_ENV"] = ENV["RAILS_ENV"] = ENV["QLESS_ENV"] = opts[:environment] if opts[:environment]
121
+ Qless::Pool.log "Qless Pool running in #{ENV["RAILS_ENV"] || "development"} environment"
122
+ ENV["QLESS_POOL_CONFIG"] = opts[:config] if opts[:config]
123
+ end
124
+
125
+ def start_pool
126
+ require 'rake'
127
+ require 'qless/pool/tasks'
128
+ Rake.application.init
129
+ Rake.application.load_rakefile
130
+ Rake.application["qless:pool"].invoke
131
+ end
132
+
133
+ end
134
+ end
135
+ end
136
+
@@ -0,0 +1,65 @@
1
+ module Qless
2
+ class Pool
3
+ module Logging
4
+ extend self
5
+
6
+ # more than a little bit complicated...
7
+ # copied this from Unicorn.
8
+ def self.reopen_logs!
9
+ log "Flushing logs"
10
+ [$stdout, $stderr].each do |fd|
11
+ if fd.instance_of? File
12
+ # skip if the file is the exact same inode and device
13
+ orig_st = fd.stat
14
+ begin
15
+ cur_st = File.stat(fd.path)
16
+ next if orig_st.ino == cur_st.ino && orig_st.dev == cur_st.dev
17
+ rescue Errno::ENOENT
18
+ end
19
+ # match up the encoding
20
+ open_arg = 'a'
21
+ if fd.respond_to?(:external_encoding) && enc = fd.external_encoding
22
+ open_arg << ":#{enc.to_s}"
23
+ enc = fd.internal_encoding and open_arg << ":#{enc.to_s}"
24
+ end
25
+ # match up buffering (does reopen reset this?)
26
+ sync = fd.sync
27
+ # sync to disk
28
+ fd.fsync
29
+ # reopen, and set ruby buffering appropriately
30
+ fd.reopen fd.path, open_arg
31
+ fd.sync = sync
32
+ log "Reopened logfile: #{fd.path}"
33
+ end
34
+ end
35
+ end
36
+
37
+ # Given a string, sets the procline ($0)
38
+ # Procline is always in the format of:
39
+ # qless-pool-master: STRING
40
+ def procline(string)
41
+ $0 = "qless-pool-master#{app}: #{string}"
42
+ end
43
+
44
+ # TODO: make this use an actual logger
45
+ def log(message)
46
+ puts "qless-pool-manager#{app}[#{Process.pid}]: #{message}"
47
+ #$stdout.fsync
48
+ end
49
+
50
+ # TODO: make this use an actual logger
51
+ def log_worker(message)
52
+ puts "qless-pool-worker#{app}[#{Process.pid}]: #{message}"
53
+ #$stdout.fsync
54
+ end
55
+
56
+ # Include optional app name in procline
57
+ def app
58
+ app_name = self.respond_to?(:app_name) && self.app_name
59
+ app_name ||= self.class.respond_to?(:app_name) && self.class.app_name
60
+ app_name ? "[#{app_name}]" : ""
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,43 @@
1
+ module Qless
2
+ class PoolFactory
3
+
4
+ def initialize(options={})
5
+ @options = {
6
+ :term_timeout => ENV['TERM_TIMEOUT'] || 4.0,
7
+ :verbose => !!ENV['VERBOSE'],
8
+ :very_verbose => !!ENV['VVERBOSE'],
9
+ :run_as_single_process => !!ENV['RUN_AS_SINGLE_PROCESS']
10
+ }.merge(options)
11
+ end
12
+
13
+ def client
14
+ @qless_client ||= Qless::Client.new
15
+ end
16
+
17
+ def client=(client)
18
+ @qless_client = client
19
+ end
20
+
21
+ def reserver_class
22
+ @reserver_class ||= Qless::JobReservers.const_get(ENV.fetch('JOB_RESERVER', 'Ordered'))
23
+ end
24
+
25
+ def reserver_class=(reserver_class)
26
+ @reserver_class = reserver_class
27
+ end
28
+
29
+ def reserver(queues)
30
+ reserver_class.new(queues)
31
+ end
32
+
33
+ def worker(queues)
34
+ queues = queues.to_s.split(',').map { |q| client.queues[q.strip] }
35
+ if queues.none?
36
+ raise "No queues provided"
37
+ end
38
+
39
+ Qless::Worker.new(reserver(queues), @options)
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ require 'qless/worker'
2
+
3
+ class Qless::Pool
4
+ module PooledWorker
5
+ def shutdown_with_pool
6
+ shutdown_without_pool || Process.ppid == 1
7
+ end
8
+
9
+ def self.included(base)
10
+ base.instance_eval do
11
+ alias_method :shutdown_without_pool, :shutdown?
12
+ alias_method :shutdown?, :shutdown_with_pool
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+
19
+ Qless::Worker.class_eval do
20
+ include Qless::Pool::PooledWorker
21
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'qless/tasks'
3
+
4
+ namespace :qless do
5
+
6
+ # qless worker config (not pool related). e.g. hoptoad, rails environment
7
+ task :setup
8
+
9
+ namespace :pool do
10
+ # qless pool config. e.g. after_prefork connection handling
11
+ task :setup
12
+ end
13
+
14
+ desc "Launch a pool of qless workers"
15
+ task :pool => %w[qless:setup qless:pool:setup] do
16
+ require 'qless/pool'
17
+ Qless::Pool.run
18
+ end
19
+
20
+ end
@@ -0,0 +1,5 @@
1
+ module Qless
2
+ class Pool
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
data/man/qless-pool.1 ADDED
@@ -0,0 +1,88 @@
1
+ .\" generated with Ronn/v0.7.3
2
+ .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
+ .
4
+ .TH "QLESS\-POOL" "1" "June 2012" "QLESS-POOL 0.4.0.DEV" "QLESS-POOL"
5
+ .
6
+ .SH "NAME"
7
+ \fBqless\-pool\fR \- qless worker pool management
8
+ .
9
+ .SH "SYNOPSIS"
10
+ \fBqless\-pool\fR \fIoptions\fR
11
+ .
12
+ .SH "DESCRIPTION"
13
+ \fBQless\-pool\fR is the best way to manage a group (pool) of qless workers\.
14
+ .
15
+ .P
16
+ When qless\-pool(1) is daemonized the \fBstdout\fR and \fBstderr\fR output streams are redirected to \fBqless\-pool\.stdxxx\.log\fR log files in the \fBlog\fR directory\. Additionally the PID file defaults to \fBqless\-pool\.pid\fR in the \fBtmp/pids\fR directory\.
17
+ .
18
+ .SH "OPTIONS"
19
+ .
20
+ .IP "\(bu" 4
21
+ \fB\-c, \-\-config\fR \fIfile\fR: Uses the configuration specified in the \fIfile\fR provided instead of searching in the current and \fBconfig\fR directories for \fBqless\-pool\.yml\fR\.
22
+ .
23
+ .IP "\(bu" 4
24
+ \fB\-a, \-\-appname\fR \fIname\fR: Specifies the app name to be used for logging and procline\. If not specified, this defaults to the current working directory\.
25
+ .
26
+ .IP "\(bu" 4
27
+ \fB\-d, \-\-daemon\fR: Runs \fBqless\-pool\fR in the background as a daemon process\. This will redirect \fBstdout\fR and \fBstderr\fR to log files and write a PID file\.
28
+ .
29
+ .IP "\(bu" 4
30
+ \fB\-o, \-\-stdout\fR \fIfile\fR: Writes the normal log output to \fIfile\fR instead of printing to the terminal\. When running as a daemon this defaults to the path \fBlog/qless\-pool\.stdout\.log\fR\.
31
+ .
32
+ .IP "\(bu" 4
33
+ \fB\-e, \-\-stderr\fR \fIfile\fR: Writes the standard error output to \fIfile\fR instead of printing to the terminal\. When running as a daemon this defaults to the path \fBlog/qless\-pool\.stderr\.log\fR\.
34
+ .
35
+ .IP "\(bu" 4
36
+ \fB\-\-nosync\fR Allows writes to \fBstdout\fR and \fBstderr\fR to be buffered\.
37
+ .
38
+ .IP "\(bu" 4
39
+ \fB\-p, \-\-pidfile\fR \fIfile\fR: Writes the PID to the \fIfile\fR\. When running as a daemon this defaults to \fBtmp/pids/qless\-pool\.pid\fR\.
40
+ .
41
+ .IP "\(bu" 4
42
+ \fB\-E, \-\-environment\fR \fIname\fR: Specifies the environment \fIname\fR to be set for \fBRAILS_ENV\fR, \fBRACK_ENV\fR and \fBQLESS_ENV\fR which will be passed on to the pooled qless workers\.
43
+ .
44
+ .IP "\(bu" 4
45
+ \fB\-\-term\-graceful\-wait\fR: Configure TERM signal handling so the master will gracefully request worker shut downs (via \fBQUIT\fR) and wait for the workers to quit before shutting down itself\. This is the same behavior as the \fBQUIT\fR signal\.
46
+ .
47
+ .IP "\(bu" 4
48
+ \fB\-\-term\-graceful\fR: Configure TERM signal handling so the master will gracefully request worker shut downs (via \fBQUIT\fR) but shut itself down immediately\. This the same behavior as the \fBINT\fR signal\.
49
+ .
50
+ .IP "\(bu" 4
51
+ \fB\-\-term\-immediate\fR: Configure TERM signal handling so the master will request imediate worker shut downs (via \fBINT\fR) and shut itself down immediately\. This is the default \fBTERM\fR signal behavior\.
52
+ .
53
+ .IP "" 0
54
+ .
55
+ .SH "HISTORY"
56
+ .
57
+ .TP
58
+ \fBv0\.3\.0\fR
59
+ Support for ruby 1\.9, qless 1\.20\.
60
+ .
61
+ .br
62
+ Added appname for logging\.
63
+ .
64
+ .br
65
+ Minor bugfixes\.
66
+ .
67
+ .TP
68
+ \fBv0\.2\.0\fR
69
+ Support for reloading logs and workers with \fBHUP\fR signal
70
+ .
71
+ .br
72
+ Cleans up PID file on startup
73
+ .
74
+ .br
75
+ Fixed \fB\-c, \-\-config\fR option\.
76
+ .
77
+ .TP
78
+ \fBv0\.1\.0\fR
79
+ \fBqless\-pool\fR command line interface added
80
+ .
81
+ .SH "AUTHOR"
82
+ Nicholas Evans
83
+ .
84
+ .SH "COPYRIGHT"
85
+ Copyright (C) 2010 by Nicholas Evans \fInick@ekenosen\.net\fR, et al\.
86
+ .
87
+ .SH "SEE ALSO"
88
+ qless\-pool\.yml(5)
@@ -0,0 +1,92 @@
1
+ qless-pool(1) -- qless worker pool management
2
+ ================================================
3
+
4
+ ## SYNOPSIS
5
+
6
+ `qless-pool` [options]
7
+
8
+ ## DESCRIPTION
9
+
10
+ **Qless-pool** is the best way to manage a group (pool) of qless workers.
11
+
12
+ When qless-pool(1) is daemonized the `stdout` and `stderr` output streams
13
+ are redirected to `qless-pool.stdxxx.log` log files in the `log` directory.
14
+ Additionally the PID file defaults to `qless-pool.pid` in the `tmp/pids`
15
+ directory.
16
+
17
+ ## OPTIONS
18
+
19
+ * `-c, --config` <file>:
20
+ Uses the configuration specified in the <file> provided instead of
21
+ searching in the current and `config` directories for `qless-pool.yml`.
22
+
23
+ * `-a, --appname` <name>:
24
+ Specifies the app name to be used for logging and procline. If not
25
+ specified, this defaults to the current working directory.
26
+
27
+ * `-d, --daemon`:
28
+ Runs `qless-pool` in the background as a daemon process. This will
29
+ redirect `stdout` and `stderr` to log files and write a PID file.
30
+
31
+ * `-o, --stdout` <file>:
32
+ Writes the normal log output to <file> instead of printing to the
33
+ terminal. When running as a daemon this defaults to the path
34
+ `log/qless-pool.stdout.log`.
35
+
36
+ * `-e, --stderr` <file>:
37
+ Writes the standard error output to <file> instead of printing to the
38
+ terminal. When running as a daemon this defaults to the path
39
+ `log/qless-pool.stderr.log`.
40
+
41
+ * `--nosync`
42
+ Allows writes to `stdout` and `stderr` to be buffered.
43
+
44
+ * `-p, --pidfile` <file>:
45
+ Writes the PID to the <file>. When running as a daemon this defaults
46
+ to `tmp/pids/qless-pool.pid`.
47
+
48
+ * `-E, --environment` <name>:
49
+ Specifies the environment <name> to be set for `RAILS_ENV`, `RACK_ENV`
50
+ and `QLESS_ENV` which will be passed on to the pooled qless workers.
51
+
52
+ * `--term-graceful-wait`:
53
+ Configure TERM signal handling so the master will gracefully request worker
54
+ shut downs (via `QUIT`) and wait for the workers to quit before shutting
55
+ down itself. This is the same behavior as the `QUIT` signal.
56
+
57
+ * `--term-graceful`:
58
+ Configure TERM signal handling so the master will gracefully request worker
59
+ shut downs (via `QUIT`) but shut itself down immediately. This the same
60
+ behavior as the `INT` signal.
61
+
62
+ * `--term-immediate`:
63
+ Configure TERM signal handling so the master will request imediate worker
64
+ shut downs (via `INT`) and shut itself down immediately. This is the
65
+ default `TERM` signal behavior.
66
+
67
+ ## HISTORY
68
+
69
+ * `v0.3.0`:
70
+ Support for ruby 1.9, qless 1.20.<br>
71
+ Added appname for logging.<br>
72
+ Minor bugfixes.
73
+
74
+ * `v0.2.0`:
75
+ Support for reloading logs and workers with `HUP` signal<br>
76
+ Cleans up PID file on startup<br>
77
+ Fixed `-c, --config` option.
78
+
79
+ * `v0.1.0`:
80
+ `qless-pool` command line interface added
81
+
82
+ ## AUTHOR
83
+
84
+ Nicholas Evans
85
+
86
+ ## COPYRIGHT
87
+
88
+ Copyright (C) 2010 by Nicholas Evans <nick@ekenosen.net>, et al.
89
+
90
+ ## SEE ALSO
91
+
92
+ qless-pool.yml(5)