public
Description: A very fast & simple Ruby web server
Homepage: http://code.macournoyer.com/thin/
Clone URL: git://github.com/macournoyer/thin.git
Search Repo:
Add cluster support through the -s option in the thin script, start 3 
thins like this:

  thin start -s3 -p3000

3 thin servers will be started on port 3000, 3001, 3002, also the port 
number will be
injected in the pid and log filenames.
macournoyer (author)
Sat Jan 12 08:27:28 -0800 2008
commit  6a7725668ba733c1d23fc1df3c0aa0a0d0d05c3e
tree    4f578ee9056c49bde569e30b2a9e5f92a11ab3af
parent  31d7fe1c1dc59604ea690166528bf820fea13770
...
1
 
 
 
 
2
3
4
5
6
7
8
9
10
11
 
 
 
 
 
12
13
14
...
1
2
3
4
5
6
7
8
9
 
 
 
 
 
 
10
11
12
13
14
15
16
17
0
@@ -1,14 +1,17 @@
0
 == 0.5.2 Cheezburger release
0
+ * Add cluster support through the -s option in the thin script, start 3 thins like this:
0
+ thin start -s3 -p3000
0
+ 3 thin servers will be started on port 3000, 3001, 3002, also the port number will be
0
+ injected in the pid and log filenames.
0
  * Fix IOError when writing to logger when starting server as a daemon.
0
  * Really change directory when the -c option is specified.
0
  * Add restart command to thin script.
0
  * Fix typo in thin script usage message and expand chdir path.
0
- * Rename thin script options to be the same as mongrel_rails script:
0
- -o --host => -a --address
0
- --log-file => --log
0
- --pid-file => --pid
0
- --env => --environment
0
- [thronedrk]
0
+ * Rename thin script options to be the same as mongrel_rails script [thronedrk]:
0
+ -o --host => -a --address
0
+ --log-file => --log
0
+ --pid-file => --pid
0
+ --env => --environment
0
  
0
 == 0.5.1 LOLCAT release
0
  * Add URL rewriting to Rails adapter so that page caching works and / fetches index.html if present.
...
7
8
9
10
11
12
13
14
15
16
 
 
 
 
 
 
 
 
17
18
19
20
21
22
...
22
23
24
25
26
27
28
 
 
 
 
 
29
30
31
 
32
33
 
34
35
 
36
37
38
39
40
41
42
43
44
45
46
47
48
...
40
41
42
 
 
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
 
 
 
 
60
61
 
 
 
 
 
62
63
64
65
 
 
 
66
67
68
69
70
 
 
 
 
71
72
73
74
 
 
 
75
76
77
78
 
 
 
 
 
 
79
80
81
82
83
 
 
 
 
 
 
84
85
86
 
 
 
87
88
89
90
91
92
 
93
94
95
...
7
8
9
 
 
 
 
 
 
 
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
23
24
25
 
 
 
 
26
27
28
29
30
31
32
 
33
34
 
35
36
 
37
38
39
40
41
42
43
44
45
46
47
48
49
50
...
42
43
44
45
46
47
 
 
 
 
 
 
 
 
 
 
48
49
50
51
52
53
54
55
56
57
58
 
59
60
61
62
63
64
 
 
 
65
66
67
68
 
 
 
 
69
70
71
72
73
 
 
 
74
75
76
77
78
79
 
80
81
82
83
84
85
86
87
88
 
 
89
90
91
92
93
94
95
 
 
96
97
98
99
100
101
102
103
 
104
105
106
107
0
@@ -7,13 +7,14 @@
0
 COMMANDS = %w(start stop restart)
0
 
0
 options = {
0
- :root => Dir.pwd,
0
- :env => 'development',
0
- :host => '0.0.0.0',
0
- :port => 3000,
0
- :timeout => 60,
0
- :log_file => 'log/thin.log',
0
- :pid_file => 'tmp/pids/thin.pid'
0
+ :chdir => Dir.pwd,
0
+ :environment => 'development',
0
+ :address => '0.0.0.0',
0
+ :port => 3000,
0
+ :timeout => 60,
0
+ :log => 'log/thin.log',
0
+ :pid => 'tmp/pids/thin.pid',
0
+ :servers => 1
0
 }
0
 
0
 opts = OptionParser.new do |opts|
0
0
0
0
@@ -22,17 +23,18 @@
0
   opts.separator ""
0
   opts.separator "Server options:"
0
 
