thin 1.2.3-x86-mswin32
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of thin might be problematic. Click here for more details.
- data/CHANGELOG +263 -0
- data/COPYING +18 -0
- data/README +69 -0
- data/Rakefile +36 -0
- data/benchmark/abc +51 -0
- data/benchmark/benchmarker.rb +80 -0
- data/benchmark/runner +82 -0
- data/bin/thin +6 -0
- data/example/adapter.rb +32 -0
- data/example/async_app.ru +126 -0
- data/example/async_chat.ru +247 -0
- data/example/async_tailer.ru +100 -0
- data/example/config.ru +22 -0
- data/example/monit_sockets +20 -0
- data/example/monit_unixsock +20 -0
- data/example/myapp.rb +1 -0
- data/example/ramaze.ru +12 -0
- data/example/thin.god +80 -0
- data/example/thin_solaris_smf.erb +36 -0
- data/example/thin_solaris_smf.readme.txt +150 -0
- data/example/vlad.rake +64 -0
- data/ext/thin_parser/common.rl +55 -0
- data/ext/thin_parser/ext_help.h +14 -0
- data/ext/thin_parser/extconf.rb +6 -0
- data/ext/thin_parser/parser.c +452 -0
- data/ext/thin_parser/parser.h +49 -0
- data/ext/thin_parser/parser.rl +157 -0
- data/ext/thin_parser/thin.c +433 -0
- data/lib/rack/adapter/loader.rb +79 -0
- data/lib/rack/adapter/rails.rb +181 -0
- data/lib/thin.rb +46 -0
- data/lib/thin/backends/base.rb +141 -0
- data/lib/thin/backends/swiftiply_client.rb +56 -0
- data/lib/thin/backends/tcp_server.rb +29 -0
- data/lib/thin/backends/unix_server.rb +51 -0
- data/lib/thin/command.rb +53 -0
- data/lib/thin/connection.rb +222 -0
- data/lib/thin/controllers/cluster.rb +127 -0
- data/lib/thin/controllers/controller.rb +183 -0
- data/lib/thin/controllers/service.rb +75 -0
- data/lib/thin/controllers/service.sh.erb +39 -0
- data/lib/thin/daemonizing.rb +174 -0
- data/lib/thin/headers.rb +39 -0
- data/lib/thin/logging.rb +54 -0
- data/lib/thin/request.rb +153 -0
- data/lib/thin/response.rb +101 -0
- data/lib/thin/runner.rb +209 -0
- data/lib/thin/server.rb +247 -0
- data/lib/thin/stats.html.erb +216 -0
- data/lib/thin/stats.rb +52 -0
- data/lib/thin/statuses.rb +43 -0
- data/lib/thin/version.rb +32 -0
- data/lib/thin_parser.so +0 -0
- data/spec/backends/swiftiply_client_spec.rb +66 -0
- data/spec/backends/tcp_server_spec.rb +33 -0
- data/spec/backends/unix_server_spec.rb +37 -0
- data/spec/command_spec.rb +25 -0
- data/spec/configs/cluster.yml +9 -0
- data/spec/configs/single.yml +9 -0
- data/spec/connection_spec.rb +106 -0
- data/spec/controllers/cluster_spec.rb +235 -0
- data/spec/controllers/controller_spec.rb +129 -0
- data/spec/controllers/service_spec.rb +50 -0
- data/spec/daemonizing_spec.rb +192 -0
- data/spec/headers_spec.rb +40 -0
- data/spec/logging_spec.rb +46 -0
- data/spec/perf/request_perf_spec.rb +50 -0
- data/spec/perf/response_perf_spec.rb +19 -0
- data/spec/perf/server_perf_spec.rb +39 -0
- data/spec/rack/loader_spec.rb +29 -0
- data/spec/rack/rails_adapter_spec.rb +106 -0
- data/spec/rails_app/app/controllers/application.rb +10 -0
- data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
- data/spec/rails_app/app/helpers/application_helper.rb +3 -0
- data/spec/rails_app/app/views/simple/index.html.erb +15 -0
- data/spec/rails_app/config/boot.rb +109 -0
- data/spec/rails_app/config/environment.rb +64 -0
- data/spec/rails_app/config/environments/development.rb +18 -0
- data/spec/rails_app/config/environments/production.rb +19 -0
- data/spec/rails_app/config/environments/test.rb +22 -0
- data/spec/rails_app/config/initializers/inflections.rb +10 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/routes.rb +35 -0
- data/spec/rails_app/public/404.html +30 -0
- data/spec/rails_app/public/422.html +30 -0
- data/spec/rails_app/public/500.html +30 -0
- data/spec/rails_app/public/dispatch.cgi +10 -0
- data/spec/rails_app/public/dispatch.fcgi +24 -0
- data/spec/rails_app/public/dispatch.rb +10 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/images/rails.png +0 -0
- data/spec/rails_app/public/index.html +277 -0
- data/spec/rails_app/public/javascripts/application.js +2 -0
- data/spec/rails_app/public/javascripts/controls.js +963 -0
- data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
- data/spec/rails_app/public/javascripts/effects.js +1120 -0
- data/spec/rails_app/public/javascripts/prototype.js +4225 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/rails_app/script/about +3 -0
- data/spec/rails_app/script/console +3 -0
- data/spec/rails_app/script/destroy +3 -0
- data/spec/rails_app/script/generate +3 -0
- data/spec/rails_app/script/performance/benchmarker +3 -0
- data/spec/rails_app/script/performance/profiler +3 -0
- data/spec/rails_app/script/performance/request +3 -0
- data/spec/rails_app/script/plugin +3 -0
- data/spec/rails_app/script/process/inspector +3 -0
- data/spec/rails_app/script/process/reaper +3 -0
- data/spec/rails_app/script/process/spawner +3 -0
- data/spec/rails_app/script/runner +3 -0
- data/spec/rails_app/script/server +3 -0
- data/spec/request/mongrel_spec.rb +39 -0
- data/spec/request/parser_spec.rb +215 -0
- data/spec/request/persistent_spec.rb +35 -0
- data/spec/request/processing_spec.rb +45 -0
- data/spec/response_spec.rb +91 -0
- data/spec/runner_spec.rb +168 -0
- data/spec/server/builder_spec.rb +44 -0
- data/spec/server/pipelining_spec.rb +110 -0
- data/spec/server/robustness_spec.rb +34 -0
- data/spec/server/stopping_spec.rb +55 -0
- data/spec/server/swiftiply.yml +6 -0
- data/spec/server/swiftiply_spec.rb +32 -0
- data/spec/server/tcp_spec.rb +57 -0
- data/spec/server/threaded_spec.rb +27 -0
- data/spec/server/unix_socket_spec.rb +26 -0
- data/spec/server_spec.rb +96 -0
- data/spec/spec_helper.rb +219 -0
- data/tasks/announce.rake +22 -0
- data/tasks/deploy.rake +13 -0
- data/tasks/email.erb +30 -0
- data/tasks/gem.rake +74 -0
- data/tasks/rdoc.rake +25 -0
- data/tasks/site.rake +15 -0
- data/tasks/spec.rake +49 -0
- data/tasks/stats.rake +28 -0
- metadata +246 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Thin
|
4
|
+
# Error raised that will abort the process and print not backtrace.
|
5
|
+
class RunnerError < RuntimeError; end
|
6
|
+
|
7
|
+
# Raised when a mandatory option is missing to run a command.
|
8
|
+
class OptionRequired < RunnerError
|
9
|
+
def initialize(option)
|
10
|
+
super("#{option} option required")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Raised when an option is not valid.
|
15
|
+
class InvalidOption < RunnerError; end
|
16
|
+
|
17
|
+
# Build and control Thin servers.
|
18
|
+
# Hey Controller pattern is not only for web apps yo!
|
19
|
+
module Controllers
|
20
|
+
# Controls one Thin server.
|
21
|
+
# Allow to start, stop, restart and configure a single thin server.
|
22
|
+
class Controller
|
23
|
+
include Logging
|
24
|
+
|
25
|
+
# Command line options passed to the thin script
|
26
|
+
attr_accessor :options
|
27
|
+
|
28
|
+
def initialize(options)
|
29
|
+
@options = options
|
30
|
+
|
31
|
+
if @options[:socket]
|
32
|
+
@options.delete(:address)
|
33
|
+
@options.delete(:port)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def start
|
38
|
+
# Constantize backend class
|
39
|
+
@options[:backend] = eval(@options[:backend], TOPLEVEL_BINDING) if @options[:backend]
|
40
|
+
|
41
|
+
server = Server.new(@options[:socket] || @options[:address], # Server detects kind of socket
|
42
|
+
@options[:port], # Port ignored on UNIX socket
|
43
|
+
@options)
|
44
|
+
|
45
|
+
# Set options
|
46
|
+
server.pid_file = @options[:pid]
|
47
|
+
server.log_file = @options[:log]
|
48
|
+
server.timeout = @options[:timeout]
|
49
|
+
server.maximum_connections = @options[:max_conns]
|
50
|
+
server.maximum_persistent_connections = @options[:max_persistent_conns]
|
51
|
+
server.threaded = @options[:threaded]
|
52
|
+
server.no_epoll = @options[:no_epoll] if server.backend.respond_to?(:no_epoll=)
|
53
|
+
|
54
|
+
# Detach the process, after this line the current process returns
|
55
|
+
server.daemonize if @options[:daemonize]
|
56
|
+
|
57
|
+
# +config+ must be called before changing privileges since it might require superuser power.
|
58
|
+
server.config
|
59
|
+
|
60
|
+
server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
|
61
|
+
|
62
|
+
# If a Rack config file is specified we eval it inside a Rack::Builder block to create
|
63
|
+
# a Rack adapter from it. Or else we guess which adapter to use and load it.
|
64
|
+
if @options[:rackup]
|
65
|
+
server.app = load_rackup_config
|
66
|
+
else
|
67
|
+
server.app = load_adapter
|
68
|
+
end
|
69
|
+
|
70
|
+
# If a prefix is required, wrap in Rack URL mapper
|
71
|
+
server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
|
72
|
+
|
73
|
+
# If a stats URL is specified, wrap in Stats adapter
|
74
|
+
server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
|
75
|
+
|
76
|
+
# Register restart procedure which just start another process with same options,
|
77
|
+
# so that's why this is done here.
|
78
|
+
server.on_restart { Command.run(:start, @options) }
|
79
|
+
|
80
|
+
server.start
|
81
|
+
end
|
82
|
+
|
83
|
+
def stop
|
84
|
+
raise OptionRequired, :pid unless @options[:pid]
|
85
|
+
|
86
|
+
tail_log(@options[:log]) do
|
87
|
+
if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
|
88
|
+
wait_for_file :deletion, @options[:pid]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def restart
|
94
|
+
raise OptionRequired, :pid unless @options[:pid]
|
95
|
+
|
96
|
+
tail_log(@options[:log]) do
|
97
|
+
if Server.restart(@options[:pid])
|
98
|
+
wait_for_file :creation, @options[:pid]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def config
|
104
|
+
config_file = @options.delete(:config) || raise(OptionRequired, :config)
|
105
|
+
|
106
|
+
# Stringify keys
|
107
|
+
@options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
|
108
|
+
|
109
|
+
File.open(config_file, 'w') { |f| f << @options.to_yaml }
|
110
|
+
log ">> Wrote configuration to #{config_file}"
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
# Wait for a pid file to either be created or deleted.
|
115
|
+
def wait_for_file(state, file)
|
116
|
+
Timeout.timeout(@options[:timeout] || 30) do
|
117
|
+
case state
|
118
|
+
when :creation then sleep 0.1 until File.exist?(file)
|
119
|
+
when :deletion then sleep 0.1 while File.exist?(file)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Tail the log file of server +number+ during the execution of the block.
|
125
|
+
def tail_log(log_file)
|
126
|
+
if log_file
|
127
|
+
tail_thread = tail(log_file)
|
128
|
+
yield
|
129
|
+
tail_thread.kill
|
130
|
+
else
|
131
|
+
yield
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Acts like GNU tail command. Taken from Rails.
|
136
|
+
def tail(file)
|
137
|
+
cursor = File.exist?(file) ? File.size(file) : 0
|
138
|
+
last_checked = Time.now
|
139
|
+
tail_thread = Thread.new do
|
140
|
+
Thread.pass until File.exist?(file)
|
141
|
+
File.open(file, 'r') do |f|
|
142
|
+
loop do
|
143
|
+
f.seek cursor
|
144
|
+
if f.mtime > last_checked
|
145
|
+
last_checked = f.mtime
|
146
|
+
contents = f.read
|
147
|
+
cursor += contents.length
|
148
|
+
print contents
|
149
|
+
STDOUT.flush
|
150
|
+
end
|
151
|
+
sleep 0.1
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
|
156
|
+
tail_thread
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
def load_adapter
|
161
|
+
adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
|
162
|
+
log ">> Using #{adapter} adapter"
|
163
|
+
Rack::Adapter.for(adapter, @options)
|
164
|
+
rescue Rack::AdapterNotFound => e
|
165
|
+
raise InvalidOption, e.message
|
166
|
+
end
|
167
|
+
|
168
|
+
def load_rackup_config
|
169
|
+
ENV['RACK_ENV'] = @options[:environment]
|
170
|
+
case @options[:rackup]
|
171
|
+
when /\.rb$/
|
172
|
+
Kernel.load(@options[:rackup])
|
173
|
+
Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
|
174
|
+
when /\.ru$/
|
175
|
+
rackup_code = File.read(@options[:rackup])
|
176
|
+
eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, @options[:rackup])
|
177
|
+
else
|
178
|
+
raise "Invalid rackup file. please specify either a .ru or .rb file"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Thin
|
4
|
+
module Controllers
|
5
|
+
# System service controller to launch all servers which
|
6
|
+
# config files are in a directory.
|
7
|
+
class Service < Controller
|
8
|
+
INITD_PATH = '/etc/init.d/thin'
|
9
|
+
DEFAULT_CONFIG_PATH = '/etc/thin'
|
10
|
+
TEMPLATE = File.dirname(__FILE__) + '/service.sh.erb'
|
11
|
+
|
12
|
+
def initialize(options)
|
13
|
+
super
|
14
|
+
|
15
|
+
raise PlatformNotSupported, 'Running as a service only supported on Linux' unless Thin.linux?
|
16
|
+
end
|
17
|
+
|
18
|
+
def config_path
|
19
|
+
@options[:all] || DEFAULT_CONFIG_PATH
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
run :start
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop
|
27
|
+
run :stop
|
28
|
+
end
|
29
|
+
|
30
|
+
def restart
|
31
|
+
run :restart
|
32
|
+
end
|
33
|
+
|
34
|
+
def install(config_files_path=DEFAULT_CONFIG_PATH)
|
35
|
+
if File.exist?(INITD_PATH)
|
36
|
+
log ">> Thin service already installed at #{INITD_PATH}"
|
37
|
+
else
|
38
|
+
log ">> Installing thin service at #{INITD_PATH} ..."
|
39
|
+
sh "mkdir -p #{File.dirname(INITD_PATH)}"
|
40
|
+
log "writing #{INITD_PATH}"
|
41
|
+
File.open(INITD_PATH, 'w') do |f|
|
42
|
+
f << ERB.new(File.read(TEMPLATE)).result(binding)
|
43
|
+
end
|
44
|
+
sh "chmod +x #{INITD_PATH}" # Make executable
|
45
|
+
end
|
46
|
+
|
47
|
+
sh "mkdir -p #{config_files_path}"
|
48
|
+
|
49
|
+
log ''
|
50
|
+
log "To configure thin to start at system boot:"
|
51
|
+
log "on RedHat like systems:"
|
52
|
+
log " sudo /sbin/chkconfig --level 345 #{NAME} on"
|
53
|
+
log "on Debian-like systems (Ubuntu):"
|
54
|
+
log " sudo /usr/sbin/update-rc.d -f #{NAME} defaults"
|
55
|
+
log "on Gentoo:"
|
56
|
+
log " sudo rc-update add #{NAME} default"
|
57
|
+
log ''
|
58
|
+
log "Then put your config files in #{config_files_path}"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def run(command)
|
63
|
+
Dir[config_path + '/*'].each do |config|
|
64
|
+
log "[#{command}] #{config} ..."
|
65
|
+
Command.run(command, :config => config, :daemonize => true)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def sh(cmd)
|
70
|
+
log cmd
|
71
|
+
system(cmd)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
### BEGIN INIT INFO
|
3
|
+
# Provides: thin
|
4
|
+
# Required-Start: $local_fs $remote_fs
|
5
|
+
# Required-Stop: $local_fs $remote_fs
|
6
|
+
# Default-Start: 2 3 4 5
|
7
|
+
# Default-Stop: S 0 1 6
|
8
|
+
# Short-Description: thin initscript
|
9
|
+
# Description: thin
|
10
|
+
### END INIT INFO
|
11
|
+
|
12
|
+
# Original author: Forrest Robertson
|
13
|
+
|
14
|
+
# Do NOT "set -e"
|
15
|
+
|
16
|
+
DAEMON=<%= Command.script %>
|
17
|
+
SCRIPT_NAME=<%= INITD_PATH %>
|
18
|
+
CONFIG_PATH=<%= config_files_path %>
|
19
|
+
|
20
|
+
# Exit if the package is not installed
|
21
|
+
[ -x "$DAEMON" ] || exit 0
|
22
|
+
|
23
|
+
case "$1" in
|
24
|
+
start)
|
25
|
+
$DAEMON start --all $CONFIG_PATH
|
26
|
+
;;
|
27
|
+
stop)
|
28
|
+
$DAEMON stop --all $CONFIG_PATH
|
29
|
+
;;
|
30
|
+
restart)
|
31
|
+
$DAEMON restart --all $CONFIG_PATH
|
32
|
+
;;
|
33
|
+
*)
|
34
|
+
echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
|
35
|
+
exit 3
|
36
|
+
;;
|
37
|
+
esac
|
38
|
+
|
39
|
+
:
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'etc'
|
2
|
+
require 'daemons' unless Thin.win?
|
3
|
+
|
4
|
+
module Process
|
5
|
+
# Returns +true+ the process identied by +pid+ is running.
|
6
|
+
def running?(pid)
|
7
|
+
Process.getpgid(pid) != -1
|
8
|
+
rescue Errno::ESRCH
|
9
|
+
false
|
10
|
+
end
|
11
|
+
module_function :running?
|
12
|
+
end
|
13
|
+
|
14
|
+
module Thin
|
15
|
+
# Raised when the pid file already exist starting as a daemon.
|
16
|
+
class PidFileExist < RuntimeError; end
|
17
|
+
|
18
|
+
# Module included in classes that can be turned into a daemon.
|
19
|
+
# Handle stuff like:
|
20
|
+
# * storing the PID in a file
|
21
|
+
# * redirecting output to the log file
|
22
|
+
# * changing processs privileges
|
23
|
+
# * killing the process gracefully
|
24
|
+
module Daemonizable
|
25
|
+
attr_accessor :pid_file, :log_file
|
26
|
+
|
27
|
+
def self.included(base)
|
28
|
+
base.extend ClassMethods
|
29
|
+
end
|
30
|
+
|
31
|
+
def pid
|
32
|
+
File.exist?(pid_file) ? open(pid_file).read.to_i : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Turns the current script into a daemon process that detaches from the console.
|
36
|
+
def daemonize
|
37
|
+
raise PlatformNotSupported, 'Daemonizing is not supported on Windows' if Thin.win?
|
38
|
+
raise ArgumentError, 'You must specify a pid_file to daemonize' unless @pid_file
|
39
|
+
|
40
|
+
remove_stale_pid_file
|
41
|
+
|
42
|
+
pwd = Dir.pwd # Current directory is changed during daemonization, so store it
|
43
|
+
|
44
|
+
Daemonize.daemonize(File.expand_path(@log_file), name)
|
45
|
+
|
46
|
+
Dir.chdir(pwd)
|
47
|
+
|
48
|
+
write_pid_file
|
49
|
+
|
50
|
+
trap('HUP') { restart }
|
51
|
+
at_exit do
|
52
|
+
log ">> Exiting!"
|
53
|
+
remove_pid_file
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Change privileges of the process
|
58
|
+
# to the specified user and group.
|
59
|
+
def change_privilege(user, group=user)
|
60
|
+
log ">> Changing process privilege to #{user}:#{group}"
|
61
|
+
|
62
|
+
uid, gid = Process.euid, Process.egid
|
63
|
+
target_uid = Etc.getpwnam(user).uid
|
64
|
+
target_gid = Etc.getgrnam(group).gid
|
65
|
+
|
66
|
+
if uid != target_uid || gid != target_gid
|
67
|
+
# Change process ownership
|
68
|
+
Process.initgroups(user, target_gid)
|
69
|
+
Process::GID.change_privilege(target_gid)
|
70
|
+
Process::UID.change_privilege(target_uid)
|
71
|
+
end
|
72
|
+
rescue Errno::EPERM => e
|
73
|
+
log "Couldn't change user and group to #{user}:#{group}: #{e}"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Register a proc to be called to restart the server.
|
77
|
+
def on_restart(&block)
|
78
|
+
@on_restart = block
|
79
|
+
end
|
80
|
+
|
81
|
+
# Restart the server.
|
82
|
+
def restart
|
83
|
+
raise ArgumentError, "Can't restart, no 'on_restart' proc specified" unless @on_restart
|
84
|
+
log '>> Restarting ...'
|
85
|
+
stop
|
86
|
+
remove_pid_file
|
87
|
+
@on_restart.call
|
88
|
+
exit!
|
89
|
+
end
|
90
|
+
|
91
|
+
module ClassMethods
|
92
|
+
# Send a QUIT or INT (if timeout is +0+) signal the process which
|
93
|
+
# PID is stored in +pid_file+.
|
94
|
+
# If the process is still running after +timeout+, KILL signal is
|
95
|
+
# sent.
|
96
|
+
def kill(pid_file, timeout=60)
|
97
|
+
if timeout == 0
|
98
|
+
send_signal('INT', pid_file, timeout)
|
99
|
+
else
|
100
|
+
send_signal('QUIT', pid_file, timeout)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Restart the server by sending HUP signal.
|
105
|
+
def restart(pid_file)
|
106
|
+
send_signal('HUP', pid_file)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Send a +signal+ to the process which PID is stored in +pid_file+.
|
110
|
+
def send_signal(signal, pid_file, timeout=60)
|
111
|
+
if pid = read_pid_file(pid_file)
|
112
|
+
Logging.log "Sending #{signal} signal to process #{pid} ... "
|
113
|
+
Process.kill(signal, pid)
|
114
|
+
Timeout.timeout(timeout) do
|
115
|
+
sleep 0.1 while Process.running?(pid)
|
116
|
+
end
|
117
|
+
else
|
118
|
+
Logging.log "Can't stop process, no PID found in #{pid_file}"
|
119
|
+
end
|
120
|
+
rescue Timeout::Error
|
121
|
+
Logging.log "Timeout!"
|
122
|
+
force_kill pid_file
|
123
|
+
rescue Interrupt
|
124
|
+
force_kill pid_file
|
125
|
+
rescue Errno::ESRCH # No such process
|
126
|
+
Logging.log "process not found!"
|
127
|
+
force_kill pid_file
|
128
|
+
end
|
129
|
+
|
130
|
+
def force_kill(pid_file)
|
131
|
+
if pid = read_pid_file(pid_file)
|
132
|
+
Logging.log "Sending KILL signal to process #{pid} ... "
|
133
|
+
Process.kill("KILL", pid)
|
134
|
+
File.delete(pid_file) if File.exist?(pid_file)
|
135
|
+
else
|
136
|
+
Logging.log "Can't stop process, no PID found in #{pid_file}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def read_pid_file(file)
|
141
|
+
if File.file?(file) && pid = File.read(file)
|
142
|
+
pid.to_i
|
143
|
+
else
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
protected
|
150
|
+
def remove_pid_file
|
151
|
+
File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
|
152
|
+
end
|
153
|
+
|
154
|
+
def write_pid_file
|
155
|
+
log ">> Writing PID to #{@pid_file}"
|
156
|
+
FileUtils.mkdir_p File.dirname(@pid_file)
|
157
|
+
open(@pid_file,"w") { |f| f.write(Process.pid) }
|
158
|
+
File.chmod(0644, @pid_file)
|
159
|
+
end
|
160
|
+
|
161
|
+
# If PID file is stale, remove it.
|
162
|
+
def remove_stale_pid_file
|
163
|
+
if File.exist?(@pid_file)
|
164
|
+
if pid && Process.running?(pid)
|
165
|
+
raise PidFileExist, "#{@pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
|
166
|
+
"Stop the process or delete #{@pid_file}."
|
167
|
+
else
|
168
|
+
log ">> Deleting stale PID file #{@pid_file}"
|
169
|
+
remove_pid_file
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|