thin 0.7.0-x86-mswin32-60 → 0.7.1-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.

Files changed (47) hide show
  1. data/CHANGELOG +11 -1
  2. data/COMMITTERS +3 -0
  3. data/README +4 -0
  4. data/Rakefile +2 -0
  5. data/ext/thin_parser/thin.c +9 -0
  6. data/lib/thin.rb +5 -5
  7. data/lib/thin/backends/base.rb +114 -0
  8. data/lib/thin/{connectors → backends}/swiftiply_client.rb +6 -6
  9. data/lib/thin/{connectors → backends}/tcp_server.rb +2 -2
  10. data/lib/thin/{connectors → backends}/unix_server.rb +19 -8
  11. data/lib/thin/connection.rb +3 -3
  12. data/lib/thin/controllers/controller.rb +6 -5
  13. data/lib/thin/controllers/service.rb +3 -1
  14. data/lib/thin/daemonizing.rb +20 -7
  15. data/lib/thin/logging.rb +1 -1
  16. data/lib/thin/request.rb +5 -5
  17. data/lib/thin/server.rb +63 -78
  18. data/lib/thin/version.rb +4 -2
  19. data/lib/thin_parser.so +0 -0
  20. data/spec/{connectors → backends}/swiftiply_client_spec.rb +10 -10
  21. data/spec/{connectors → backends}/tcp_server_spec.rb +5 -5
  22. data/spec/backends/unix_server_spec.rb +37 -0
  23. data/spec/command_spec.rb +1 -0
  24. data/spec/connection_spec.rb +1 -1
  25. data/spec/controllers/controller_spec.rb +1 -0
  26. data/spec/daemonizing_spec.rb +31 -23
  27. data/spec/{request/perf_spec.rb → perf/request_perf_spec.rb} +0 -0
  28. data/spec/perf/response_perf_spec.rb +19 -0
  29. data/spec/perf/server_perf_spec.rb +39 -0
  30. data/spec/request/mongrel_spec.rb +1 -1
  31. data/spec/response_spec.rb +0 -11
  32. data/spec/runner_spec.rb +4 -4
  33. data/spec/server/builder_spec.rb +8 -3
  34. data/spec/server/pipelining_spec.rb +6 -5
  35. data/spec/server/swiftiply_spec.rb +25 -20
  36. data/spec/server/tcp_spec.rb +1 -9
  37. data/spec/server/unix_socket_spec.rb +1 -5
  38. data/spec/server_spec.rb +4 -7
  39. data/spec/spec_helper.rb +29 -5
  40. data/tasks/announce.rake +2 -2
  41. data/tasks/email.erb +13 -15
  42. data/tasks/ext.rake +28 -24
  43. data/tasks/gem.rake +8 -4
  44. data/tasks/spec.rake +14 -9
  45. metadata +19 -15
  46. data/lib/thin/connectors/connector.rb +0 -73
  47. data/spec/connectors/unix_server_spec.rb +0 -43
data/CHANGELOG CHANGED
@@ -1,3 +1,13 @@
1
+ == 0.7.1 Fancy Pants release
2
+ * Clean stale PID files when starting as daemon, fixes #53 [Chu Yeow]
3
+ * Require EventMachine 0.11.0 for UNIX domain sockets. Until it's released, install from:
4
+ gem install eventmachine --source http://code.macournoyer.com
5
+ * Ruby 1.8.5 compatibility, closes #49 [Wincent Colaiuta]
6
+ * Move all EventMachine stuff out of Server, you can now create a Thin Backend that
7
+ does not depend on EventMachine.
8
+ * Rename Connector to Backend. Extend Thin::Backends::Base to implement your own.
9
+ * Fix high memory usage with big POST body, fixes #48
10
+
1
11
  == 0.7.0 Spherical Cow release
2
12
  * Add --max-persistent-conns option to sets the maximum number of persistent connections.
3
13
  Set to 0 to disable Keep-Alive.
@@ -13,7 +23,7 @@
13
23
  * --debug (-D) option no longer set $DEBUG so logging will be less verbose
14
24
  and Ruby won't be too strict, fixes #36.
15
25
  * Deprecate Server#silent in favour of Logging.silent.
16
- * Persistent connection (keep-alive) & HTTP pipelining support.
26
+ * Persistent connection (keep-alive) support.
17
27
  * Fix -s option not being included in generated config file, fixes #37.
18
28
  * Add Swiftiply support. Use w/ the --swiftiply (-y) option in the thin script,