0
- opts.on("-a", "--address HOST", "bind to HOST address (default: 0.0.0.0)") { |host| options[:host] = host }
0
- opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port }
0
- opts.on("-e", "--environment ENV", "Rails environment (default: development)") { |env| options[:env] = env }
0
- opts.on("-c", "--chdir PATH", "Change to dir before starting") { |dir| options[:root] = File.expand_path(dir) }
0
+ opts.on("-a", "--address HOST", "bind to HOST address (default: 0.0.0.0)") { |host| options[:address] = host }
0
+ opts.on("-p", "--port PORT", "use PORT (default: 3000)") { |port| options[:port] = port.to_i }
0
+ opts.on("-e", "--environment ENV", "Rails environment (default: development)") { |env| options[:environment] = env }
0
+ opts.on("-c", "--chdir PATH", "Change to dir before starting") { |dir| options[:chdir] = File.expand_path(dir) }
0
+ opts.on("-s", "--servers NUM", "Number of servers to start") { |num| options[:servers] = num.to_i }
0
   opts.on("-d", "--daemonize", "Run daemonized in the background") { options[:daemonize] = true }
0
   opts.on("-l", "--log FILE", "File to redirect output",
0
- "(default: #{options[:log_file]})") { |file| options[:log_file] = file }
0
+ "(default: #{options[:log]})") { |file| options[:log] = file }
0
   opts.on("-P", "--pid FILE", "File to store PID",
0
- "(default: #{options[:pid_file]})") { |file| options[:pid_file] = file }
0
+ "(default: #{options[:pid]})") { |file| options[:pid] = file }
0
   opts.on("-t", "--timeout SEC", "Request or command timeout in sec",
0
- "(default: #{options[:timeout]})") { |sec| options[:timeout] = sec }
0
+ "(default: #{options[:timeout]})") { |sec| options[:timeout] = sec.to_i }
0
   opts.on("-u", "--user NAME", "User to run daemon as (use with -g)") { |user| options[:user] = user }
0
   opts.on("-g", "--group NAME", "Group to run daemon as (use with -u)") { |group| options[:group] = group }
0
   
0
0
0
0
0
0
0
0
0
0
0
@@ -40,56 +42,66 @@
0
   opts.separator "Common options:"
0
 
0
   opts.on_tail("-D", "--debug", "Set debbuging on") { $DEBUG = true }
0
+ opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
0
+ opts.on_tail('-v', '--version', "Show version") { puts Thin::SERVER; exit }
0
 
0
- opts.on_tail("-h", "--help", "Show this message") do
0
- puts opts
0
- exit
0
- end
0
-
0
- opts.on_tail('-v', '--version', "Show version") do
0
- puts Thin::SERVER
0
- exit
0
- end
0
-
0
   opts.parse! ARGV
0
 end
0
 
0
 
0
 # Commands definitions
0
 
0
+def cluster?(options)
0
+ options[:servers] && options[:servers] > 1
0
+end
0
+
0
 def start(options)
0
- server = Thin::Server.new(options[:host], options[:port])
0
+ if cluster?(options)
0
+ server = Thin::Cluster.new(options)
0
+ server.start
0
+ else
0
+ server = Thin::Server.new(options[:address], options[:port])
0
   
0
- server.pid_file = options[:pid_file]
0
- server.log_file = options[:log_file]
0
- server.timeout = options[:timeout]
0
+ server.pid_file = options[:pid]
0
+ server.log_file = options[:log]
0
+ server.timeout = options[:timeout]
0
   
0
- if options[:daemonize]
0
- server.change_privilege options[:user], options[:group] if options[:user] && options[:group]
0
- server.daemonize
0
- end
0
+ if options[:daemonize]
0
+ server.change_privilege options[:user], options[:group] if options[:user] && options[:group]
0
+ server.daemonize
0
+ end
0
   
0
- server.app = Rack::Adapter::Rails.new(options)
0
-
0
- server.start!
0
+ server.app = Rack::Adapter::Rails.new(options.merge(:root => options[:chdir]))
0
+ server.start!
0
+ end
0
 end
0
 
0
 def stop(options)
0
- Thin::Server.kill options[:pid_file], options[:timeout]
0
+ if cluster?(options)
0
+ server = Thin::Cluster.new(options)
0
+ server.stop
0
+ else
0
+ Thin::Server.kill options[:pid], options[:timeout]
0
+ end
0
 end
0
 
0
 def restart(options)
0
- # Restart only make sense when running as a daemon
0
- options.update :daemonize => true
0
+ if cluster?(options)
0
+ server = Thin::Cluster.new(options)
0
+ server.restart
0
+ else
0
+ # Restart only make sense when running as a daemon
0
+ options.update :daemonize => true
0
   
0
- stop(options)
0
- start(options)
0
+ stop(options)
0
+ start(options)
0
+ end
0
 end
0
 
0
 
0
 # Runs the command
0
 
0
-Dir.chdir(options[:root])
0
+Dir.chdir(options[:chdir])
0
 command = ARGV[0]
0
 
0
 if COMMANDS.include?(command)
