thin 0.6.1-x86-mswin32-60 → 0.6.2-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 CHANGED
@@ -1,3 +1,27 @@
1
+ == 0.6.2 Rambo release
2
+ * Server now let current connections finish before stopping, fixes #18
3
+ * Fix uploading hanging bug when body is moved to a tempfile,
4
+ also delete the tempfile properly upon completion, fixes #25
5
+ * 'thin restart' now sends HUP signals rather then stopping & starting, closes #17
6
+ * HUP signal now launches a new process with the same options.
7
+ * Add PID and more info from the last request to the Stats adapter
8
+ mostly taken from Rack::ShowException.
9
+ * pid and log files in cluster are no longer required to be relative to the
10
+ app directory (chdir option), fixes #24
11
+ * Revert to using #each when building response headers under Ruby 1.8,
12
+ solves an issue w/ Camping adapter, fixes #22
13
+ * Restructure thin script options in 3 sections: server, daemon and cluster
14
+ * Add --only (-o) option to control only one server of a cluster.
15
+ * Stylize stats page and make the url configurable from the thin script.
16
+ * Raise error if attempting to use unix sockets on windows.
17
+ * Add example config files for http://www.tildeslash.com/monit useage.
18
+ Include the example file using "include /path/to/thin/monit/file" in your monitrc file.
19
+ The group settings let you do this to manage your clusters:
20
+
21
+ sudo monit -g blog restart all
22
+
23
+ There are examples of thin listening on sockets and thin listening on unix sockets.
24
+
1
25
  == 0.6.1 Cheesecake release
2
26
  * Remove socket file when server stops.
3
27
  * Set back cluster to use 'thin' command to launch servers.
data/benchmark/simple.rb CHANGED
@@ -3,12 +3,13 @@
3
3
  #
4
4
  # Run with:
5
5
  #
6
- # ruby simple.rb [num of request]
6
+ # ruby simple.rb [num of request] [print|graph] [concurrency levels]
7
7
  #
8
8
  require File.dirname(__FILE__) + '/../lib/thin'
9
9
  require File.dirname(__FILE__) + '/utils'
10
10
 
11
- request = (ARGV[0] || 1000).to_i # Number of request to send (ab -n option)
11
+ request = (ARGV[0] || 1000).to_i # Number of request to send (ab -n option)
12
12
  output_type = (ARGV[1] || 'print')
13
+ levels = eval(ARGV[2] || '[1, 10, 100]').to_a
13
14
 
14
- benchmark output_type, %w(WEBrick Mongrel EMongrel Thin), request, [1, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
15
+ benchmark output_type, %w(WEBrick Mongrel EMongrel Thin), request, levels
data/benchmark/utils.rb CHANGED
@@ -29,7 +29,7 @@ def run(handler_name, n=1000, c=1)
29
29
 
30
30
  sleep 2
31
31
 
32
- out = `nice -n20 ab -c #{c} -n #{n} http://127.0.0.1:port/ 2> /dev/null`
32
+ out = `nice -n20 ab -c #{c} -n #{n} http://127.0.0.1:#{port}/ 2> /dev/null`
33
33
 
34
34
  Process.kill('SIGKILL', server)
35
35
  Process.wait
data/bin/thin CHANGED
@@ -34,20 +34,29 @@ opts = OptionParser.new do |opts|
34
34
  opts.on("-S", "--socket PATH", "bind to unix domain socket") { |file| options[:socket] = file }
35
35
  opts.on("-e", "--environment ENV", "Rails environment (default: development)") { |env| options[:environment] = env }
36
36
  opts.on("-c", "--chdir PATH", "Change to dir before starting") { |dir| options[:chdir] = File.expand_path(dir) }
37
- opts.on("-s", "--servers NUM", "Number of servers to start",
38
- "set a value >1 to start a cluster") { |num| options[:servers] = num.to_i }
37
+ opts.on("-t", "--timeout SEC", "Request or command timeout in sec",
38
+ "(default: #{options[:timeout]})") { |sec| options[:timeout] = sec.to_i }
39
+ opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| options[:prefix] = path }
40
+ opts.on( "--stats PATH", "Mount the Stats adapter under PATH") { |path| options[:stats] = path }
41
+
42
+ opts.separator ""
43
+ opts.separator "Daemon options:"
44
+
39
45
  opts.on("-d", "--daemonize", "Run daemonized in the background") { options[:daemonize] = true }
40
46
  opts.on("-l", "--log FILE", "File to redirect output",
41
47
  "(default: #{options[:log]})") { |file| options[:log] = file }
