thin 0.8.2 → 1.0.0
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 +15 -0
- data/README +1 -1
- data/example/adapter.rb +2 -5
- data/example/config.ru +2 -5
- data/example/vlad.rake +6 -3
- data/lib/rack/adapter/loader.rb +27 -23
- data/lib/rack/adapter/rails.rb +9 -3
- data/lib/thin/backends/base.rb +13 -3
- data/lib/thin/command.rb +7 -5
- data/lib/thin/connection.rb +68 -28
- data/lib/thin/controllers/controller.rb +2 -1
- data/lib/thin/daemonizing.rb +25 -20
- data/lib/thin/headers.rb +8 -0
- data/lib/thin/logging.rb +28 -23
- data/lib/thin/request.rb +32 -32
- data/lib/thin/response.rb +22 -19
- data/lib/thin/runner.rb +14 -7
- data/lib/thin/server.rb +6 -2
- data/lib/thin/stats.rb +1 -4
- data/lib/thin/version.rb +4 -4
- data/lib/thin.rb +1 -0
- data/spec/backends/tcp_server_spec.rb +11 -0
- data/spec/command_spec.rb +7 -1
- data/spec/daemonizing_spec.rb +17 -0
- data/spec/headers_spec.rb +11 -0
- data/spec/logging_spec.rb +6 -2
- data/spec/rack/rails_adapter_spec.rb +10 -8
- data/spec/request/parser_spec.rb +1 -1
- data/spec/response_spec.rb +7 -0
- data/spec/runner_spec.rb +22 -3
- data/spec/server/pipelining_spec.rb +1 -1
- data/spec/server/robustness_spec.rb +1 -1
- data/spec/server/stopping_spec.rb +1 -1
- data/spec/server/swiftiply_spec.rb +1 -1
- data/spec/server/tcp_spec.rb +18 -7
- data/spec/server/threaded_spec.rb +1 -1
- data/spec/server/unix_socket_spec.rb +1 -1
- data/spec/spec_helper.rb +10 -0
- data/tasks/announce.rake +1 -1
- data/tasks/deploy.rake +2 -2
- data/tasks/email.erb +0 -4
- data/tasks/gem.rake +2 -2
- metadata +6 -4
- data/lib/thin_parser.bundle +0 -0
data/CHANGELOG
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
== 1.0.0 That's What She Said release
|
2
|
+
* Fixed vlad.rake to allow TCP or socket [hellekin]
|
3
|
+
* Updated Mack adapter to handle both <0.8.0 and >0.8.0 [Mark Bates]
|
4
|
+
* rails rack adapter uses File.readable_real? so it recognizes ACL permissions [Ricardo Chimal]
|
5
|
+
* Log a warning if Rack application returns nil body [Michael S. Klishin]
|
6
|
+
* Handle nil and Time header values correctly [#76 state:resolved] [tmm1]
|
7
|
+
* Add Content-Length header to response automatically when possible [#74 state:resolved] [dkubb]
|
8
|
+
* Runner now remembers -r, -D and -V parameters so that clustered servers inherit those and
|
9
|
+
`restart` keep your parameters.
|
10
|
+
* Make Set-Cookie header, in Rails adapter, compatible with current Rack spec [Pedro Belo]
|
11
|
+
[#73, state:resolved]
|
12
|
+
* Add --no-epoll option to disable epoll usage on Linux [#61 state:resolved]
|
13
|
+
* Add --force (-f) option to force stopping of a daemonized server [#72 state:resolved]
|
14
|
+
* Update halycon adapter loader [mtodd]
|
15
|
+
|
1
16
|
== 0.8.2 Double Margarita release
|
2
17
|
* Require EventMachine 0.12.0
|
3
18
|
* [bug] Fix timeout handling when running command
|
data/README
CHANGED
data/example/adapter.rb
CHANGED
@@ -8,10 +8,7 @@ class SimpleAdapter
|
|
8
8
|
body = ["hello!"]
|
9
9
|
[
|
10
10
|
200,
|
11
|
-
{
|
12
|
-
'Content-Type' => 'text/plain',
|
13
|
-
'Content-Length' => body.join.size.to_s,
|
14
|
-
},
|
11
|
+
{ 'Content-Type' => 'text/plain' },
|
15
12
|
body
|
16
13
|
]
|
17
14
|
end
|
@@ -31,5 +28,5 @@ end
|
|
31
28
|
#
|
32
29
|
# app = Rack::URLMap.new('/test' => SimpleAdapter.new,
|
33
30
|
# '/files' => Rack::File.new('.'))
|
34
|
-
# Thin::Server.
|
31
|
+
# Thin::Server.start('0.0.0.0', 3000, app)
|
35
32
|
#
|
data/example/config.ru
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Run with: rackup -s thin
|
2
2
|
# then browse to http://localhost:9292
|
3
|
-
# Or with: thin start -
|
3
|
+
# Or with: thin start -R config.ru
|
4
4
|
# then browse to http://localhost:3000
|
5
5
|
#
|
6
6
|
# Check Rack::Builder doc for more details on this file format:
|
@@ -15,10 +15,7 @@ app = proc do |env|
|
|
15
15
|
|
16
16
|
[
|
17
17
|
200, # Status code
|
18
|
-
{
|
19
|
-
'Content-Type' => 'text/html', # Reponse headers
|
20
|
-
'Content-Length' => body.join.size.to_s
|
21
|
-
},
|
18
|
+
{ 'Content-Type' => 'text/html' }, # Reponse headers
|
22
19
|
body # Body of the response
|
23
20
|
]
|
24
21
|
end
|
data/example/vlad.rake
CHANGED
@@ -15,7 +15,7 @@ namespace :vlad do
|
|
15
15
|
set :thin_log_file, nil
|
16
16
|
set :thin_pid_file, nil
|
17
17
|
set :thin_port, nil
|
18
|
-
set :thin_socket,
|
18
|
+
set :thin_socket, nil
|
19
19
|
set :thin_prefix, nil
|
20
20
|
set :thin_servers, 2
|
21
21
|
set :thin_user, nil
|
@@ -24,10 +24,13 @@ namespace :vlad do
|
|
24
24
|
configuration is set via the thin_* variables.".cleanup
|
25
25
|
|
26
26
|
remote_task :setup_app, :roles => :app do
|
27
|
+
|
28
|
+
raise(ArgumentError, "Please provide either thin_socket or thin_port") if thin_port.nil? && thin_socket.nil?
|
29
|
+
|
27
30
|
cmd = [
|
28
31
|
"#{thin_command} config",
|
29
32
|
"-s #{thin_servers}",
|
30
|
-
"-S #{thin_socket}",
|
33
|
+
("-S #{thin_socket}" if thin_socket),
|
31
34
|
"-e #{thin_environment}",
|
32
35
|
"-a #{thin_address}",
|
33
36
|
"-c #{current_path}",
|
@@ -58,4 +61,4 @@ configuration is set via the thin_* variables.".cleanup
|
|
58
61
|
remote_task :stop_app, :roles => :app do
|
59
62
|
run thin("stop -s #{thin_servers}")
|
60
63
|
end
|
61
|
-
end
|
64
|
+
end
|
data/lib/rack/adapter/loader.rb
CHANGED
@@ -1,25 +1,29 @@
|
|
1
1
|
module Rack
|
2
2
|
class AdapterNotFound < RuntimeError; end
|
3
|
-
|
4
|
-
#
|
5
|
-
# Framework name => file unique to this framework
|
3
|
+
|
4
|
+
# Mapping used to guess which adapter to use in <tt>Adapter.for</tt>.
|
5
|
+
# Framework <name> => <file unique to this framework> in order they will
|
6
|
+
# be tested.
|
6
7
|
# +nil+ for value to never guess.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
|
15
|
-
|
16
|
-
|
8
|
+
# NOTE: If a framework has a file that is not unique, make sure to place
|
9
|
+
# it at the end.
|
10
|
+
ADAPTERS = [
|
11
|
+
[:rails, 'config/environment.rb'],
|
12
|
+
[:ramaze, 'start.rb'],
|
13
|
+
[:halcyon, 'runner.ru'],
|
14
|
+
[:merb, 'config/init.rb'],
|
15
|
+
[:mack, 'config/app_config/default.yml'],
|
16
|
+
[:mack, 'config/configatron/default.rb'],
|
17
|
+
[:file, nil]
|
18
|
+
]
|
19
|
+
|
20
|
+
module Adapter
|
17
21
|
# Guess which adapter to use based on the directory structure
|
18
22
|
# or file content.
|
19
23
|
# Returns a symbol representing the name of the adapter to use
|
20
24
|
# to load the application under <tt>dir/</tt>.
|
21
25
|
def self.guess(dir)
|
22
|
-
ADAPTERS.
|
26
|
+
ADAPTERS.each do |adapter, file|
|
23
27
|
return adapter if file && ::File.exist?(::File.join(dir, file))
|
24
28
|
end
|
25
29
|
raise AdapterNotFound, "No adapter found for #{dir}"
|
@@ -33,39 +37,39 @@ module Rack
|
|
33
37
|
|
34
38
|
when :ramaze
|
35
39
|
require "#{options[:chdir]}/start"
|
36
|
-
|
40
|
+
|
37
41
|
Ramaze.trait[:essentials].delete Ramaze::Adapter
|
38
42
|
Ramaze.start :force => true
|
39
|
-
|
43
|
+
|
40
44
|
return Ramaze::Adapter::Base
|
41
|
-
|
45
|
+
|
42
46
|
when :merb
|
43
47
|
require 'merb-core'
|
44
|
-
|
48
|
+
|
45
49
|
Merb::Config.setup(:merb_root => options[:chdir],
|
46
50
|
:environment => options[:environment])
|
47
51
|
Merb.environment = Merb::Config[:environment]
|
48
52
|
Merb.root = Merb::Config[:merb_root]
|
49
53
|
Merb::BootLoader.run
|
50
|
-
|
54
|
+
|
51
55
|
return Merb::Rack::Application.new
|
52
|
-
|
56
|
+
|
53
57
|
when :halcyon
|
54
58
|
require 'halcyon'
|
55
59
|
|
56
60
|
$:.unshift(Halcyon.root/'lib')
|
57
|
-
Halcyon::Runner.load_config Halcyon.root/'config'/'config.yml'
|
58
61
|
|
59
62
|
return Halcyon::Runner.new
|
60
|
-
|
63
|
+
|
61
64
|
when :mack
|
62
65
|
ENV["MACK_ENV"] = options[:environment]
|
63
66
|
load(::File.join(options[:chdir], "Rakefile"))
|
64
67
|
require 'mack'
|
65
68
|
return Mack::Utils::Server.build_app
|
69
|
+
|
66
70
|
when :file
|
67
71
|
return Rack::File.new(options[:chdir])
|
68
|
-
|
72
|
+
|
69
73
|
else
|
70
74
|
raise AdapterNotFound, "Adapter not found: #{name}"
|
71
75
|
|
data/lib/rack/adapter/rails.rb
CHANGED
@@ -37,7 +37,7 @@ module Rack
|
|
37
37
|
# TODO refactor this in File#can_serve?(path) ??
|
38
38
|
def file_exist?(path)
|
39
39
|
full_path = ::File.join(@file_server.root, Utils.unescape(path))
|
40
|
-
::File.file?(full_path) && ::File.
|
40
|
+
::File.file?(full_path) && ::File.readable_real?(full_path)
|
41
41
|
end
|
42
42
|
|
43
43
|
def serve_file(env)
|
@@ -112,10 +112,16 @@ module Rack
|
|
112
112
|
when Hash then cookie.each { |_, c| cookies << c.to_s }
|
113
113
|
else cookies << cookie.to_s
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
@output_cookies.each { |c| cookies << c.to_s } if @output_cookies
|
117
117
|
|
118
|
-
@response['Set-Cookie'] = [@response['Set-Cookie'], cookies].compact
|
118
|
+
@response['Set-Cookie'] = [@response['Set-Cookie'], cookies].compact
|
119
|
+
# See http://groups.google.com/group/rack-devel/browse_thread/thread/e8759b91a82c5a10/a8dbd4574fe97d69?#a8dbd4574fe97d69
|
120
|
+
if Thin.ruby_18?
|
121
|
+
@response['Set-Cookie'].flatten!
|
122
|
+
else
|
123
|
+
@response['Set-Cookie'] = @response['Set-Cookie'].join("\n")
|
124
|
+
end
|
119
125
|
end
|
120
126
|
|
121
127
|
options.each { |k,v| @response[k] = v }
|
data/lib/thin/backends/base.rb
CHANGED
@@ -30,22 +30,32 @@ module Thin
|
|
30
30
|
# Number of persistent connections currently opened
|
31
31
|
attr_accessor :persistent_connection_count
|
32
32
|
|
33
|
+
# Disable the use of epoll under Linux
|
34
|
+
attr_accessor :no_epoll
|
35
|
+
|
33
36
|
def initialize
|
34
37
|
@connections = []
|
35
38
|
@timeout = Server::DEFAULT_TIMEOUT
|
36
39
|
@persistent_connection_count = 0
|
37
40
|
@maximum_connections = Server::DEFAULT_MAXIMUM_CONNECTIONS
|
38
41
|
@maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
|
42
|
+
@no_epoll = false
|
39
43
|
end
|
40
44
|
|
41
45
|
# Start the backend and connect it.
|
42
46
|
def start
|
43
47
|
@stopping = false
|
44
|
-
|
45
|
-
EventMachine.run do
|
48
|
+
starter = proc do
|
46
49
|
connect
|
47
50
|
@running = true
|
48
51
|
end
|
52
|
+
|
53
|
+
# Allow for early run up of eventmachine.
|
54
|
+
if EventMachine.reactor_running?
|
55
|
+
starter.call
|
56
|
+
else
|
57
|
+
EventMachine.run(&starter)
|
58
|
+
end
|
49
59
|
end
|
50
60
|
|
51
61
|
# Stop of the backend from accepting new connections.
|
@@ -72,7 +82,7 @@ module Thin
|
|
72
82
|
# so you can do crazy stuff that require godlike powers here.
|
73
83
|
def config
|
74
84
|
# See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
|
75
|
-
EventMachine.epoll
|
85
|
+
EventMachine.epoll unless @no_epoll
|
76
86
|
|
77
87
|
# Set the maximum number of socket descriptors that the server may open.
|
78
88
|
# The process needs to have required privilege to set it higher the 1024 on
|
data/lib/thin/command.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'open3'
|
2
2
|
|
3
3
|
module Thin
|
4
|
-
# Run a command
|
4
|
+
# Run a command through the +thin+ command-line script.
|
5
5
|
class Command
|
6
6
|
include Logging
|
7
7
|
|
@@ -34,12 +34,14 @@ module Thin
|
|
34
34
|
# Turn into a runnable shell command
|
35
35
|
def shellify
|
36
36
|
shellified_options = @options.inject([]) do |args, (name, value)|
|
37
|
-
|
37
|
+
case value
|
38
38
|
when NilClass,
|
39
|
-
TrueClass then "--#{name}"
|
39
|
+
TrueClass then args << "--#{name}"
|
40
40
|
when FalseClass
|
41
|
-
|
41
|
+
when Array then value.each { |v| args << "--#{name}=#{v.inspect}" }
|
42
|
+
else args << "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
|
42
43
|
end
|
44
|
+
args
|
43
45
|
end
|
44
46
|
|
45
47
|
raise ArgumentError, "Path to thin script can't be found, set Command.script" unless self.class.script
|
@@ -47,4 +49,4 @@ module Thin
|
|
47
49
|
"#{self.class.script} #{@name} #{shellified_options.compact.join(' ')}"
|
48
50
|
end
|
49
51
|
end
|
50
|
-
end
|
52
|
+
end
|
data/lib/thin/connection.rb
CHANGED
@@ -5,30 +5,34 @@ module Thin
|
|
5
5
|
# This class is instanciated by EventMachine on each new connection
|
6
6
|
# that is opened.
|
7
7
|
class Connection < EventMachine::Connection
|
8
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
9
|
+
TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
|
10
|
+
CHUNKED_REGEXP = /\bchunked\b/i.freeze
|
11
|
+
|
8
12
|
include Logging
|
9
|
-
|
13
|
+
|
10
14
|
# Rack application (adapter) served by this connection.
|
11
15
|
attr_accessor :app
|
12
|
-
|
16
|
+
|
13
17
|
# Backend to the server
|
14
18
|
attr_accessor :backend
|
15
|
-
|
19
|
+
|
16
20
|
# Current request served by the connection
|
17
21
|
attr_accessor :request
|
18
|
-
|
22
|
+
|
19
23
|
# Next response sent through the connection
|
20
24
|
attr_accessor :response
|
21
|
-
|
25
|
+
|
22
26
|
# Calling the application in a threaded allowing
|
23
27
|
# concurrent processing of requests.
|
24
28
|
attr_writer :threaded
|
25
|
-
|
29
|
+
|
26
30
|
# Get the connection ready to process a request.
|
27
31
|
def post_init
|
28
32
|
@request = Request.new
|
29
33
|
@response = Response.new
|
30
34
|
end
|
31
|
-
|
35
|
+
|
32
36
|
# Called when data is received from the client.
|
33
37
|
def receive_data(data)
|
34
38
|
trace { data }
|
@@ -38,7 +42,7 @@ module Thin
|
|
38
42
|
log_error e
|
39
43
|
close_connection
|
40
44
|
end
|
41
|
-
|
45
|
+
|
42
46
|
# Called when all data was received and the request
|
43
47
|
# is ready to be processed.
|
44
48
|
def process
|
@@ -50,63 +54,72 @@ module Thin
|
|
50
54
|
post_process(pre_process)
|
51
55
|
end
|
52
56
|
end
|
53
|
-
|
57
|
+
|
54
58
|
def pre_process
|
55
59
|
# Add client info to the request env
|
56
60
|
@request.remote_address = remote_address
|
57
|
-
|
61
|
+
|
58
62
|
# Process the request calling the Rack adapter
|
59
63
|
@app.call(@request.env)
|
60
|
-
rescue
|
64
|
+
rescue Exception
|
61
65
|
handle_error
|
62
66
|
terminate_request
|
63
67
|
nil # Signal to post_process that the request could not be processed
|
64
68
|
end
|
65
|
-
|
69
|
+
|
66
70
|
def post_process(result)
|
67
71
|
return unless result
|
68
|
-
|
72
|
+
|
73
|
+
# Set the Content-Length header if possible
|
74
|
+
set_content_length(result) if need_content_length?(result)
|
75
|
+
|
69
76
|
@response.status, @response.headers, @response.body = result
|
70
|
-
|
77
|
+
|
78
|
+
log "!! Rack application returned nil body. Probably you wanted it to be an empty string?" if @response.body.nil?
|
71
79
|
# Make the response persistent if requested by the client
|
72
80
|
@response.persistent! if @request.persistent?
|
73
|
-
|
81
|
+
|
74
82
|
# Send the response
|
75
83
|
@response.each do |chunk|
|
76
84
|
trace { chunk }
|
77
85
|
send_data chunk
|
78
86
|
end
|
79
|
-
|
87
|
+
|
80
88
|
# If no more request on that same connection, we close it.
|
81
89
|
close_connection_after_writing unless persistent?
|
82
|
-
|
83
|
-
rescue
|
90
|
+
|
91
|
+
rescue Exception
|
84
92
|
handle_error
|
85
93
|
ensure
|
86
94
|
terminate_request
|
87
95
|
end
|
88
|
-
|
96
|
+
|
97
|
+
# Logs catched exception and closes the connection.
|
89
98
|
def handle_error
|
90
99
|
log "!! Unexpected error while processing request: #{$!.message}"
|
91
100
|
log_error
|
92
101
|
close_connection rescue nil
|
93
102
|
end
|
94
|
-
|
103
|
+
|
104
|
+
# Does request and response cleanup (closes open IO streams and
|
105
|
+
# deletes created temporary files).
|
106
|
+
# Re-initializes response and request if client supports persistent
|
107
|
+
# connection.
|
95
108
|
def terminate_request
|
96
109
|
@request.close rescue nil
|
97
110
|
@response.close rescue nil
|
98
|
-
|
111
|
+
|
99
112
|
# Prepare the connection for another request if the client
|
100
113
|
# supports HTTP pipelining (persistent connection).
|
101
114
|
post_init if persistent?
|
102
115
|
end
|
103
|
-
|
116
|
+
|
104
117
|
# Called when the connection is unbinded from the socket
|
105
118
|
# and can no longer be used to process requests.
|
106
119
|
def unbind
|
107
120
|
@backend.connection_finished(self)
|
108
121
|
end
|
109
|
-
|
122
|
+
|
110
123
|
# Allows this connection to be persistent.
|
111
124
|
def can_persist!
|
112
125
|
@can_persist = true
|
@@ -122,25 +135,52 @@ module Thin
|
|
122
135
|
def persistent?
|
123
136
|
@can_persist && @response.persistent?
|
124
137
|
end
|
125
|
-
|
138
|
+
|
126
139
|
# +true+ if <tt>app.call</tt> will be called inside a thread.
|
127
140
|
# You can set all requests as threaded setting <tt>Connection#threaded=true</tt>
|
128
141
|
# or on a per-request case returning +true+ in <tt>app.deferred?</tt>.
|
129
142
|
def threaded?
|
130
143
|
@threaded || (@app.respond_to?(:deferred?) && @app.deferred?(@request.env))
|
131
144
|
end
|
132
|
-
|
145
|
+
|
133
146
|
# IP Address of the remote client.
|
134
147
|
def remote_address
|
135
148
|
@request.forwarded_for || socket_address
|
136
|
-
rescue
|
149
|
+
rescue Exception
|
137
150
|
log_error
|
138
151
|
nil
|
139
152
|
end
|
140
|
-
|
153
|
+
|
141
154
|
protected
|
155
|
+
|
156
|
+
# Returns IP address of peer as a string.
|
142
157
|
def socket_address
|
143
158
|
Socket.unpack_sockaddr_in(get_peername)[1]
|
144
159
|
end
|
160
|
+
|
161
|
+
private
|
162
|
+
def need_content_length?(result)
|
163
|
+
status, headers, body = result
|
164
|
+
return false if headers.has_key?(CONTENT_LENGTH)
|
165
|
+
return false if (100..199).include?(status) || status == 204 || status == 304
|
166
|
+
return false if headers.has_key?(TRANSFER_ENCODING) && headers[TRANSFER_ENCODING] =~ CHUNKED_REGEXP
|
167
|
+
return false unless body.kind_of?(String) || body.kind_of?(Array)
|
168
|
+
true
|
169
|
+
end
|
170
|
+
|
171
|
+
def set_content_length(result)
|
172
|
+
headers, body = result[1..2]
|
173
|
+
case body
|
174
|
+
when String
|
175
|
+
# See http://redmine.ruby-lang.org/issues/show/203
|
176
|
+
headers[CONTENT_LENGTH] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s
|
177
|
+
when Array
|
178
|
+
bytes = 0
|
179
|
+
body.each do |p|
|
180
|
+
bytes += p.respond_to?(:bytesize) ? p.bytesize : p.size
|
181
|
+
end
|
182
|
+
headers[CONTENT_LENGTH] = bytes.to_s
|
183
|
+
end
|
184
|
+
end
|
145
185
|
end
|
146
|
-
end
|
186
|
+
end
|
@@ -49,6 +49,7 @@ module Thin
|
|
49
49
|
server.maximum_connections = @options[:max_conns]
|
50
50
|
server.maximum_persistent_connections = @options[:max_persistent_conns]
|
51
51
|
server.threaded = @options[:threaded]
|
52
|
+
server.no_epoll = @options[:no_epoll] if server.backend.respond_to?(:no_epoll=)
|
52
53
|
|
53
54
|
# Detach the process, after this line the current process returns
|
54
55
|
server.daemonize if @options[:daemonize]
|
@@ -83,7 +84,7 @@ module Thin
|
|
83
84
|
raise OptionRequired, :pid unless @options[:pid]
|
84
85
|
|
85
86
|
tail_log(@options[:log]) do
|
86
|
-
if Server.kill(@options[:pid], @options[:timeout] || 60)
|
87
|
+
if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
|
87
88
|
wait_for_file :deletion, @options[:pid]
|
88
89
|
end
|
89
90
|
end
|
data/lib/thin/daemonizing.rb
CHANGED
@@ -89,22 +89,16 @@ module Thin
|
|
89
89
|
end
|
90
90
|
|
91
91
|
module ClassMethods
|
92
|
-
# Send a QUIT
|
92
|
+
# Send a QUIT or INT (if timeout is +0+) signal the process which
|
93
|
+
# PID is stored in +pid_file+.
|
93
94
|
# If the process is still running after +timeout+, KILL signal is
|
94
95
|
# sent.
|
95
96
|
def kill(pid_file, timeout=60)
|
96
|
-
if
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
if timeout == 0
|
98
|
+
send_signal('INT', pid_file, timeout)
|
99
|
+
else
|
100
|
+
send_signal('QUIT', pid_file, timeout)
|
100
101
|
end
|
101
|
-
rescue Timeout::Error
|
102
|
-
print "Timeout! "
|
103
|
-
send_signal('KILL', pid_file)
|
104
|
-
rescue Interrupt
|
105
|
-
send_signal('KILL', pid_file)
|
106
|
-
ensure
|
107
|
-
File.delete(pid_file) if File.exist?(pid_file)
|
108
102
|
end
|
109
103
|
|
110
104
|
# Restart the server by sending HUP signal.
|
@@ -113,20 +107,31 @@ module Thin
|
|
113
107
|
end
|
114
108
|
|
115
109
|
# Send a +signal+ to the process which PID is stored in +pid_file+.
|
116
|
-
def send_signal(signal, pid_file)
|
117
|
-
if File.
|
110
|
+
def send_signal(signal, pid_file, timeout=60)
|
111
|
+
if File.file?(pid_file) && pid = File.read(pid_file)
|
118
112
|
pid = pid.to_i
|
119
|
-
|
113
|
+
Logging.log "Sending #{signal} signal to process #{pid} ... "
|
120
114
|
Process.kill(signal, pid)
|
121
|
-
|
122
|
-
|
115
|
+
Timeout.timeout(timeout) do
|
116
|
+
sleep 0.1 while Process.running?(pid)
|
117
|
+
end
|
118
|
+
Logging.log ""
|
123
119
|
else
|
124
120
|
puts "Can't stop process, no PID found in #{pid_file}"
|
125
|
-
nil
|
126
121
|
end
|
122
|
+
rescue Timeout::Error
|
123
|
+
Logging.log "Timeout!"
|
124
|
+
force_kill pid_file
|
125
|
+
rescue Interrupt
|
126
|
+
force_kill pid_file
|
127
127
|
rescue Errno::ESRCH # No such process
|
128
|
-
|
129
|
-
|
128
|
+
Logging.log "process not found!"
|
129
|
+
force_kill pid_file
|
130
|
+
end
|
131
|
+
|
132
|
+
def force_kill(pid_file)
|
133
|
+
Process.kill("KILL", File.read(pid_file)) rescue nil
|
134
|
+
File.delete(pid_file) if File.exist?(pid_file) rescue nil
|
130
135
|
end
|
131
136
|
end
|
132
137
|
|
data/lib/thin/headers.rb
CHANGED
@@ -16,6 +16,14 @@ module Thin
|
|
16
16
|
def []=(key, value)
|
17
17
|
if !@sent.has_key?(key) || ALLOWED_DUPLICATES.include?(key)
|
18
18
|
@sent[key] = true
|
19
|
+
value = case value
|
20
|
+
when Time
|
21
|
+
value.httpdate
|
22
|
+
when NilClass
|
23
|
+
return
|
24
|
+
else
|
25
|
+
value.to_s
|
26
|
+
end
|
19
27
|
@out << HEADER_FORMAT % [key, value]
|
20
28
|
end
|
21
29
|
end
|
data/lib/thin/logging.rb
CHANGED
@@ -15,35 +15,40 @@ module Thin
|
|
15
15
|
def silent?; @silent end
|
16
16
|
end
|
17
17
|
|
18
|
-
#
|
18
|
+
# Global silencer methods
|
19
19
|
def silent
|
20
|
-
warn "`#{self.class.name}\#silent` deprecated, use `Thin::Logging.silent?` instead"
|
21
20
|
Logging.silent?
|
22
21
|
end
|
23
22
|
def silent=(value)
|
24
|
-
warn "`#{self.class.name}\#silent=` deprecated, use `Thin::Logging.silent = #{value}` instead"
|
25
23
|
Logging.silent = value
|
26
24
|
end
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
26
|
+
# Log a message to the console
|
27
|
+
def log(msg)
|
28
|
+
puts msg unless Logging.silent?
|
29
|
+
end
|
30
|
+
module_function :log
|
31
|
+
public :log
|
32
|
+
|
33
|
+
# Log a message to the console if tracing is activated
|
34
|
+
def trace(msg=nil)
|
35
|
+
log msg || yield if Logging.trace?
|
36
|
+
end
|
37
|
+
module_function :trace
|
38
|
+
public :trace
|
39
|
+
|
40
|
+
# Log a message to the console if debugging is activated
|
41
|
+
def debug(msg=nil)
|
42
|
+
log msg || yield if Logging.debug?
|
43
|
+
end
|
44
|
+
module_function :debug
|
45
|
+
public :debug
|
46
|
+
|
47
|
+
# Log an error backtrace if debugging is activated
|
48
|
+
def log_error(e=$!)
|
49
|
+
debug "#{e}\n\t" + e.backtrace.join("\n\t")
|
50
|
+
end
|
51
|
+
module_function :log_error
|
52
|
+
public :log_error
|
48
53
|
end
|
49
54
|
end
|