...
14
15
16
17
18
 
 
19
20
21
...
14
15
16
 
 
17
18
19
20
21
0
@@ -14,8 +14,8 @@
0
   module Adapter
0
     class Rails
0
       def initialize(options={})
0
- @root = options[:root] || Dir.pwd
0
- @env = options[:env] || 'development'
0
+ @root = options[:root] || Dir.pwd
0
+ @env = options[:environment] || 'development'
0
         
0
         load_application
0
         
...
14
15
16
 
17
18
19
...
14
15
16
17
18
19
20
0
@@ -14,6 +14,7 @@
0
   NAME = 'thin'.freeze
0
   SERVER = "#{NAME} #{VERSION::STRING} codename #{VERSION::CODENAME}".freeze
0
   
0
+ autoload :Cluster, 'thin/cluster'
0
   autoload :Connection, 'thin/connection'
0
   autoload :Daemonizable, 'thin/daemonizing'
0
   autoload :Logging, 'thin/logging'
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
0
@@ -1 +1,104 @@
0
+module Thin
0
+ # Control a set of servers. Generate start and stop commands and run them.
0
+ class Cluster
0
+ include Logging
0
+
0
+ class << self
0
+ # Script to run
0
+ attr_accessor :thin_script
0
+ end
0
+ self.thin_script = 'thin'
0
+
0
+ # Number of servers in the cluster.
0
+ attr_accessor :size
0
+
0
+ # Command line options passed to the thin script
0
+ attr_accessor :options
0
+
0
+ # Create a new cluster of servers launched using +options+.
0
+ def initialize(options)
0
+ @options = options.merge(:daemonize => true)
0
+ @size = @options.delete(:servers)
0
+ end
0
+
0
+ def first_port; @options[:port] end
0
+ def address; @options[:address] end
0
+ def pid_file; File.expand_path File.join(@options[:chdir], @options[:pid]) end
0
+ def log_file; File.expand_path File.join(@options[:chdir], @options[:log]) end
0
+
0
+ # Start the servers
0
+ def start
0
+ with_each_server { |port| start_on_port port }
0
+ end
0
+
0
+ # Start the server on a single port
0
+ def start_on_port(port)
0
+ log "Starting #{address}:#{port} ... "
0
+
0
+ run :start, @options, port
0
+ end
0
+
0
+ # Stop the servers
0
+ def stop
0
+ with_each_server { |port| stop_on_port port }
0
+ end
0
+
0
+ # Stop the server running on +port+
0
+ def stop_on_port(port)
0
+ log "Stopping #{address}:#{port} ... "
0
+
0
+ run :stop, @options, port
0
+ end
0
+
0
+ # Stop and start the servers.
0
+ def restart
0
+ stop
0
+ sleep 0.1 # Let's breath a bit shall we ?
0
+ start
0
+ end
0
+
0
+ def log_file_for(port)
0
+ include_port_number log_file, port
0
+ end
0
+
0
+ def pid_file_for(port)
0
+ include_port_number pid_file, port
0
+ end
0
+
0
+ def pid_for(port)
0
+ File.read(pid_file_for(port)).chomp.to_i
0
+ end
0
+
0
+ private
0
+ # Send the command to the +thin+ script
0
+ def run(cmd, options, port)
0
+ shell_cmd = shellify(cmd, options.merge(:pid => pid_file_for(port), :log => log_file_for(port)))
0
+ trace shell_cmd
0
+ log `#{shell_cmd}`
0
+ end
0
+
0
+ # Turn into a runnable shell command
0
+ def shellify(cmd, options)
0
+ shellified_options = options.inject([]) do |args, (name, value)|
0
+ args << case value
0
+ when NilClass
0
+ when TrueClass then "--#{name}"
0
+ else "--#{name.to_s.tr('_', '-')}=#{value.inspect}"
0
+ end
0
+ end
0
+ "#{self.class.thin_script} #{cmd} #{shellified_options.compact.join(' ')}"
0
+ end
0
+
0
+ def with_each_server
0
+ @size.times { |n| yield first_port + n }
0
+ end
0
+
0
+ # Add the port numbers in the filename
0
+ # so each instance get its own file
0
+ def include_port_number(path, port)
0
+ raise ArgumentError, "filename '#{path}' must include an extension" unless path =~ /\./
0
+ path.gsub(/\.(.+)$/) { ".#{port}.#{$1}" }
0
+ end
0
+ end
0
+end
...
50
51
52
53
 
54
55
56
...
50
51
52
 
53
54
55
56
0
@@ -50,7 +50,7 @@
0
       
0
       if @parser.finished? # Header finished, can only be some more body
0
         body << data