19
29
  closes #28 [Alex MacCaw]
@@ -0,0 +1,3 @@
1
+ Marc-Andre Cournoyer <macournoyer@gmail.com>
2
+ Kevin Williams <kevwil@gmail.com>
3
+ James Golick
data/README CHANGED
@@ -10,6 +10,10 @@ Which makes it, with all humility, the most secure, stable, fast and extensible
10
10
  bundled in an easy to use gem for your own pleasure.
11
11
 
12
12
  === Installation
13
+ IMPORTANT: Until EventMachine 0.11.0 is out, you have to install it from trunk or from my gem server:
14
+
15
+ sudo gem install eventmachine --source http://code.macournoyer.com
16
+
13
17
  For the latest stable version:
14
18
 
15
19
  sudo gem install thin
data/Rakefile CHANGED
@@ -9,3 +9,5 @@ require 'lib/thin'
9
9
  Dir['tasks/**/*.rake'].each { |rake| load rake }
10
10
 
11
11
  task :default => :spec
12
+
13
+ ext_task :thin_parser
@@ -52,6 +52,15 @@ static VALUE global_path_info;
52
52
  /** Defines global strings in the init method. */
53
53
  #define DEF_GLOBAL(N, val) global_##N = rb_obj_freeze(rb_str_new2(val)); rb_global_variable(&global_##N)
54
54
 
55
+ /* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_PTR */
56
+ #ifndef RSTRING_PTR
57
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
58
+ #endif
59
+
60
+ /* for compatibility with Ruby 1.8.5, which doesn't declare RSTRING_LEN */
61
+ #ifndef RSTRING_LEN
62
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
63
+ #endif
55
64
 
56
65
  /* Defines the maximum allowed lengths for various input elements.*/
57
66
  DEF_MAX_LENGTH(FIELD_NAME, 256);
@@ -22,11 +22,11 @@ module Thin
22
22
  autoload :Server, 'thin/server'
23
23
  autoload :Stats, 'thin/stats'
24
24
 
25
- module Connectors
26
- autoload :Connector, 'thin/connectors/connector'
27
- autoload :SwiftiplyClient, 'thin/connectors/swiftiply_client'
28
- autoload :TcpServer, 'thin/connectors/tcp_server'
29
- autoload :UnixServer, 'thin/connectors/unix_server'
25
+ module Backends
26
+ autoload :Base, 'thin/backends/base'
27
+ autoload :SwiftiplyClient, 'thin/backends/swiftiply_client'
28
+ autoload :TcpServer, 'thin/backends/tcp_server'
29
+ autoload :UnixServer, 'thin/backends/unix_server'
30
30
  end
31
31
 
32
32
  module Controllers