42
48
  opts.on("-P", "--pid FILE", "File to store PID",
43
49
  "(default: #{options[:pid]})") { |file| options[:pid] = file }
44
- opts.on("-t", "--timeout SEC", "Request or command timeout in sec",
45
- "(default: #{options[:timeout]})") { |sec| options[:timeout] = sec.to_i }
46
50
  opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| options[:user] = user }
47
51
  opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| options[:group] = group }
48
- opts.on( "--prefix PATH", "Mount the app under PATH (start with /)") { |path| options[:prefix] = path }
49
- opts.on("-C", "--config PATH", "Load option from a config file") { |file| options[:config] = file }
50
- opts.on( "--stats", "Install the Stats adapter at /stats") { options[:stats] = true }
52
+
53
+ opts.separator ""
54
+ opts.separator "Cluster options:"
55
+
56
+ opts.on("-s", "--servers NUM", "Number of servers to start",
57
+ "set a value >1 to start a cluster") { |num| options[:servers] = num.to_i }
58
+ opts.on("-o", "--only NUM", "Send command to only one server of the cluster") { |only| options[:only] = only }
59
+ opts.on("-C", "--config PATH", "Load options from a config file") { |file| options[:config] = file }
51
60
 
52
61
  opts.separator ""
53
62
  opts.separator "Common options:"
@@ -62,7 +71,7 @@ end
62
71
  # == Utilities
63
72
 
64
73
  def cluster?(options)
65
- options[:servers] && options[:servers] > 1
74
+ options[:only] || (options[:servers] && options[:servers] > 1)
66
75
  end
67
76
 
68
77
  def load_options_from_config_file!(options)
@@ -101,9 +110,12 @@ def start(options)
101
110
  server.app = Rack::URLMap.new(options[:prefix] => server.app) if options[:prefix]
102
111
 
103
112
  # If a stats are required, wrap in Stats adapter
104
- server.app = Thin::Stats::Adapter.new(server.app) if options[:stats]
113
+ server.app = Thin::Stats::Adapter.new(server.app, options[:stats]) if options[:stats]
105
114
 
106
- server.start!
115
+ # Register restart procedure
116
+ server.on_restart { Thin::Command.run(:start, options) }
117
+
118
+ server.start
107
119
  end
108
120
  end
109
121
 
@@ -113,7 +125,7 @@ def stop(options)
113
125
  if cluster?(options)
114
126
  Thin::Cluster.new(options).stop
115
127
  else
116
- Thin::Server.kill options[:pid], options[:timeout]
128
+ Thin::Server.kill(options[:pid], options[:timeout])
117
129
  end
118
130
  end
119
131
 
@@ -123,11 +135,7 @@ def restart(options)
123
135
  if cluster?(options)
124
136
  Thin::Cluster.new(options).restart
125
137
  else
126
- # Restart only make sense when running as a daemon
127
- options.update :daemonize => true
128
-
129
- stop(options)
130
- start(options)
138
+ Thin::Server.restart(options[:pid])
131
139
  end
132
140
  end
133
141
 
data/example/adapter.rb CHANGED
@@ -1,17 +1,35 @@
1
- require 'rubygems'
2
- require 'thin'
1
+ # Run with: ruby adapter.rb
2
+ # Then browse to http://localhost:3000/test
3
+ # and http://localhost:3000/files/adapter.rb
4
+ require File.dirname(__FILE__) + '/../lib/thin'
3
5
 
4
6
  class SimpleAdapter
5
7
  def call(env)
8
+ body = ["hello!"]
6
9
  [
7
10
  200,
8
- { 'Content-Type' => 'text/plain' },
9
- ["hello!\n"]
11
+ {
12
+ 'Content-Type' => 'text/plain',
13
+ 'Content-Type' => body.join.size.to_s
14
+ },
15
+ body
10
16
  ]
11
17
  end
12
18
  end
13
19
 
14
- app = Rack::URLMap.new('/test' => SimpleAdapter.new,
15
- '/files' => Rack::File.new('.'))
20
+ Thin::Server.start('0.0.0.0', 3000) do
21
+ use Rack::CommonLogger
22
+ map '/test' do
23
+ run SimpleAdapter.new
24
+ end
25
+ map '/files' do
26
+ run Rack::File.new('.')
27
+ end
28
+ end
16
29
 