0
- elsif @data.size > MAX_HEADER # Oho! very big header, must be a bad person
0
+ elsif @data.size > MAX_HEADER # Oho! very big header, must be a mean person
0
        raise InvalidRequest, MAX_HEADER_MSG
0
       else # Parse more header
0
        @nparsed = @parser.execute(@env, @data, @nparsed)
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
0
@@ -1 +1,63 @@
0
+require File.dirname(__FILE__) + '/spec_helper'
0
+
0
+describe Cluster do
0
+ before do
0
+ Thin::Cluster.thin_script = File.dirname(__FILE__) + '/../bin/thin'
0
+ @cluster = Thin::Cluster.new(:chdir => File.dirname(__FILE__) + '/rails_app',
0
+ :address => '0.0.0.0',
0
+ :port => 3000,
0
+ :servers => 3,
0
+ :timeout => 10,
0
+ :log => 'thin.log',
0
+ :pid => 'thin.pid'
0
+ )
0
+ @cluster.silent = true
0
+ end
0
+
0
+ it 'should include port number in file names' do
0
+ @cluster.send(:include_port_number, 'thin.log', 3000).should == 'thin.3000.log'
0
+ @cluster.send(:include_port_number, 'thin.pid', 3000).should == 'thin.3000.pid'
0
+ proc { @cluster.send(:include_port_number, 'thin', 3000) }.should raise_error(ArgumentError)
0
+ end
0
+
0
+ it 'should call each server' do
0
+ calls = []
0
+ @cluster.send(:with_each_server) do |port|
0
+ calls << port
0
+ end
0
+ calls.should == [3000, 3001, 3002]
0
+ end
0
+
0
+ it 'should shellify command' do
0
+ out = @cluster.send(:shellify, :start, :port => 3000, :daemonize => true, :log => 'hi.log', :pid => nil)
0
+ out.should include('--port=3000', '--daemonize', '--log="hi.log"', 'thin start --')
0
+ out.should_not include('--pid=')
0
+ end
0
+
0
+ it 'should absolutize file path' do
0
+ @cluster.pid_file_for(3000).should == File.expand_path(File.dirname(__FILE__) + "/rails_app/thin.3000.pid")
0
+ end
0
+
0
+ it 'should start on specified port' do
0
+ @cluster.start_on_port 3000
0
+
0
+ File.exist?(@cluster.pid_file_for(3000)).should be_true
0
+ File.exist?(@cluster.log_file_for(3000)).should be_true
0
+ end
0
+
0
+ it 'should stop on specified port' do
0
+ @cluster.start_on_port 3000
0
+ @cluster.stop_on_port 3000
0
+
0
+ File.exist?(@cluster.pid_file_for(3000)).should be_false
0
+ end
0
+
0
+ after do
0
+ 3000.upto(3003) do |port|
0
+ Process.kill 9, @cluster.pid_for(port) rescue nil
0
+ File.delete @cluster.pid_file_for(port) rescue nil
0
+ File.delete @cluster.log_file_for(port) rescue nil
0
+ end
0
+ end
0
+end
...
1
2
3
 
4
5
6
...
32
33
34
 
 
 
 
 
 
 
 
35
36
37
...
43
44
45
46
 
47
48
49
...
1
2
 
3
4
5
6
...
32
33
34
35
36
37
38
39
40
41
42
43
44
45
...
51
52
53
 
54
55
56
57
0
@@ -1,6 +1,6 @@
0
 require 'rake/gempackagetask'
0
 
0
-CLEAN.include %w(pkg *.gem)
0
+task :clean => :clobber_package
0
 
0
 spec = Gem::Specification.new do |s|
0
   s.name = Thin::NAME
0
@@ -32,6 +32,14 @@
0
   p.gem_spec = spec
0
 end
0
 
0
+task :tag_warn do
0
+ puts "*" * 40
0
+ puts "Don't forget to tag the release:"
0
+ puts " git tag -a v#{Thin::VERSION::STRING}"
0
+ puts "*" * 40
0
+end
0
+task :gem => :tag_warn
0
+
0
 namespace :gem do
0
   desc 'Upload gem to code.macournoyer.com'
0
   task :upload => :gem do
0
@@ -43,7 +51,7 @@
0
   task :upload_rubyforge => :gem do
0
     sh 'rubyforge login'
0
     sh "rubyforge add_release thin thin #{Thin::VERSION::STRING} pkg/thin-#{Thin::VERSION::STRING}.gem"
0
- sh "rubyforge add_file thin thin #{Thin::VERSION::STRING} pkg/thin-#{Thin::VERSION::STRING}.gem"
0
+ sh "rubyforge add_file thin thin #{Thin::VERSION::STRING} pkg/thin-#{Thin::VERSION::STRING}.gem"
0
   end
0
 end
0
 

Comments

    No one has commented yet.