@@ -0,0 +1,114 @@
1
+ module Thin
2
+ module Backends
3
+ # A Backend connects the server to the client. It handles:
4
+ # * connection/disconnection to the server
5
+ # * initialization of the connections
6
+ # * manitoring of the active connections.
7
+ class Base
8
+ # Server serving the connections throught the backend
9
+ attr_accessor :server
10
+
11
+ # Maximum time for incoming data to arrive
12
+ attr_accessor :timeout
13
+
14
+ # Maximum number of file or socket descriptors that the server may open.
15
+ attr_accessor :maximum_connections
16
+
17
+ # Maximum number of connections that can be persistent
18
+ attr_accessor :maximum_persistent_connections
19
+
20
+ # Number of persistent connections currently opened
21
+ attr_accessor :persistent_connection_count
22
+
23
+ def initialize
24
+ @connections = []
25
+ @timeout = Server::DEFAULT_TIMEOUT
26
+ @persistent_connection_count = 0
27
+ @maximum_connections = Server::DEFAULT_MAXIMUM_CONNECTIONS
28
+ @maximum_persistent_connections = Server::DEFAULT_MAXIMUM_PERSISTENT_CONNECTIONS
29
+ end
30
+
31
+ def start
32
+ @stopping = false
33
+
34
+ EventMachine.run do
35
+ connect
36
+ @running = true
37
+ end
38
+ end
39
+
40
+ def stop
41
+ @running = false
42
+ @stopping = true
43
+
44
+ # Do not accept anymore connection
45
+ disconnect
46
+ stop! if @connections.empty?
47
+ end
48
+
49
+ def stop!
50
+ @running = false
51
+ @stopping = false
52
+
53
+ EventMachine.stop if EventMachine.reactor_running?
54
+ @connections.each { |connection| connection.close_connection }
55
+ close
56
+ end
57
+
58
+ def config
59
+ # See http://rubyeventmachine.com/pub/rdoc/files/EPOLL.html
60
+ EventMachine.epoll
61
+
62
+ # Set the maximum number of socket descriptors that the server may open.
63
+ # The process needs to have required privilege to set it higher the 1024 on
64
+ # some systems.
65
+ @maximum_connections = EventMachine.set_descriptor_table_size(@maximum_connections) unless Thin.win?
66
+ end
67
+
68
+ # Free up resources used by the backend.
69
+ def close
70
+ end
71
+
72
+ def running?
73
+ @running
74
+ end
75
+
76
+ # Called by a connection when it's unbinded.
77
+ def connection_finished(connection)
78
+ @persistent_connection_count -= 1 if connection.can_persist?
79
+ @connections.delete(connection)
80
+
81
+ # Finalize gracefull stop if there's no more active connection.
82
+ stop! if @stopping && @connections.empty?
83
+ end
84
+
85
+ # Returns +true+ if no active connection.
86
+ def empty?
87
+ @connections.empty?
88
+ end
89
+
90
+ # Number of active connections.
91
+ def size
92
+ @connections.size
93
+ end
94
+
95
+ protected
96
+ # Initialize a new connection to a client.
97
+ def initialize_connection(connection)
98
+ connection.backend = self
99
+ connection.app = @server.app
100
+ connection.comm_inactivity_timeout = @timeout
101
+
102
+ # We control the number of persistent connections by keeping
103
+ # a count of the total one allowed yet.
104
+ if @persistent_connection_count < @maximum_persistent_connections
105
+ connection.can_persist!
106
+ @persistent_connection_count += 1
107
+ end
108
+
109
+ @connections << connection
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -1,6 +1,6 @@
1
1
  module Thin
2
- module Connectors
3
- class SwiftiplyClient < Connector
2
+ module Backends
3
+ class SwiftiplyClient < Base
4
4
  attr_accessor :key
5
5
 
6
6
  attr_accessor :host, :port
@@ -30,7 +30,7 @@ module Thin
30
30
 
31
31
  class SwiftiplyConnection < Connection
32
32
  def connection_completed
33
- send_data swiftiply_handshake(@connector.key)
33
+ send_data swiftiply_handshake(@backend.key)
34
34
  end
35
35
 
36
36
  def persistent?
@@ -39,17 +39,17 @@ module Thin
39
39
 
40
40
  def unbind
41
41
  super
42
- EventMachine.add_timer(rand(2)) { reconnect(@connector.host, @connector.port) } if @connector.running?
42
+ EventMachine.add_timer(rand(2)) { reconnect(@backend.host, @backend.port) } if @backend.running?
43
43
  end
44
44
 
45
45
  protected
46
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
47
+ 'swiftclient' << host_ip.collect { |x| sprintf('%02x', x.to_i)}.join << sprintf('%04x', @backend.port) << sprintf('%02x', key.length) << key
48
48
  end
49
49
 
50
50
  # For some reason Swiftiply request the current host
51
51
  def host_ip
52
- Socket.gethostbyname(@connector.host)[3].unpack('CCCC') rescue [0,0,0,0]
52
+ Socket.gethostbyname(@backend.host)[3].unpack('CCCC') rescue [0,0,0,0]
53
53
  end
54
54
  end
55
55
  end
@@ -1,7 +1,7 @@
1
1
  module Thin
2
- module Connectors
2
+ module Backends
3
3
  # Connectior to act as a TCP socket server.
4
- class TcpServer < Connector
4
+ class TcpServer < Base
5
5
  # Address and port on which the server is listening for connections.
6
6
  attr_accessor :host, :port
7
7
 
@@ -1,12 +1,13 @@
1
1
  module Thin
2
- module Connectors
3
- # Connector to act as a UNIX domain socket server.
4
- class UnixServer < Connector
2
+ module Backends
3
+ # Backend to act as a UNIX domain socket server.
4
+ class UnixServer < Base
5
5
  # UNIX domain socket on which the server is listening for connections.
6
6
  attr_accessor :socket
7
7
 
8
8
  def initialize(socket)
9
- raise PlatformNotSupported, 'UNIX sockets not available on Windows' if Thin.win?
9
+ raise PlatformNotSupported, 'UNIX domain sockets not available on Windows' if Thin.win?
10
+ check_event_machine_version
10
11
  @socket = socket