17
- Thin::Server.new('0.0.0.0', 3000, app).start!
30
+ # You could also start the server like this:
31
+ #
32
+ # app = Rack::URLMap.new('/test' => SimpleAdapter.new,
33
+ # '/files' => Rack::File.new('.'))
34
+ # Thin::Server.new('0.0.0.0', 3000, app).start
35
+ #
@@ -0,0 +1,20 @@
1
+ check process blog1
2
+ with pidfile /u/apps/blog/shared/pids/thin.14000.pid
3
+ start program = "/usr/local/bin/ruby /usr/local/bin/thin start -d -e production -u nobody -g nobody -p 14000 -a 127.0.0.1 -P tmp/pids/thin.14000.pid -c /u/apps/blog/current"
4
+ stop program = "/usr/local/bin/ruby /usr/local/bin/thin stop -P /u/apps/blog/shared/pids/thin.14000.pid"
5
+ if totalmem > 90.0 MB for 5 cycles then restart
6
+ if failed port 14000 then restart
7
+ if cpu usage > 95% for 3 cycles then restart
8
+ if 5 restarts within 5 cycles then timeout
9
+ group blog
10
+
11
+ check process blog2
12
+ with pidfile /u/apps/blog/shared/pids/thin.14001.pid
13
+ start program = "/usr/local/bin/ruby /usr/local/bin/thin start -d -e production -u nobody -g nobody -p 14001 -a 127.0.0.1 -P tmp/pids/thin.14001.pid -c /u/apps/blog/current"
14
+ stop program = "/usr/local/bin/ruby /usr/local/bin/thin stop -P /u/apps/blog/shared/pids/thin.14001.pid"
15
+ if totalmem > 90.0 MB for 5 cycles then restart
16
+ if failed port 14001 then restart
17
+ if cpu usage > 95% for 3 cycles then restart
18
+ if 5 restarts within 5 cycles then timeout
19
+ group blog
20
+
@@ -0,0 +1,20 @@
1
+ check process blog1
2
+ with pidfile /u/apps/blog/shared/pids/thin.1.pid
3
+ start program = "/usr/local/bin/ruby /usr/local/bin/thin start -d -e production -S /u/apps/blog/shared/pids/thin.1.sock -P tmp/pids/thin.1.pid -c /u/apps/blog/current"
4
+ stop program = "/usr/local/bin/ruby /usr/local/bin/thin stop -P /u/apps/blog/shared/pids/thin.1.pid"
5
+ if totalmem > 90.0 MB for 5 cycles then restart
6
+ if failed unixsocket /u/apps/blog/shared/pids/thin.1.sock then restart
7
+ if cpu usage > 95% for 3 cycles then restart
8
+ if 5 restarts within 5 cycles then timeout
9
+ group blog
10
+
11
+ check process blog2
12
+ with pidfile /u/apps/blog/shared/pids/thin.2.pid
13
+ start program = "/usr/local/bin/ruby /usr/local/bin/thin start -d -e production -S /u/apps/blog/shared/pids/thin.2.sock -P tmp/pids/thin.2.pid -c /u/apps/blog/current"
14
+ stop program = "/usr/local/bin/ruby /usr/local/bin/thin stop -P /u/apps/blog/shared/pids/thin.2.pid"
15
+ if totalmem > 90.0 MB for 5 cycles then restart
16
+ if failed unixsocket /u/apps/blog/shared/pids/thin.2.sock then restart
17
+ if cpu usage > 95% for 3 cycles then restart
18
+ if 5 restarts within 5 cycles then timeout
19
+ group blog
20
+
data/lib/thin.rb CHANGED
@@ -15,6 +15,7 @@ module Thin
15
15
  SERVER = "#{NAME} #{VERSION::STRING} codename #{VERSION::CODENAME}".freeze
16
16
 
17
17
  autoload :Cluster, 'thin/cluster'
18
+ autoload :Command, 'thin/command'
18
19
  autoload :Connection, 'thin/connection'
19
20
  autoload :Daemonizable, 'thin/daemonizing'
20
21
  autoload :Logging, 'thin/logging'
data/lib/thin/cluster.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Thin
2
2
  # Control a set of servers.
3
3
  # * Generate start and stop commands and run them.
4
- # * Inject the port number in the pid and log filenames.
5
- # Servers are started throught the +thin+ commandline script.
4
+ # * Inject the port or socket number in the pid and log filenames.
5
+ # Servers are started throught the +thin+ command-line script.
6
6
  class Cluster
7
7
  include Logging
8
8
 
@@ -20,7 +20,8 @@ module Thin
20
20
  def initialize(options)
