thin 1.8.2 → 2.0.0.pre

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.

Files changed (108) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG +29 -116
  3. data/Gemfile +8 -0
  4. data/README.md +44 -78
  5. data/Rakefile +28 -18
  6. data/bin/thin +4 -4
  7. data/examples/async.ru +21 -0
  8. data/examples/thin.conf.rb +39 -0
  9. data/lib/thin/async.rb +108 -0
  10. data/lib/thin/backends/prefork.rb +44 -0
  11. data/lib/thin/backends/single_process.rb +28 -0
  12. data/lib/thin/chunked_body.rb +28 -0
  13. data/lib/thin/configurator.rb +118 -0
  14. data/lib/thin/connection.rb +246 -172
  15. data/lib/thin/listener.rb +114 -0
  16. data/lib/thin/request.rb +94 -76
  17. data/lib/thin/response.rb +112 -45
  18. data/lib/thin/runner.rb +134 -197
  19. data/lib/thin/server.rb +203 -252
  20. data/lib/thin/system.rb +49 -0
  21. data/lib/thin/version.rb +12 -27
  22. data/lib/thin.rb +2 -44
  23. data/man/index.txt +3 -0
  24. data/man/thin-conf.5.ronn +121 -0
  25. data/man/thin.1.ronn +105 -0
  26. data/site/.gitignore +2 -0
  27. data/site/README.md +21 -0
  28. data/site/Rakefile +20 -0
  29. data/site/config.ru +4 -0
  30. data/site/public/images/grid.png +0 -0
  31. data/site/public/javascripts/dd_belatedpng.js +13 -0
  32. data/site/public/javascripts/modernizr-1.6.min.js +30 -0
  33. data/site/public/man/thin-conf.5.html +220 -0
  34. data/site/public/man/thin.1.html +177 -0
  35. data/site/site/assets/javascripts/main.coffee +2 -0
  36. data/site/site/assets/stylesheets/_config.scss +55 -0
  37. data/site/site/assets/stylesheets/main.scss +24 -0
  38. data/site/site/helpers.rb +17 -0
  39. data/site/site/layouts/base.erb +55 -0
  40. data/site/site/layouts/default.erb +17 -0
  41. data/site/site/pages/about.md +5 -0
  42. data/site/site/pages/index.erb +10 -0
  43. data/site/site/partials/.gitkeep +0 -0
  44. data/test/fixtures/big.txt +1 -0
  45. data/test/fixtures/small.txt +1 -0
  46. data/test/fixtures/thin.conf.rb +15 -0
  47. data/test/integration/async_test.rb +35 -0
  48. data/test/integration/big_request_test.rb +30 -0
  49. data/test/integration/config.ru +57 -0
  50. data/test/integration/daemonize_test.rb +26 -0
  51. data/test/integration/env_test.rb +44 -0
  52. data/test/integration/error_test.rb +37 -0
  53. data/test/integration/file_sending_test.rb +24 -0
  54. data/test/integration/keep_alive_test.rb +35 -0
  55. data/test/integration/robustness_test.rb +37 -0
  56. data/test/integration/single_process_test.rb +15 -0
  57. data/test/integration/socket_family_test.rb +38 -0
  58. data/test/integration/worker_test.rb +22 -0
  59. data/test/test_helper.rb +195 -0
  60. data/test/unit/configurator_test.rb +43 -0
  61. data/test/unit/connection_test.rb +94 -0
  62. data/test/unit/listener_test.rb +74 -0
  63. data/test/unit/request_test.rb +74 -0
  64. data/test/unit/response_test.rb +90 -0
  65. data/test/unit/server_test.rb +29 -0
  66. data/test/unit/system_test.rb +17 -0
  67. data/thin.gemspec +26 -0
  68. data/v2.todo +21 -0
  69. metadata +138 -93
  70. checksums.yaml +0 -7
  71. data/example/adapter.rb +0 -32
  72. data/example/async_app.ru +0 -126
  73. data/example/async_chat.ru +0 -247
  74. data/example/async_tailer.ru +0 -100
  75. data/example/config.ru +0 -22
  76. data/example/monit_sockets +0 -20
  77. data/example/monit_unixsock +0 -20
  78. data/example/myapp.rb +0 -1
  79. data/example/ramaze.ru +0 -12
  80. data/example/thin.god +0 -80
  81. data/example/thin_solaris_smf.erb +0 -36
  82. data/example/thin_solaris_smf.readme.txt +0 -150
  83. data/example/vlad.rake +0 -72
  84. data/ext/thin_parser/common.rl +0 -59
  85. data/ext/thin_parser/ext_help.h +0 -14
  86. data/ext/thin_parser/extconf.rb +0 -6
  87. data/ext/thin_parser/parser.c +0 -1447
  88. data/ext/thin_parser/parser.h +0 -49
  89. data/ext/thin_parser/parser.rl +0 -152
  90. data/ext/thin_parser/thin.c +0 -435
  91. data/lib/rack/adapter/loader.rb +0 -75
  92. data/lib/rack/adapter/rails.rb +0 -178
  93. data/lib/rack/handler/thin.rb +0 -38
  94. data/lib/thin/backends/base.rb +0 -169
  95. data/lib/thin/backends/swiftiply_client.rb +0 -66
  96. data/lib/thin/backends/tcp_server.rb +0 -34
  97. data/lib/thin/backends/unix_server.rb +0 -56
  98. data/lib/thin/command.rb +0 -53
  99. data/lib/thin/controllers/cluster.rb +0 -178
  100. data/lib/thin/controllers/controller.rb +0 -189
  101. data/lib/thin/controllers/service.rb +0 -76
  102. data/lib/thin/controllers/service.sh.erb +0 -39
  103. data/lib/thin/daemonizing.rb +0 -199
  104. data/lib/thin/headers.rb +0 -47
  105. data/lib/thin/logging.rb +0 -174
  106. data/lib/thin/stats.html.erb +0 -216
  107. data/lib/thin/stats.rb +0 -52
  108. data/lib/thin/statuses.rb +0 -48
