thin 0.6.4-x86-mswin32-60 → 0.7.0-x86-mswin32-60
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 +20 -0
- data/README +11 -12
- data/benchmark/abc +51 -0
- data/benchmark/benchmarker.rb +80 -0
- data/benchmark/runner +79 -0
- data/example/adapter.rb +3 -3
- data/example/thin.god +11 -7
- data/lib/thin.rb +17 -16
- data/lib/thin/command.rb +10 -4
- data/lib/thin/connection.rb +51 -9
- data/lib/thin/connectors/connector.rb +22 -10
- data/lib/thin/connectors/swiftiply_client.rb +55 -0
- data/lib/thin/connectors/unix_server.rb +5 -7
- data/lib/thin/controllers/cluster.rb +28 -22
- data/lib/thin/controllers/controller.rb +74 -14
- data/lib/thin/controllers/service.rb +1 -1
- data/lib/thin/daemonizing.rb +6 -4
- data/lib/thin/headers.rb +4 -0
- data/lib/thin/logging.rb +34 -9
- data/lib/thin/request.rb +31 -2
- data/lib/thin/response.rb +22 -7
- data/lib/thin/runner.rb +27 -14
- data/lib/thin/server.rb +55 -7
- data/lib/thin/version.rb +3 -3
- data/lib/thin_parser.so +0 -0
- data/spec/command_spec.rb +2 -3
- data/spec/connection_spec.rb +25 -1
- data/spec/connectors/swiftiply_client_spec.rb +66 -0
- data/spec/controllers/cluster_spec.rb +43 -12
- data/spec/controllers/controller_spec.rb +16 -4
- data/spec/controllers/service_spec.rb +0 -1
- data/spec/logging_spec.rb +42 -0
- data/spec/request/persistent_spec.rb +35 -0
- data/spec/response_spec.rb +18 -0
- data/spec/server/pipelining_spec.rb +108 -0
- data/spec/server/swiftiply.yml +6 -0
- data/spec/server/swiftiply_spec.rb +27 -0
- data/spec/server/tcp_spec.rb +3 -3
- data/spec/server_spec.rb +22 -0
- data/spec/spec_helper.rb +3 -3
- data/tasks/gem.rake +1 -1
- data/tasks/spec.rake +9 -0
- metadata +14 -6
- data/benchmark/previous.rb +0 -14
- data/benchmark/simple.rb +0 -15
- data/benchmark/utils.rb +0 -75
@@ -4,27 +4,32 @@ module Thin
|
|
4
4
|
# * connection/disconnection to the server
|
5
5
|
# * initialization of the connections
|
6
6
|
# * manitoring of the active connections.
|
7
|
-
class Connector
|
8
|
-
include Logging
|
9
|
-
|
7
|
+
class Connector
|
10
8
|
# Server serving the connections throught the connector
|
11
|
-
|
9
|
+
attr_accessor :server
|
12
10
|
|
13
11
|
# Maximum time for incoming data to arrive
|
14
12
|
attr_accessor :timeout
|
15
13
|
|
14
|
+
# Maximum number of connections that can be persistent
|
15
|
+
attr_accessor :maximum_persistent_connections
|
16
|
+
|
17
|
+
# Number of persistent connections currently opened
|
18
|
+
attr_accessor :persistent_connection_count
|
19
|
+
|
16
20
|
def initialize
|
17
|
-
@connections
|
18
|
-
@timeout
|
21
|
+
@connections = []
|
22
|
+
@timeout = Server::DEFAULT_TIMEOUT
|
23
|
+
@persistent_connection_count = 0
|
24
|
+
@maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
|
19
25
|
end
|
20
26
|
|
21
27
|
# Free up resources used by the connector.
|
22
28
|
def close
|
23
29
|
end
|
24
30
|
|
25
|
-
def
|
26
|
-
@server
|
27
|
-
@silent = @server.silent
|
31
|
+
def running?
|
32
|
+
@server.running?
|
28
33
|
end
|
29
34
|
|
30
35
|
# Initialize a new connection to a client.
|
@@ -32,7 +37,13 @@ module Thin
|
|
32
37
|
connection.connector = self
|
33
38
|
connection.app = @server.app
|
34
39
|
connection.comm_inactivity_timeout = @timeout
|
35
|
-
|
40
|
+
|
41
|
+
# We control the number of persistent connections by keeping
|
42
|
+
# a count of the total one allowed yet.
|
43
|
+
if @persistent_connection_count < @maximum_persistent_connections
|
44
|
+
connection.can_persist!
|
45
|
+
@persistent_connection_count += 1
|
46
|
+
end
|
36
47
|
|
37
48
|
@connections << connection
|
38
49
|
end
|
@@ -44,6 +55,7 @@ module Thin
|
|
44
55
|
|
45
56
|
# Called by a connection when it's unbinded.
|
46
57
|
def connection_finished(connection)
|
58
|
+
@persistent_connection_count -= 1 if connection.can_persist?
|
47
59
|
@connections.delete(connection)
|
48
60
|
end
|
49
61
|
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Thin
|
2
|
+
module Connectors
|
3
|
+
class SwiftiplyClient < Connector
|
4
|
+
attr_accessor :key
|
5
|
+
|
6
|
+
attr_accessor :host, :port
|
7
|
+
|
8
|
+
def initialize(host, port, key=nil)
|
9
|
+
@host = host
|
10
|
+
@port = port.to_i
|
11
|
+
@key = key || ''
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
# Connect the server
|
16
|
+
def connect
|
17
|
+
EventMachine.connect(@host, @port, SwiftiplyConnection, &method(:initialize_connection))
|
18
|
+
end
|
19
|
+
|
20
|
+
# Stops the server
|
21
|
+
def disconnect
|
22
|
+
EventMachine.stop
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"#{@host}:#{@port} swiftiply"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class SwiftiplyConnection < Connection
|
32
|
+
def connection_completed
|
33
|
+
send_data swiftiply_handshake(@connector.key)
|
34
|
+
end
|
35
|
+
|
36
|
+
def persistent?
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def unbind
|
41
|
+
super
|
42
|
+
EventMachine.add_timer(rand(2)) { reconnect(@connector.host, @connector.port) } if @connector.running?
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
def swiftiply_handshake(key)
|
47
|
+
'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i)}.join << sprintf('%04x', @connector.port) << sprintf('%02x', key.length) << key
|
48
|
+
end
|
49
|
+
|
50
|
+
# For some reason Swiftiply request the current host
|
51
|
+
def host_ip
|
52
|
+
Socket.gethostbyname(@connector.host)[3].unpack('CCCC') rescue [0,0,0,0]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -43,12 +43,10 @@ module Thin
|
|
43
43
|
|
44
44
|
# Connection through a UNIX domain socket.
|
45
45
|
class UnixConnection < Connection
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
nil
|
52
|
-
end
|
46
|
+
protected
|
47
|
+
def socket_address
|
48
|
+
# FIXME not sure about this, does it even make sense on a UNIX socket?
|
49
|
+
Socket.unpack_sockaddr_un(get_peername)
|
50
|
+
end
|
53
51
|
end
|
54
52
|
end
|
@@ -5,19 +5,15 @@ module Thin
|
|
5
5
|
# * Inject the port or socket number in the pid and log filenames.
|
6
6
|
# Servers are started throught the +thin+ command-line script.
|
7
7
|
class Cluster < Controller
|
8
|
-
#
|
9
|
-
|
10
|
-
|
8
|
+
# Cluster only options that should not be passed in the command sent
|
9
|
+
# to the indiviual servers.
|
10
|
+
CLUSTER_OPTIONS = [:servers, :only]
|
11
|
+
|
11
12
|
# Create a new cluster of servers launched using +options+.
|
12
13
|
def initialize(options)
|
13
|
-
|
14
|
-
|
15
|
-
@
|
16
|
-
|
17
|
-
if socket
|
18
|
-
@options.delete(:address)
|
19
|
-
@options.delete(:port)
|
20
|
-
end
|
14
|
+
super
|
15
|
+
# Cluster can only contain daemonized servers
|
16
|
+
@options.merge!(:daemonize => true)
|
21
17
|
end
|
22
18
|
|
23
19
|
def first_port; @options[:port] end
|
@@ -25,17 +21,23 @@ module Thin
|
|
25
21
|
def socket; @options[:socket] end
|
26
22
|
def pid_file; @options[:pid] end
|
27
23
|
def log_file; @options[:log] end
|
24
|
+
def size; @options[:servers] end
|
25
|
+
def only; @options[:only] end
|
26
|
+
|
27
|
+
def swiftiply?
|
28
|
+
@options.has_key?(:swiftiply)
|
29
|
+
end
|
28
30
|
|
29
31
|
# Start the servers
|
30
32
|
def start
|
31
|
-
with_each_server { |
|
33
|
+
with_each_server { |n| start_server n }
|
32
34
|
end
|
33
35
|
|
34
36
|
# Start a single server
|
35
37
|
def start_server(number)
|
36
38
|
log "Starting server on #{server_id(number)} ... "
|
37
39
|
|
38
|
-
run :start,
|
40
|
+
run :start, number
|
39
41
|
end
|
40
42
|
|
41
43
|
# Stop the servers
|
@@ -47,7 +49,7 @@ module Thin
|
|
47
49
|
def stop_server(number)
|
48
50
|
log "Stopping server on #{server_id(number)} ... "
|
49
51
|
|
50
|
-
run :stop,
|
52
|
+
run :stop, number
|
51
53
|
end
|
52
54
|
|
53
55
|
# Stop and start the servers.
|
@@ -60,6 +62,8 @@ module Thin
|
|
60
62
|
def server_id(number)
|
61
63
|
if socket
|
62
64
|
socket_for(number)
|
65
|
+
elsif swiftiply?
|
66
|
+
[address, first_port, number].join(':')
|
63
67
|
else
|
64
68
|
[address, number].join(':')
|
65
69
|
end
|
@@ -80,14 +84,16 @@ module Thin
|
|
80
84
|
def pid_for(number)
|
81
85
|
File.read(pid_file_for(number)).chomp.to_i
|
82
86
|
end
|
83
|
-
|
87
|
+
|
84
88
|
private
|
85
89
|
# Send the command to the +thin+ script
|
86
|
-
def run(cmd,
|
87
|
-
cmd_options = options.
|
90
|
+
def run(cmd, number)
|
91
|
+
cmd_options = @options.reject { |option, value| CLUSTER_OPTIONS.include?(option) }
|
88
92
|
cmd_options.merge!(:pid => pid_file_for(number), :log => log_file_for(number))
|
89
93
|
if socket
|
90
94
|
cmd_options.merge!(:socket => socket_for(number))
|
95
|
+
elsif swiftiply?
|
96
|
+
cmd_options.merge!(:port => first_port)
|
91
97
|
else
|
92
98
|
cmd_options.merge!(:port => number)
|
93
99
|
end
|
@@ -95,12 +101,12 @@ module Thin
|
|
95
101
|
end
|
96
102
|
|
97
103
|
def with_each_server
|
98
|
-
if
|
99
|
-
yield
|
104
|
+
if only
|
105
|
+
yield only
|
106
|
+
elsif socket || swiftiply?
|
107
|
+
size.times { |n| yield n }
|
100
108
|
else
|
101
|
-
|
102
|
-
yield socket ? n : (first_port + n)
|
103
|
-
end
|
109
|
+
size.times { |n| yield first_port + n }
|
104
110
|
end
|
105
111
|
end
|
106
112
|
|
@@ -19,30 +19,41 @@ module Thin
|
|
19
19
|
|
20
20
|
def initialize(options)
|
21
21
|
@options = options
|
22
|
+
|
23
|
+
if @options[:socket]
|
24
|
+
@options.delete(:address)
|
25
|
+
@options.delete(:port)
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
29
|
def start
|
25
|
-
|
26
|
-
|
30
|
+
server = case
|
31
|
+
when @options.has_key?(:socket)
|
32
|
+
Server.new(@options[:socket])
|
33
|
+
when @options.has_key?(:swiftiply)
|
34
|
+
Server.new(Connectors::SwiftiplyClient.new(@options[:address], @options[:port], @options[:swiftiply]))
|
27
35
|
else
|
28
|
-
|
36
|
+
Server.new(@options[:address], @options[:port])
|
29
37
|
end
|
30
38
|
|
31
|
-
server.pid_file
|
32
|
-
server.log_file
|
33
|
-
server.timeout
|
39
|
+
server.pid_file = @options[:pid]
|
40
|
+
server.log_file = @options[:log]
|
41
|
+
server.timeout = @options[:timeout]
|
42
|
+
server.maximum_connections = @options[:max_conns]
|
43
|
+
server.maximum_persistent_connections = @options[:max_persistent_conns]
|
34
44
|
|
35
|
-
if @options[:daemonize]
|
36
|
-
|
37
|
-
|
38
|
-
|
45
|
+
server.daemonize if @options[:daemonize]
|
46
|
+
|
47
|
+
# Must be called before changing privileges since it might require superuser power.
|
48
|
+
server.set_descriptor_table_size!
|
49
|
+
server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
|
39
50
|
|
40
51
|
# If a Rack config file is specified we eval it inside a Rack::Builder block to create
|
41
52
|
# a Rack adapter from it. DHH was hacker of the year a couple years ago so we default
|
42
53
|
# to Rails adapter.
|
43
54
|
if @options[:rackup]
|
44
55
|
rackup_code = File.read(@options[:rackup])
|
45
|
-
server.app = eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app",
|
56
|
+
server.app = eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, @options[:rackup])
|
46
57
|
else
|
47
58
|
server.app = Rack::Adapter::Rails.new(@options.merge(:root => @options[:chdir]))
|
48
59
|
end
|
@@ -50,7 +61,7 @@ module Thin
|
|
50
61
|
# If a prefix is required, wrap in Rack URL mapper
|
51
62
|
server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
|
52
63
|
|
53
|
-
# If a stats
|
64
|
+
# If a stats URL is specified, wrap in Stats adapter
|
54
65
|
server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
|
55
66
|
|
56
67
|
# Register restart procedure
|
@@ -62,13 +73,19 @@ module Thin
|
|
62
73
|
def stop
|
63
74
|
raise OptionRequired, :pid unless @options[:pid]
|
64
75
|
|
65
|
-
|
76
|
+
tail_log(@options[:log]) do
|
77
|
+
Server.kill(@options[:pid], @options[:timeout] || 60)
|
78
|
+
wait_for_file :deletion, @options[:pid]
|
79
|
+
end
|
66
80
|
end
|
67
81
|
|
68
82
|
def restart
|
69
83
|
raise OptionRequired, :pid unless @options[:pid]
|
70
84
|
|
71
|
-
|
85
|
+
tail_log(@options[:log]) do
|
86
|
+
Server.restart(@options[:pid])
|
87
|
+
wait_for_file :creation, @options[:pid]
|
88
|
+
end
|
72
89
|
end
|
73
90
|
|
74
91
|
def config
|
@@ -80,6 +97,49 @@ module Thin
|
|
80
97
|
File.open(config_file, 'w') { |f| f << @options.to_yaml }
|
81
98
|
log ">> Wrote configuration to #{config_file}"
|
82
99
|
end
|
100
|
+
|
101
|
+
protected
|
102
|
+
# Wait for a pid file to either be created or deleted.
|
103
|
+
def wait_for_file(state, file)
|
104
|
+
case state
|
105
|
+
when :creation then sleep 0.1 until File.exist?(file)
|
106
|
+
when :deletion then sleep 0.1 while File.exist?(file)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Tail the log file of server +number+ during the execution of the block.
|
111
|
+
def tail_log(log_file)
|
112
|
+
if log_file
|
113
|
+
tail_thread = tail(log_file)
|
114
|
+
yield
|
115
|
+
tail_thread.kill
|
116
|
+
else
|
117
|
+
yield
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Acts like GNU tail command. Taken from Rails.
|
122
|
+
def tail(file)
|
123
|
+
tail_thread = Thread.new do
|
124
|
+
Thread.pass until File.exist?(file)
|
125
|
+
cursor = File.size(file)
|
126
|
+
last_checked = Time.now
|
127
|
+
File.open(file, 'r') do |f|
|
128
|
+
loop do
|
129
|
+
f.seek cursor
|
130
|
+
if f.mtime > last_checked
|
131
|
+
last_checked = f.mtime
|
132
|
+
contents = f.read
|
133
|
+
cursor += contents.length
|
134
|
+
print contents
|
135
|
+
STDOUT.flush
|
136
|
+
end
|
137
|
+
sleep 0.1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
tail_thread
|
142
|
+
end
|
83
143
|
end
|
84
144
|
end
|
85
145
|
end
|
data/lib/thin/daemonizing.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'etc'
|
2
|
-
require 'daemons'
|
2
|
+
require 'daemons' unless Thin.win?
|
3
3
|
|
4
4
|
module Process
|
5
5
|
# Returns +true+ the process identied by +pid+ is running.
|
@@ -89,17 +89,19 @@ module Thin
|
|
89
89
|
end
|
90
90
|
|
91
91
|
module ClassMethods
|
92
|
-
# Send a
|
92
|
+
# Send a QUIT signal the process which PID is stored in +pid_file+.
|
93
93
|
# If the process is still running after +timeout+, KILL signal is
|
94
94
|
# sent.
|
95
95
|
def kill(pid_file, timeout=60)
|
96
|
-
if pid = send_signal('
|
96
|
+
if pid = send_signal('QUIT', pid_file)
|
97
97
|
begin
|
98
98
|
Timeout.timeout(timeout) do
|
99
99
|
sleep 0.1 while Process.running?(pid)
|
100
100
|
end
|
101
101
|
rescue Timeout::Error
|
102
|
-
print "
|
102
|
+
print "Timeout! "
|
103
|
+
send_signal('KILL', pid_file)
|
104
|
+
rescue Interrupt
|
103
105
|
send_signal('KILL', pid_file)
|
104
106
|
end
|
105
107
|
end
|
data/lib/thin/headers.rb
CHANGED
data/lib/thin/logging.rb
CHANGED
@@ -1,24 +1,49 @@
|
|
1
1
|
module Thin
|
2
|
-
# To be included
|
3
|
-
# that can be silented (
|
2
|
+
# To be included in classes to allow some basic logging
|
3
|
+
# that can be silented (<tt>Logging.silent=</tt>) or made
|
4
|
+
# more verbose.
|
5
|
+
# <tt>Logging.debug=</tt>: log all error backtrace and messages
|
6
|
+
# logged with +debug+.
|
7
|
+
# <tt>Logging.trace=</tt>: log all raw request and response and
|
8
|
+
# messages logged with +trace+.
|
4
9
|
module Logging
|
5
|
-
|
6
|
-
|
10
|
+
class << self
|
11
|
+
attr_writer :trace, :debug, :silent
|
12
|
+
|
13
|
+
def trace?; !@silent && @trace end
|
14
|
+
def debug?; !@silent && @debug end
|
15
|
+
def silent?; @silent end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Deprecated silencer methods, those are now a module methods
|
19
|
+
def silent
|
20
|
+
warn "`#{self.class.name}\#silent` deprecated, use `Thin::Logging.silent?` instead"
|
21
|
+
Logging.silent?
|
22
|
+
end
|
23
|
+
def silent=(value)
|
24
|
+
warn "`#{self.class.name}\#silent=` deprecated, use `Thin::Logging.silent = #{value}` instead"
|
25
|
+
Logging.silent = value
|
26
|
+
end
|
7
27
|
|
8
28
|
protected
|
9
29
|
# Log a message to the console
|
10
30
|
def log(msg)
|
11
|
-
puts msg unless
|
31
|
+
puts msg unless Logging.silent?
|
12
32
|
end
|
13
33
|
|
14
34
|
# Log a message to the console if tracing is activated
|
15
35
|
def trace(msg=nil)
|
16
|
-
|
36
|
+
log msg || yield if Logging.trace?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Log a message to the console if debugging is activated
|
40
|
+
def debug(msg=nil)
|
41
|
+
log msg || yield if Logging.debug?
|
17
42
|
end
|
18
43
|
|
19
|
-
# Log an error backtrace if
|
20
|
-
def log_error(e)
|
21
|
-
|
44
|
+
# Log an error backtrace if debugging is activated
|
45
|
+
def log_error(e=$!)
|
46
|
+
debug "#{e}\n\t" + e.backtrace.join("\n\t")
|
22
47
|
end
|
23
48
|
end
|
24
49
|
end
|