21
21
  @options = options.merge(:daemonize => true)
22
22
  @size = @options.delete(:servers)
23
- @script = 'thin'
23
+ @only = @options.delete(:only)
24
+ @script = $PROGRAM_NAME
24
25
 
25
26
  if socket
26
27
  @options.delete(:address)
@@ -31,8 +32,8 @@ module Thin
31
32
  def first_port; @options[:port] end
32
33
  def address; @options[:address] end
33
34
  def socket; @options[:socket] end
34
- def pid_file; File.expand_path File.join(@options[:chdir], @options[:pid]) end
35
- def log_file; File.expand_path File.join(@options[:chdir], @options[:log]) end
35
+ def pid_file; @options[:pid] end
36
+ def log_file; @options[:log] end
36
37
 
37
38
  # Start the servers
38
39
  def start
@@ -99,27 +100,16 @@ module Thin
99
100
  else
100
101
  cmd_options.merge!(:port => number)
101
102
  end
102
- shell_cmd = shellify(cmd, cmd_options)
103
- trace shell_cmd
104
- ouput = `#{shell_cmd}`.chomp
105
- log " " + ouput.gsub("\n", " \n") unless ouput.empty?
106
- end
107
-
108
- # Turn into a runnable shell command
109
- def shellify(cmd, options)
110
- shellified_options = options.inject([]) do |args, (name, value)|
111
- args << case value
112
- when NilClass
113
- when TrueClass then "--#{name}"
114
- else "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
115
- end
116
- end
117
- "#{@script} #{cmd} #{shellified_options.compact.join(' ')}"
103
+ Command.run(cmd, cmd_options)
118
104
  end
119
105
 
120
106
  def with_each_server
121
- @size.times do |n|
122
- yield socket ? n : (first_port + n)
107
+ if @only
108
+ yield @only
109
+ else
110
+ @size.times do |n|
111
+ yield socket ? n : (first_port + n)
112
+ end
123
113
  end
124
114
  end
125
115
 
@@ -0,0 +1,40 @@
1
+ module Thin
2
+ # Run a command though the +thin+ command-line script.
3
+ class Command
4
+ include Logging
5
+
6
+ # Path to the +thin+ script used to control the servers.
7
+ # Leave this to default to use the one in the path.
8
+ attr_accessor :script
9
+
10
+ def initialize(name, options={})
11
+ @name = name
12
+ @options = options
13
+ @script = $PROGRAM_NAME
14
+ end
15
+
16
+ def self.run(*args)
17
+ new(*args).run
18
+ end
19
+
20
+ # Send the command to the +thin+ script
21
+ def run
22
+ shell_cmd = shellify
23
+ trace shell_cmd
24
+ ouput = `#{shell_cmd}`.chomp
25
+ log " " + ouput.gsub("\n", " \n") unless ouput.empty?
26
+ end
27
+
28
+ # Turn into a runnable shell command
29
+ def shellify
30
+ shellified_options = @options.inject([]) do |args, (name, value)|
31
+ args << case value
32
+ when NilClass
33
+ when TrueClass then "--#{name}"
34
+ else "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
35
+ end
36
+ end
37
+ "#{@script} #{@name} #{shellified_options.compact.join(' ')}"
38
+ end
39
+ end
40
+ end
@@ -10,6 +10,9 @@ module Thin
10
10
  # +true+ if the connection is on a UNIX domain socket.
11
11
  attr_accessor :unix_socket
12
12
 
13
+ # Server owning the connection
14
+ attr_accessor :server
15
+
13
16
  def post_init
14
17
  @request = Request.new
15
18
  @response = Response.new
@@ -25,13 +28,11 @@ module Thin
25
28
  end
26
29
 
27
30
  def process
28
- env = @request.env
29
-
30
31
  # Add client info to the request env
31
- env[Request::REMOTE_ADDR] = remote_address(env)
32
+ @request.env[Request::REMOTE_ADDR] = remote_address
32
33
 
33
34
  # Process the request
34
- @response.status, @response.headers, @response.body = @app.call(env)
35
+ @response.status, @response.headers, @response.body = @app.call(@request.env)
35
36
 
36
37
  # Send the response
37
38
  @response.each do |chunk|
@@ -46,13 +47,17 @@ module Thin
46
47
  log_error e
47
48
  close_connection rescue nil
48
49
  ensure
49
- @request.close rescue nil
50
+ @request.close rescue nil
50
51
  @response.close rescue nil
51
52
  end
52
53
 