11
12
  super()
12
13
  end
@@ -25,7 +26,7 @@ module Thin
25
26
  EventMachine.stop_server(@signature)
26
27
  end
27
28
 
28
- # Free up resources used by the connector.
29
+ # Free up resources used by the backend.
29
30
  def close
30
31
  remove_socket_file
31
32
  end
@@ -38,15 +39,25 @@ module Thin
38
39
  def remove_socket_file
39
40
  File.delete(@socket) if @socket && File.exist?(@socket)
40
41
  end
42
+
43
+ def check_event_machine_version
44
+ # TODO remove this crap once eventmachine 0.11.0 is released
45
+ begin
46
+ gem 'eventmachine', '>= 0.11.0'
47
+ rescue Gem::LoadError
48
+ raise LoadError, "UNIX domain sockets require EventMachine version 0.11.0 or higher, " +
49
+ "install the (not yet released) gem with: " +
50
+ "gem install eventmachine --source http://code.macournoyer.com"
51
+ end
52
+ end
41
53
  end
42
54
  end
43
55
 
44
56
  # Connection through a UNIX domain socket.
45
57
  class UnixConnection < Connection
46
58
  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)
59
+ def socket_address
60
+ '127.0.0.1' # Unix domain sockets can only be local
50
61
  end
51
62
  end
52
63
  end
@@ -10,8 +10,8 @@ module Thin
10
10
  # Rack application served by this connection.
11
11
  attr_accessor :app
12
12
 
13
- # Connector to the server
14
- attr_accessor :connector
13
+ # Backend to the server
14
+ attr_accessor :backend
15
15
 
16
16
  # Current request served by the connection
17
17
  attr_accessor :request
@@ -72,7 +72,7 @@ module Thin
72
72
  # Called when the connection is unbinded from the socket
73
73
  # and can no longer be used to process requests.
74
74
  def unbind
75
- @connector.connection_finished(self)
75
+ @backend.connection_finished(self)
76
76
  end
77
77
 
78
78
  # Allows this connection to be persistent.
@@ -31,7 +31,7 @@ module Thin
31
31
  when @options.has_key?(:socket)
32
32
  Server.new(@options[:socket])
33
33
  when @options.has_key?(:swiftiply)
34
- Server.new(Connectors::SwiftiplyClient.new(@options[:address], @options[:port], @options[:swiftiply]))
34
+ Server.new(Backends::SwiftiplyClient.new(@options[:address], @options[:port], @options[:swiftiply]))
35
35
  else
36
36
  Server.new(@options[:address], @options[:port])
37
37
  end
@@ -44,8 +44,8 @@ module Thin
44
44
 
45
45
  server.daemonize if @options[:daemonize]
46
46
 
47
- # Must be called before changing privileges since it might require superuser power.
48
- server.set_descriptor_table_size!
47
+ server.config # Must be called before changing privileges since it might require superuser power.
48
+
49
49
  server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
50
50
 
51
51
  # If a Rack config file is specified we eval it inside a Rack::Builder block to create
@@ -120,10 +120,10 @@ module Thin
120
120
 
121
121
  # Acts like GNU tail command. Taken from Rails.
122
122
  def tail(file)
123
+ cursor = File.exist?(file) ? File.size(file) : 0
124
+ last_checked = Time.now
123
125
  tail_thread = Thread.new do
124
126
  Thread.pass until File.exist?(file)
125
- cursor = File.size(file)
126
- last_checked = Time.now
127
127
  File.open(file, 'r') do |f|
128
128
  loop do
129
129
  f.seek cursor
@@ -138,6 +138,7 @@ module Thin
138
138
  end
139
139
  end
140
140
  end
141
+ sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
141
142
  tail_thread
142
143
  end
143
144
  end
@@ -50,8 +50,10 @@ module Thin
50
50
  log "To configure thin to start at system boot:"
51
51
  log "on RedHat like systems:"
52
52
  log " sudo /sbin/chkconfig --level 345 #{NAME} on"
53
- log "on Debian like systems (Ubuntu):"
53
+ log "on Debian-like systems (Ubuntu):"
54
54
  log " sudo /usr/sbin/update-rc.d -f #{NAME} defaults"
55
+ log "on Gentoo:"
56
+ log " sudo rc-update add #{NAME} default"
55
57
  log ''
56
58
  log "Then put your config files in #{config_files_path}"