@@ -1,178 +0,0 @@
1
- require 'socket'
2
-
3
- module Thin
4
- # An exception class to handle the event that server didn't start on time
5
- class RestartTimeout < RuntimeError; end
6
-
7
- module Controllers
8
- # Control a set of servers.
9
- # * Generate start and stop commands and run them.
10
- # * Inject the port or socket number in the pid and log filenames.
11
- # Servers are started throught the +thin+ command-line script.
12
- class Cluster < Controller
13
- # Cluster only options that should not be passed in the command sent
14
- # to the indiviual servers.
15
- CLUSTER_OPTIONS = [:servers, :only, :onebyone, :wait]
16
-
17
- # Maximum wait time for the server to be restarted
18
- DEFAULT_WAIT_TIME = 30 # seconds
19
-
20
- # Create a new cluster of servers launched using +options+.
21
- def initialize(options)
22
- super
23
- # Cluster can only contain daemonized servers
24
- @options.merge!(:daemonize => true)
25
- end
26
-
27
- def first_port; @options[:port] end
28
- def address; @options[:address] end
29
- def socket; @options[:socket] end
30
- def pid_file; @options[:pid] end
31
- def log_file; @options[:log] end
32
- def size; @options[:servers] end
33
- def only; @options[:only] end
34
- def onebyone; @options[:onebyone] end
35
- def wait; @options[:wait] end
36
-
37
- def swiftiply?
38
- @options.has_key?(:swiftiply)
39
- end
40
-
41
- # Start the servers
42
- def start
43
- with_each_server { |n| start_server n }
44
- end
45
-
46
- # Start a single server
47
- def start_server(number)
48
- log_info "Starting server on #{server_id(number)} ... "
49
-
50
- run :start, number
51
- end
52
-
53
- # Stop the servers
54
- def stop
55
- with_each_server { |n| stop_server n }
56
- end
57
-
58
- # Stop a single server
59
- def stop_server(number)
60
- log_info "Stopping server on #{server_id(number)} ... "
61
-
62
- run :stop, number
63
- end
64
-
65
- # Stop and start the servers.
66
- def restart
67
- unless onebyone
68
- # Let's do a normal restart by defaults
69
- stop
70
- sleep 0.1 # Let's breath a bit shall we ?
71
- start
72
- else
73
- with_each_server do |n|
74
- stop_server(n)
75
- sleep 0.1 # Let's breath a bit shall we ?
76
- start_server(n)
77
- wait_until_server_started(n)
78
- end
79
- end
80
- end
81
-
82
- def test_socket(number)
83
- if socket
84
- UNIXSocket.new(socket_for(number))
85
- else
86
- TCPSocket.new(address, number)
87
- end
88
- rescue
89
- nil
90
- end
91
-
92
- # Make sure the server is running before moving on to the next one.
93
- def wait_until_server_started(number)
94
- log_info "Waiting for server to start ..."
95
- STDOUT.flush # Need this to make sure user got the message
96
-
97
- tries = 0
98
- loop do
99
- if test_socket = test_socket(number)
100
- test_socket.close
101
- break
102
- elsif tries < wait
103
- sleep 1
104
- tries += 1
105
- else
106
- raise RestartTimeout, "The server didn't start in time. Please look at server's log file " +
107
- "for more information, or set the value of 'wait' in your config " +
108
- "file to be higher (defaults: 30)."
109
- end
110
- end
111
- end
112
-
113
- def server_id(number)
114
- if socket
115
- socket_for(number)
116
- elsif swiftiply?
117
- [address, first_port, number].join(':')
118
- else
119
- [address, number].join(':')
120
- end
121
- end
122
-
123
- def log_file_for(number)
124
- include_server_number log_file, number
125
- end
126
-
127
- def pid_file_for(number)
128
- include_server_number pid_file, number
129
- end
130
-
131
- def socket_for(number)
132
- include_server_number socket, number
133
- end
134
-
135
- def pid_for(number)
136
- File.read(pid_file_for(number)).chomp.to_i
137
- end
138
-
139
- private
140
- # Send the command to the +thin+ script
141
- def run(cmd, number)
142
- cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
143
- cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
144
- if socket
145
- cmd_options.merge!(:socket => socket_for(number))
146
- elsif swiftiply?
147
- cmd_options.merge!(:port => first_port)
148
- else
149
- cmd_options.merge!(:port => number)
150
- end
151
- Command.run(cmd, cmd_options)
152
- end
153
-
154
- def with_each_server
155
- if only
156
- if first_port && only < 80
157
- # interpret +only+ as a sequence number
158
- yield first_port + only
159
- else
160
- # interpret +only+ as an absolute port number
161
- yield only
162
- end
163
- elsif socket || swiftiply?
164
- size.times { |n| yield n }
165
- else
166
- size.times { |n| yield first_port + n }
167
- end
168
- end
169
-
170
- # Add the server port or number in the filename
171
- # so each instance get its own file
172
- def include_server_number(path, number)
173
- ext = File.extname(path)
174
- path.gsub(/#{ext}$/, ".#{number}#{ext}")
175
- end
176
- end
177
- end
178
- end
@@ -1,189 +0,0 @@
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
- server.threadpool_size = @options[:threadpool_size] if server.threaded?
54
-
55
- # ssl support
56
- if @options[:ssl]
57
- server.ssl = true
58
- server.ssl_options = { :private_key_file => @options[:ssl_key_file], :cert_chain_file => @options[:ssl_cert_file], :verify_peer => !@options[:ssl_disable_verify], :ssl_version => @options[:ssl_version], :cipher_list => @options[:ssl_cipher_list]}
59
- end
60
-
61
- # Detach the process, after this line the current process returns
62
- server.daemonize if @options[:daemonize]
63
-
64
- # +config+ must be called before changing privileges since it might require superuser power.
65
- server.config
66
-
67
- server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
68
-
69
- # If a Rack config file is specified we eval it inside a Rack::Builder block to create
70
- # a Rack adapter from it. Or else we guess which adapter to use and load it.
71
- if @options[:rackup]
72
- server.app = load_rackup_config
73
- else
74
- server.app = load_adapter
75
- end
76
-
77
- # If a prefix is required, wrap in Rack URL mapper
78
- server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
79
-
80
- # If a stats URL is specified, wrap in Stats adapter
81
- server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
82
-
83
- # Register restart procedure which just start another process with same options,
84
- # so that's why this is done here.
85
- server.on_restart { Command.run(:start, @options) }
86
-
87
- server.start
88
- end
89
-
90
- def stop
91
- raise OptionRequired, :pid unless @options[:pid]
92
-
93
- tail_log(@options[:log]) do
94
- if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
95
- wait_for_file :deletion, @options[:pid]
96
- end
97
- end
98
- end
99
-
100
- def restart
101
- raise OptionRequired, :pid unless @options[:pid]
102
-
103
- tail_log(@options[:log]) do
104
- if Server.restart(@options[:pid])
105
- wait_for_file :creation, @options[:pid]
106
- end
107
- end
108
- end
109
-
110
- def config
111
- config_file = @options.delete(:config) || raise(OptionRequired, :config)
112
-
113
- # Stringify keys
114
- @options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
115
-
116
- File.open(config_file, 'w') { |f| f << @options.to_yaml }
117
- log_info "Wrote configuration to #{config_file}"
118
- end
119
-
120
- protected
121
- # Wait for a pid file to either be created or deleted.
122
- def wait_for_file(state, file)
123
- Timeout.timeout(@options[:timeout] || 30) do
124
- case state
125
- when :creation then sleep 0.1 until File.exist?(file)
126
- when :deletion then sleep 0.1 while File.exist?(file)
127
- end
128
- end
129
- end
130
-
131
- # Tail the log file of server +number+ during the execution of the block.
132
- def tail_log(log_file)
133
- if log_file
134
- tail_thread = tail(log_file)
135
- yield
136
- tail_thread.kill
137
- else
138
- yield
139
- end
140
- end
141
-
142
- # Acts like GNU tail command. Taken from Rails.
143
- def tail(file)
144
- cursor = File.exist?(file) ? File.size(file) : 0
145
- last_checked = Time.now
146
- tail_thread = Thread.new do
147
- Thread.pass until File.exist?(file)
148
- File.open(file, 'r') do |f|
149
- loop do
150
- f.seek cursor
151
- if f.mtime > last_checked
152
- last_checked = f.mtime
153
- contents = f.read
154
- cursor += contents.length
155
- print contents
156
- STDOUT.flush
157
- end
158
- sleep 0.1
159
- end
160
- end
161
- end
162
- sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
163
- tail_thread
164
- end
165
-
166
- private
167
- def load_adapter
168
- adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
169
- log_info "Using #{adapter} adapter"
170
- Rack::Adapter.for(adapter, @options)
171
- rescue Rack::AdapterNotFound => e
172
- raise InvalidOption, e.message
173
- end
174
-
175
- def load_rackup_config
176
- ENV['RACK_ENV'] = @options[:environment]
177
- case @options[:rackup]
178
- when /\.rb$/
179
- Kernel.load(@options[:rackup])
180
- Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
181
- when /\.ru$/
182
- Rack::Adapter.load(@options[:rackup])
183
- else
184
- raise "Invalid rackup file. please specify either a .ru or .rb file"
185
- end
186
- end
187
- end
188
- end
189
- end
@@ -1,76 +0,0 @@
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 = File.directory?('/etc/rc.d') ? '/etc/rc.d/thin' : '/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_info "Thin service already installed at #{INITD_PATH}"
37
- else
38
- log_info "Installing thin service at #{INITD_PATH} ..."
39
- sh "mkdir -p #{File.dirname(INITD_PATH)}"
40
- log_info "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_info ''
50
- log_info "To configure thin to start at system boot:"
51
- log_info "on RedHat like systems:"
52
- log_info " sudo /sbin/chkconfig --level 345 #{NAME} on"
53
- log_info "on Debian-like systems (Ubuntu):"
54
- log_info " sudo /usr/sbin/update-rc.d -f #{NAME} defaults"
55
- log_info "on Gentoo:"
56
- log_info " sudo rc-update add #{NAME} default"
57
- log_info ''
58
- log_info "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
- next if config.end_with?("~")
65
- log_info "[#{command}] #{config} ..."
66
- Command.run(command, :config => config, :daemonize => true)
67
- end
68
- end
69
-
70
- def sh(cmd)
71
- log_info cmd
72
- system(cmd)
73
- end
74
- end
75
- end
76
- end
@@ -1,39 +0,0 @@
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
- :
@@ -1,199 +0,0 @@
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::EPERM
9
- true
10
- rescue Errno::ESRCH
11
- false
12
- end
13
- module_function :running?
14
- end
15
-
16
- module Thin
17
- # Raised when the pid file already exist starting as a daemon.
18
- class PidFileExist < RuntimeError; end
19
- class PidFileNotFound < RuntimeError; end
20
-
21
- # Module included in classes that can be turned into a daemon.
22
- # Handle stuff like:
23
- # * storing the PID in a file
24
- # * redirecting output to the log file
25
- # * changing process privileges
26
- # * killing the process gracefully
27
- module Daemonizable
28
- attr_accessor :pid_file, :log_file
29
-
30
- def self.included(base)
31
- base.extend ClassMethods
32
- end
33
-
34
- def pid
35
- File.exist?(pid_file) && !File.zero?(pid_file) ? open(pid_file).read.to_i : nil
36
- end
37
-
38
- def kill(timeout = 60)
39
- if File.exist?(@pid_file)
40
- self.class.kill(@pid_file, timeout)
41
- end
42
- end
43
-
44
- # Turns the current script into a daemon process that detaches from the console.
45
- def daemonize
46
- raise PlatformNotSupported, 'Daemonizing is not supported on Windows' if Thin.win?
47
- raise ArgumentError, 'You must specify a pid_file to daemonize' unless @pid_file
48
-
49
- remove_stale_pid_file
50
-
51
- pwd = Dir.pwd # Current directory is changed during daemonization, so store it
52
-
53
- # HACK we need to create the directory before daemonization to prevent a bug under 1.9
54
- # ignoring all signals when the directory is created after daemonization.
55
- FileUtils.mkdir_p File.dirname(@pid_file)
56
- FileUtils.mkdir_p File.dirname(@log_file)
57
-
58
- Daemonize.daemonize(File.expand_path(@log_file), name)
59
-
60
- Dir.chdir(pwd)
61
-
62
- write_pid_file
63
-
64
- at_exit do
65
- log_info "Exiting!"
66
- remove_pid_file
67
- end
68
- end
69
-
70
- # Change privileges of the process
71
- # to the specified user and group.
72
- def change_privilege(user, group=user)
73
- log_info "Changing process privilege to #{user}:#{group}"
74
-
75
- uid, gid = Process.euid, Process.egid
76
- target_uid = Etc.getpwnam(user).uid
77
- target_gid = Etc.getgrnam(group).gid
78
-
79
- if uid != target_uid || gid != target_gid
80
- # Change PID file ownership
81
- File.chown(target_uid, target_gid, @pid_file) if File.exist?(@pid_file)
82
-
83
- # Change process ownership
84
- Process.initgroups(user, target_gid)
85
- Process::GID.change_privilege(target_gid)
86
- Process::UID.change_privilege(target_uid)
87
-
88
- # Correct environment variables
89
- ENV.store('USER', user)
90
- ENV.store('HOME', File.expand_path("~#{user}"))
91
- end
92
- rescue Errno::EPERM => e
93
- log_info "Couldn't change user and group to #{user}:#{group}: #{e}"
94
- end
95
-
96
- # Register a proc to be called to restart the server.
97
- def on_restart(&block)
98
- @on_restart = block
99
- end
100
-
101
- # Restart the server.
102
- def restart
103
- if @on_restart
104
- log_info 'Restarting ...'
105
- stop
106
- remove_pid_file
107
- @on_restart.call
108
- EM.next_tick { exit! }
109
- end
110
- end
111
-
112
- module ClassMethods
113
- # Send a QUIT or INT (if timeout is +0+) signal the process which
114
- # PID is stored in +pid_file+.
115
- # If the process is still running after +timeout+, KILL signal is
116
- # sent.
117
- def kill(pid_file, timeout=60)
118
- if timeout == 0
119
- send_signal('INT', pid_file, timeout)
120
- else
121
- send_signal('QUIT', pid_file, timeout)
122
- end
123
- end
124
-
125
- # Restart the server by sending HUP signal.
126
- def restart(pid_file)
127
- send_signal('HUP', pid_file)
128
- end
129
-
130
- def monotonic_time
131
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
132
- end
133
-
134
- # Send a +signal+ to the process which PID is stored in +pid_file+.
135
- def send_signal(signal, pid_file, timeout=60)
136
- if pid = read_pid_file(pid_file)
137
- Logging.log_info "Sending #{signal} signal to process #{pid} ... "
138
-
139
- Process.kill(signal, pid)
140
-
141
- # This loop seems kind of racy to me...
142
- started_at = monotonic_time
143
- while Process.running?(pid)
144
- sleep 0.1
145
- raise Timeout::Error if (monotonic_time - started_at) > timeout
146
- end
147
- else
148
- raise PidFileNotFound, "Can't stop process, no PID found in #{pid_file}"
149
- end
150
- rescue Timeout::Error
151
- Logging.log_info "Timeout!"
152
- force_kill(pid, pid_file)
153
- rescue Interrupt
154
- force_kill(pid, pid_file)
155
- rescue Errno::ESRCH # No such process
156
- Logging.log_info "process not found!"
157
- force_kill(pid, pid_file)
158
- end
159
-
160
- def force_kill(pid, pid_file)
161
- Logging.log_info "Sending KILL signal to process #{pid} ... "
162
- Process.kill("KILL", pid)
163
- File.delete(pid_file) if File.exist?(pid_file)
164
- end
165
-
166
- def read_pid_file(file)
167
- if File.file?(file) && pid = File.read(file)
168
- pid.to_i
169
- else
170
- nil
171
- end
172
- end
173
- end
174
-
175
- protected
176
- def remove_pid_file
177
- File.delete(@pid_file) if @pid_file && File.exist?(@pid_file)
178
- end
179
-
180
- def write_pid_file
181
- log_info "Writing PID to #{@pid_file}"
182
- open(@pid_file,"w") { |f| f.write(Process.pid) }
183
- File.chmod(0644, @pid_file)
184
- end
185
-
186
- # If PID file is stale, remove it.
187
- def remove_stale_pid_file
188
- if File.exist?(@pid_file)
189
- if pid && Process.running?(pid)
190
- raise PidFileExist, "#{@pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
191
- "Stop the process or delete #{@pid_file}."
192
- else
193
- log_info "Deleting stale PID file #{@pid_file}"
194
- remove_pid_file
195
- end
196
- end
197
- end
198
- end
199
- end