54
+ def unbind
55
+ @server.connection_finished(self)
56
+ end
57
+
53
58
  protected
54
- def remote_address(env)
55
- if remote_addr = env[Request::FORWARDED_FOR]
59
+ def remote_address
60
+ if remote_addr = @request.env[Request::FORWARDED_FOR]
56
61
  remote_addr
57
62
  elsif @unix_socket
58
63
  # FIXME not sure about this, does it even make sense on a UNIX socket?
@@ -31,7 +31,7 @@ module Thin
31
31
 
32
32
  # Turns the current script into a daemon process that detaches from the console.
33
33
  def daemonize
34
- check_plateform_support
34
+ raise PlatformNotSupported, 'Daemonizing not supported on Windows' if Thin.win?
35
35
  raise ArgumentError, 'You must specify a pid_file to deamonize' unless @pid_file
36
36
 
37
37
  pwd = Dir.pwd # Current directory is changed during daemonization, so store it
@@ -39,8 +39,10 @@ module Thin
39
39
  Daemonize.daemonize(File.expand_path(@log_file))
40
40
 
41
41
  Dir.chdir(pwd)
42
-
42
+
43
43
  write_pid_file
44
+
45
+ trap('HUP') { restart }
44
46
  at_exit do
45
47
  log ">> Exiting!"
46
48
  remove_pid_file
@@ -50,7 +52,6 @@ module Thin
50
52
  # Change privileges of the process
51
53
  # to the specified user and group.
52
54
  def change_privilege(user, group=user)
53
- check_plateform_support
54
55
  log ">> Changing process privilege to #{user}:#{group}"
55
56
 
56
57
  uid, gid = Process.euid, Process.egid
@@ -67,41 +68,67 @@ module Thin
67
68
  log "Couldn't change user and group to #{user}:#{group}: #{e}"
68
69
  end
69
70
 
71
+ # Registerer a proc to be called to restart the server.
72
+ def on_restart(&block)
73
+ @on_restart = block
74
+ end
75
+
76
+ # Restart the server
77
+ def restart
78
+ raise ArgumentError, "Can't restart, no 'on_restart' proc specified" unless @on_restart
79
+ log '>> Restarting ...'
80
+ stop
81
+ remove_pid_file
82
+ @on_restart.call
83
+ exit!
84
+ end
85
+
70
86
  module ClassMethods
71
- # Kill the process which PID is stored in +pid_file+.
87
+ # Send a INT signal the process which PID is stored in +pid_file+.
88
+ # If the process is still running after +timeout+, KILL signal is
89
+ # sent.
72
90
  def kill(pid_file, timeout=60)
73
- if pid = open(pid_file).read
74
- pid = pid.to_i
75
- print "Sending INT signal to process #{pid} ... "
91
+ if pid = send_signal('INT', pid_file)
76
92
  begin
77
- Process.kill('INT', pid)
78
93
  Timeout.timeout(timeout) do
79
94
  sleep 0.1 while Process.running?(pid)
80
95
  end
81
96
  rescue Timeout::Error
82
- print "timeout, Sending KILL signal ... "
83
- Process.kill('KILL', pid)
97
+ print "timeout! "
98
+ send_signal('KILL', pid_file)
84
99
  end
85
- puts "stopped!"
100
+ end
101
+ File.delete(pid_file) if File.exist?(pid_file)
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)
111
+ if File.exist?(pid_file) && pid = open(pid_file).read
112
+ pid = pid.to_i
113
+ print "Sending #{signal} signal to process #{pid} ... "
114
+ Process.kill(signal, pid)
115
+ puts
116
+ pid
86
117
  else
87
- puts "Can't stop process, no PID found in #{@pid_file}"
118
+ puts "Can't stop process, no PID found in #{pid_file}"
119
+ nil
88
120
  end
89
121
  rescue Errno::ESRCH # No such process
90
122
  puts "process not found!"
91
- ensure
92
- File.delete(pid_file) rescue nil
123
+ nil
93
124
  end
94
125
  end
95
126
 
96
- private
97
- def check_plateform_support
98
- raise RuntimeError, 'Daemonizing not supported on Windows' if RUBY_PLATFORM =~ /mswin/
99
- end
100
-
127
+ protected
101
128
  def remove_pid_file
102
129
  File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
103
130
  end
104
-
131
+
105
132
  def write_pid_file
106
133
  log ">> Writing PID to #{@pid_file}"
107
134
  FileUtils.mkdir_p File.dirname(@pid_file)