57
59
  end
@@ -34,10 +34,10 @@ module Thin
34
34
 
35
35
  # Turns the current script into a daemon process that detaches from the console.
36
36
  def daemonize
37
- raise PlatformNotSupported, 'Daemonizing not supported on Windows' if Thin.win?
37
+ raise PlatformNotSupported, 'Daemonizing is not supported on Windows' if Thin.win?
38
38
  raise ArgumentError, 'You must specify a pid_file to daemonize' unless @pid_file
39
- raise PidFileExist, "#{@pid_file} already exist, seems like it's already running. " +
40
- "Stop the process or delete #{@pid_file}." if File.exist?(@pid_file)
39
+
40
+ remove_stale_pid_file
41
41
 
42
42
  pwd = Dir.pwd # Current directory is changed during daemonization, so store it
43
43
 
@@ -53,7 +53,7 @@ module Thin
53
53
  remove_pid_file
54
54
  end
55
55
  end
56
-
56
+
57
57
  # Change privileges of the process
58
58
  # to the specified user and group.
59
59
  def change_privilege(user, group=user)
@@ -73,12 +73,12 @@ module Thin
73
73
  log "Couldn't change user and group to #{user}:#{group}: #{e}"
74
74
  end
75
75
 
76
- # Registerer a proc to be called to restart the server.
76
+ # Register a proc to be called to restart the server.
77
77
  def on_restart(&block)
78
78
  @on_restart = block
79
79
  end
80
80
 
81
- # Restart the server
81
+ # Restart the server.
82
82
  def restart
83
83
  raise ArgumentError, "Can't restart, no 'on_restart' proc specified" unless @on_restart
84
84
  log '>> Restarting ...'
@@ -108,7 +108,7 @@ module Thin
108
108
  File.delete(pid_file) if File.exist?(pid_file)
109
109
  end
110
110
 
111
- # Restart the server by sending HUP signal
111
+ # Restart the server by sending HUP signal.
112
112
  def restart(pid_file)
113
113
  send_signal('HUP', pid_file)
114
114
  end
@@ -142,5 +142,18 @@ module Thin
142
142
  open(@pid_file,"w") { |f| f.write(Process.pid) }
143
143
  File.chmod(0644, @pid_file)
144
144
  end
145
+
146
+ # If PID file is stale, remove it.
147
+ def remove_stale_pid_file
148
+ if File.exist?(@pid_file)
149
+ if pid && Process.running?(pid)
150
+ raise PidFileExist, "#{@pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
151
+ "Stop the process or delete #{@pid_file}."
152
+ else
153
+ log ">> Deleting stale PID file #{@pid_file}"
154
+ remove_pid_file
155
+ end
156
+ end
157
+ end
145
158
  end
146
159
  end
@@ -1,6 +1,6 @@
1
1
  module Thin
2
2
  # To be included in classes to allow some basic logging
3
- # that can be silented (<tt>Logging.silent=</tt>) or made
3
+ # that can be silenced (<tt>Logging.silent=</tt>) or made
4
4
  # more verbose.
5
5
  # <tt>Logging.debug=</tt>: log all error backtrace and messages
6
6
  # logged with +debug+.
@@ -52,7 +52,7 @@ module Thin
52
52
  # Rack stuff
53
53
  RACK_INPUT => @body,
54
54
 
55
- RACK_VERSION => [0, 2],
55
+ RACK_VERSION => VERSION::RACK,
56
56
  RACK_ERRORS => STDERR,
57
57
 
58
58
  RACK_MULTITHREAD => false,
@@ -65,11 +65,10 @@ module Thin
65
65
  # Raises a +InvalidRequest+ if invalid.
66
66
  # Returns +true+ if the parsing is complete.
67
67
  def parse(data)
68
- @data << data
69
-
70
68
  if @parser.finished? # Header finished, can only be some more body
71
69
  body << data
72
70
  else # Parse more header using the super parser
71
+ @data << data
73
72
  @nparsed = @parser.execute(@env, @data, @nparsed)
74
73
 
75
74
  # Transfert to a tempfile if body is very big
@@ -78,9 +77,10 @@ module Thin
78
77
 
79
78
 
80
79
  if finished? # Check if header and body are complete
81
- @body.rewind
80
+ @data = nil
81
+ @body.rewind
82
82
  true # Request is fully parsed
83
- else
83
+ else
84
84
  false # Not finished, need more data
85
85
  end
86
86
  end