thin 1.2.3-x86-mswin32

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 (137) hide show
  1. data/CHANGELOG +263 -0
  2. data/COPYING +18 -0
  3. data/README +69 -0
  4. data/Rakefile +36 -0
  5. data/benchmark/abc +51 -0
  6. data/benchmark/benchmarker.rb +80 -0
  7. data/benchmark/runner +82 -0
  8. data/bin/thin +6 -0
  9. data/example/adapter.rb +32 -0
  10. data/example/async_app.ru +126 -0
  11. data/example/async_chat.ru +247 -0
  12. data/example/async_tailer.ru +100 -0
  13. data/example/config.ru +22 -0
  14. data/example/monit_sockets +20 -0
  15. data/example/monit_unixsock +20 -0
  16. data/example/myapp.rb +1 -0
  17. data/example/ramaze.ru +12 -0
  18. data/example/thin.god +80 -0
  19. data/example/thin_solaris_smf.erb +36 -0
  20. data/example/thin_solaris_smf.readme.txt +150 -0
  21. data/example/vlad.rake +64 -0
  22. data/ext/thin_parser/common.rl +55 -0
  23. data/ext/thin_parser/ext_help.h +14 -0
  24. data/ext/thin_parser/extconf.rb +6 -0
  25. data/ext/thin_parser/parser.c +452 -0
  26. data/ext/thin_parser/parser.h +49 -0
  27. data/ext/thin_parser/parser.rl +157 -0
  28. data/ext/thin_parser/thin.c +433 -0
  29. data/lib/rack/adapter/loader.rb +79 -0
  30. data/lib/rack/adapter/rails.rb +181 -0
  31. data/lib/thin.rb +46 -0
  32. data/lib/thin/backends/base.rb +141 -0
  33. data/lib/thin/backends/swiftiply_client.rb +56 -0
  34. data/lib/thin/backends/tcp_server.rb +29 -0
  35. data/lib/thin/backends/unix_server.rb +51 -0
  36. data/lib/thin/command.rb +53 -0
  37. data/lib/thin/connection.rb +222 -0
  38. data/lib/thin/controllers/cluster.rb +127 -0
  39. data/lib/thin/controllers/controller.rb +183 -0
  40. data/lib/thin/controllers/service.rb +75 -0
  41. data/lib/thin/controllers/service.sh.erb +39 -0
  42. data/lib/thin/daemonizing.rb +174 -0
  43. data/lib/thin/headers.rb +39 -0
  44. data/lib/thin/logging.rb +54 -0
  45. data/lib/thin/request.rb +153 -0
  46. data/lib/thin/response.rb +101 -0
  47. data/lib/thin/runner.rb +209 -0
  48. data/lib/thin/server.rb +247 -0
  49. data/lib/thin/stats.html.erb +216 -0
  50. data/lib/thin/stats.rb +52 -0
  51. data/lib/thin/statuses.rb +43 -0
  52. data/lib/thin/version.rb +32 -0
  53. data/lib/thin_parser.so +0 -0
  54. data/spec/backends/swiftiply_client_spec.rb +66 -0
  55. data/spec/backends/tcp_server_spec.rb +33 -0
  56. data/spec/backends/unix_server_spec.rb +37 -0
  57. data/spec/command_spec.rb +25 -0
  58. data/spec/configs/cluster.yml +9 -0
  59. data/spec/configs/single.yml +9 -0
  60. data/spec/connection_spec.rb +106 -0
  61. data/spec/controllers/cluster_spec.rb +235 -0
  62. data/spec/controllers/controller_spec.rb +129 -0
  63. data/spec/controllers/service_spec.rb +50 -0
  64. data/spec/daemonizing_spec.rb +192 -0
  65. data/spec/headers_spec.rb +40 -0
  66. data/spec/logging_spec.rb +46 -0
  67. data/spec/perf/request_perf_spec.rb +50 -0
  68. data/spec/perf/response_perf_spec.rb +19 -0
  69. data/spec/perf/server_perf_spec.rb +39 -0
  70. data/spec/rack/loader_spec.rb +29 -0
  71. data/spec/rack/rails_adapter_spec.rb +106 -0
  72. data/spec/rails_app/app/controllers/application.rb +10 -0
  73. data/spec/rails_app/app/controllers/simple_controller.rb +19 -0
  74. data/spec/rails_app/app/helpers/application_helper.rb +3 -0
  75. data/spec/rails_app/app/views/simple/index.html.erb +15 -0
  76. data/spec/rails_app/config/boot.rb +109 -0
  77. data/spec/rails_app/config/environment.rb +64 -0
  78. data/spec/rails_app/config/environments/development.rb +18 -0
  79. data/spec/rails_app/config/environments/production.rb +19 -0
  80. data/spec/rails_app/config/environments/test.rb +22 -0
  81. data/spec/rails_app/config/initializers/inflections.rb +10 -0
  82. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  83. data/spec/rails_app/config/routes.rb +35 -0
  84. data/spec/rails_app/public/404.html +30 -0
  85. data/spec/rails_app/public/422.html +30 -0
  86. data/spec/rails_app/public/500.html +30 -0
  87. data/spec/rails_app/public/dispatch.cgi +10 -0
  88. data/spec/rails_app/public/dispatch.fcgi +24 -0
  89. data/spec/rails_app/public/dispatch.rb +10 -0
  90. data/spec/rails_app/public/favicon.ico +0 -0
  91. data/spec/rails_app/public/images/rails.png +0 -0
  92. data/spec/rails_app/public/index.html +277 -0
  93. data/spec/rails_app/public/javascripts/application.js +2 -0
  94. data/spec/rails_app/public/javascripts/controls.js +963 -0
  95. data/spec/rails_app/public/javascripts/dragdrop.js +972 -0
  96. data/spec/rails_app/public/javascripts/effects.js +1120 -0
  97. data/spec/rails_app/public/javascripts/prototype.js +4225 -0
  98. data/spec/rails_app/public/robots.txt +5 -0
  99. data/spec/rails_app/script/about +3 -0
  100. data/spec/rails_app/script/console +3 -0
  101. data/spec/rails_app/script/destroy +3 -0
  102. data/spec/rails_app/script/generate +3 -0
  103. data/spec/rails_app/script/performance/benchmarker +3 -0
  104. data/spec/rails_app/script/performance/profiler +3 -0
  105. data/spec/rails_app/script/performance/request +3 -0
  106. data/spec/rails_app/script/plugin +3 -0
  107. data/spec/rails_app/script/process/inspector +3 -0
  108. data/spec/rails_app/script/process/reaper +3 -0
  109. data/spec/rails_app/script/process/spawner +3 -0
  110. data/spec/rails_app/script/runner +3 -0
  111. data/spec/rails_app/script/server +3 -0
  112. data/spec/request/mongrel_spec.rb +39 -0
  113. data/spec/request/parser_spec.rb +215 -0
  114. data/spec/request/persistent_spec.rb +35 -0
  115. data/spec/request/processing_spec.rb +45 -0
  116. data/spec/response_spec.rb +91 -0
  117. data/spec/runner_spec.rb +168 -0
  118. data/spec/server/builder_spec.rb +44 -0
  119. data/spec/server/pipelining_spec.rb +110 -0
  120. data/spec/server/robustness_spec.rb +34 -0
  121. data/spec/server/stopping_spec.rb +55 -0
  122. data/spec/server/swiftiply.yml +6 -0
  123. data/spec/server/swiftiply_spec.rb +32 -0
  124. data/spec/server/tcp_spec.rb +57 -0
  125. data/spec/server/threaded_spec.rb +27 -0
  126. data/spec/server/unix_socket_spec.rb +26 -0
  127. data/spec/server_spec.rb +96 -0
  128. data/spec/spec_helper.rb +219 -0
  129. data/tasks/announce.rake +22 -0
  130. data/tasks/deploy.rake +13 -0
  131. data/tasks/email.erb +30 -0
  132. data/tasks/gem.rake +74 -0
  133. data/tasks/rdoc.rake +25 -0
  134. data/tasks/site.rake +15 -0
  135. data/tasks/spec.rake +49 -0
  136. data/tasks/stats.rake +28 -0
  137. metadata +246 -0
@@ -0,0 +1,183 @@
1
+ require 'yaml'
2
+
3
+ module Thin
4
+ # Error raised that will abort the process and print not backtrace.
5
+ class RunnerError < RuntimeError; end
6
+
7
+ # Raised when a mandatory option is missing to run a command.
8
+ class OptionRequired < RunnerError
9
+ def initialize(option)
10
+ super("#{option} option required")
11
+ end
12
+ end
13
+
14
+ # Raised when an option is not valid.
15
+ class InvalidOption < RunnerError; end
16
+
17
+ # Build and control Thin servers.
18
+ # Hey Controller pattern is not only for web apps yo!
19
+ module Controllers
20
+ # Controls one Thin server.
21
+ # Allow to start, stop, restart and configure a single thin server.
22
+ class Controller
23
+ include Logging
24
+
25
+ # Command line options passed to the thin script
26
+ attr_accessor :options
27
+
28
+ def initialize(options)
29
+ @options = options
30
+
31
+ if @options[:socket]
32
+ @options.delete(:address)
33
+ @options.delete(:port)
34
+ end
35
+ end
36
+
37
+ def start
38
+ # Constantize backend class
39
+ @options[:backend] = eval(@options[:backend], TOPLEVEL_BINDING) if @options[:backend]
40
+
41
+ server = Server.new(@options[:socket] || @options[:address], # Server detects kind of socket
42
+ @options[:port], # Port ignored on UNIX socket
43
+ @options)
44
+
45
+ # Set options
46
+ server.pid_file = @options[:pid]
47
+ server.log_file = @options[:log]
48
+ server.timeout = @options[:timeout]
49
+ server.maximum_connections = @options[:max_conns]
50
+ server.maximum_persistent_connections = @options[:max_persistent_conns]
51
+ server.threaded = @options[:threaded]
52
+ server.no_epoll = @options[:no_epoll] if server.backend.respond_to?(:no_epoll=)
53
+
54
+ # Detach the process, after this line the current process returns
55
+ server.daemonize if @options[:daemonize]
56
+
57
+ # +config+ must be called before changing privileges since it might require superuser power.
58
+ server.config
59
+
60
+ server.change_privilege @options[:user], @options[:group] if @options[:user] && @options[:group]
61
+
62
+ # If a Rack config file is specified we eval it inside a Rack::Builder block to create
63
+ # a Rack adapter from it. Or else we guess which adapter to use and load it.
64
+ if @options[:rackup]
65
+ server.app = load_rackup_config
66
+ else
67
+ server.app = load_adapter
68
+ end
69
+
70
+ # If a prefix is required, wrap in Rack URL mapper
71
+ server.app = Rack::URLMap.new(@options[:prefix] => server.app) if @options[:prefix]
72
+
73
+ # If a stats URL is specified, wrap in Stats adapter
74
+ server.app = Stats::Adapter.new(server.app, @options[:stats]) if @options[:stats]
75
+
76
+ # Register restart procedure which just start another process with same options,
77
+ # so that's why this is done here.
78
+ server.on_restart { Command.run(:start, @options) }
79
+
80
+ server.start
81
+ end
82
+
83
+ def stop
84
+ raise OptionRequired, :pid unless @options[:pid]
85
+
86
+ tail_log(@options[:log]) do
87
+ if Server.kill(@options[:pid], @options[:force] ? 0 : (@options[:timeout] || 60))
88
+ wait_for_file :deletion, @options[:pid]
89
+ end
90
+ end
91
+ end
92
+
93
+ def restart
94
+ raise OptionRequired, :pid unless @options[:pid]
95
+
96
+ tail_log(@options[:log]) do
97
+ if Server.restart(@options[:pid])
98
+ wait_for_file :creation, @options[:pid]
99
+ end
100
+ end
101
+ end
102
+
103
+ def config
104
+ config_file = @options.delete(:config) || raise(OptionRequired, :config)
105
+
106
+ # Stringify keys
107
+ @options.keys.each { |o| @options[o.to_s] = @options.delete(o) }
108
+
109
+ File.open(config_file, 'w') { |f| f << @options.to_yaml }
110
+ log ">> Wrote configuration to #{config_file}"
111
+ end
112
+
113
+ protected
114
+ # Wait for a pid file to either be created or deleted.
115
+ def wait_for_file(state, file)
116
+ Timeout.timeout(@options[:timeout] || 30) do
117
+ case state
118
+ when :creation then sleep 0.1 until File.exist?(file)
119
+ when :deletion then sleep 0.1 while File.exist?(file)
120
+ end
121
+ end
122
+ end
123
+
124
+ # Tail the log file of server +number+ during the execution of the block.
125
+ def tail_log(log_file)
126
+ if log_file
127
+ tail_thread = tail(log_file)
128
+ yield
129
+ tail_thread.kill
130
+ else
131
+ yield
132
+ end
133
+ end
134
+
135
+ # Acts like GNU tail command. Taken from Rails.
136
+ def tail(file)
137
+ cursor = File.exist?(file) ? File.size(file) : 0
138
+ last_checked = Time.now
139
+ tail_thread = Thread.new do
140
+ Thread.pass until File.exist?(file)
141
+ File.open(file, 'r') do |f|
142
+ loop do
143
+ f.seek cursor
144
+ if f.mtime > last_checked
145
+ last_checked = f.mtime
146
+ contents = f.read
147
+ cursor += contents.length
148
+ print contents
149
+ STDOUT.flush
150
+ end
151
+ sleep 0.1
152
+ end
153
+ end
154
+ end
155
+ sleep 1 if File.exist?(file) # HACK Give the thread a little time to open the file
156
+ tail_thread
157
+ end
158
+
159
+ private
160
+ def load_adapter
161
+ adapter = @options[:adapter] || Rack::Adapter.guess(@options[:chdir])
162
+ log ">> Using #{adapter} adapter"
163
+ Rack::Adapter.for(adapter, @options)
164
+ rescue Rack::AdapterNotFound => e
165
+ raise InvalidOption, e.message
166
+ end
167
+
168
+ def load_rackup_config
169
+ ENV['RACK_ENV'] = @options[:environment]
170
+ case @options[:rackup]
171
+ when /\.rb$/
172
+ Kernel.load(@options[:rackup])
173
+ Object.const_get(File.basename(@options[:rackup], '.rb').capitalize.to_sym)
174
+ when /\.ru$/
175
+ rackup_code = File.read(@options[:rackup])
176
+ eval("Rack::Builder.new {( #{rackup_code}\n )}.to_app", TOPLEVEL_BINDING, @options[:rackup])
177
+ else
178
+ raise "Invalid rackup file. please specify either a .ru or .rb file"
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,75 @@
1
+ require 'erb'
2
+
3
+ module Thin
4
+ module Controllers
5
+ # System service controller to launch all servers which
6
+ # config files are in a directory.
7
+ class Service < Controller
8
+ INITD_PATH = '/etc/init.d/thin'
9
+ DEFAULT_CONFIG_PATH = '/etc/thin'
10
+ TEMPLATE = File.dirname(__FILE__) + '/service.sh.erb'
11
+
12
+ def initialize(options)
13
+ super
14
+
15
+ raise PlatformNotSupported, 'Running as a service only supported on Linux' unless Thin.linux?
16
+ end
17
+
18
+ def config_path
19
+ @options[:all] || DEFAULT_CONFIG_PATH
20
+ end
21
+
22
+ def start
23
+ run :start
24
+ end
25
+
26
+ def stop
27
+ run :stop
28
+ end
29
+
30
+ def restart
31
+ run :restart
32
+ end
33
+
34
+ def install(config_files_path=DEFAULT_CONFIG_PATH)
35
+ if File.exist?(INITD_PATH)
36
+ log ">> Thin service already installed at #{INITD_PATH}"
37
+ else
38
+ log ">> Installing thin service at #{INITD_PATH} ..."
39
+ sh "mkdir -p #{File.dirname(INITD_PATH)}"
40
+ log "writing #{INITD_PATH}"
41
+ File.open(INITD_PATH, 'w') do |f|
42
+ f << ERB.new(File.read(TEMPLATE)).result(binding)
43
+ end
44
+ sh "chmod +x #{INITD_PATH}" # Make executable
45
+ end
46
+
47
+ sh "mkdir -p #{config_files_path}"
48
+
49
+ log ''
50
+ log "To configure thin to start at system boot:"
51
+ log "on RedHat like systems:"
52
+ log " sudo /sbin/chkconfig --level 345 #{NAME} on"
53
+ log "on Debian-like systems (Ubuntu):"
54
+ log " sudo /usr/sbin/update-rc.d -f #{NAME} defaults"
55
+ log "on Gentoo:"
56
+ log " sudo rc-update add #{NAME} default"
57
+ log ''
58
+ log "Then put your config files in #{config_files_path}"
59
+ end
60
+
61
+ private
62
+ def run(command)
63
+ Dir[config_path + '/*'].each do |config|
64
+ log "[#{command}] #{config} ..."
65
+ Command.run(command, :config => config, :daemonize => true)
66
+ end
67
+ end
68
+
69
+ def sh(cmd)
70
+ log cmd
71
+ system(cmd)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,39 @@
1
+ #!/bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides: thin
4
+ # Required-Start: $local_fs $remote_fs
5
+ # Required-Stop: $local_fs $remote_fs
6
+ # Default-Start: 2 3 4 5
7
+ # Default-Stop: S 0 1 6
8
+ # Short-Description: thin initscript
9
+ # Description: thin
10
+ ### END INIT INFO
11
+
12
+ # Original author: Forrest Robertson
13
+
14
+ # Do NOT "set -e"
15
+
16
+ DAEMON=<%= Command.script %>
17
+ SCRIPT_NAME=<%= INITD_PATH %>
18
+ CONFIG_PATH=<%= config_files_path %>
19
+
20
+ # Exit if the package is not installed
21
+ [ -x "$DAEMON" ] || exit 0
22
+
23
+ case "$1" in
24
+ start)
25
+ $DAEMON start --all $CONFIG_PATH
26
+ ;;
27
+ stop)
28
+ $DAEMON stop --all $CONFIG_PATH
29
+ ;;
30
+ restart)
31
+ $DAEMON restart --all $CONFIG_PATH
32
+ ;;
33
+ *)
34
+ echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
35
+ exit 3
36
+ ;;
37
+ esac
38
+
39
+ :
@@ -0,0 +1,174 @@
1
+ require 'etc'
2
+ require 'daemons' unless Thin.win?
3
+
4
+ module Process
5
+ # Returns +true+ the process identied by +pid+ is running.
6
+ def running?(pid)
7
+ Process.getpgid(pid) != -1
8
+ rescue Errno::ESRCH
9
+ false
10
+ end
11
+ module_function :running?
12
+ end
13
+
14
+ module Thin
15
+ # Raised when the pid file already exist starting as a daemon.
16
+ class PidFileExist < RuntimeError; end
17
+
18
+ # Module included in classes that can be turned into a daemon.
19
+ # Handle stuff like:
20
+ # * storing the PID in a file
21
+ # * redirecting output to the log file
22
+ # * changing processs privileges
23
+ # * killing the process gracefully
24
+ module Daemonizable
25
+ attr_accessor :pid_file, :log_file
26
+
27
+ def self.included(base)
28
+ base.extend ClassMethods
29
+ end
30
+
31
+ def pid
32
+ File.exist?(pid_file) ? open(pid_file).read.to_i : nil
33
+ end
34
+
35
+ # Turns the current script into a daemon process that detaches from the console.
36
+ def daemonize
37
+ raise PlatformNotSupported, 'Daemonizing is not supported on Windows' if Thin.win?
38
+ raise ArgumentError, 'You must specify a pid_file to daemonize' unless @pid_file
39
+
40
+ remove_stale_pid_file
41
+
42
+ pwd = Dir.pwd # Current directory is changed during daemonization, so store it
43
+
44
+ Daemonize.daemonize(File.expand_path(@log_file), name)
45
+
46
+ Dir.chdir(pwd)
47
+
48
+ write_pid_file
49
+
50
+ trap('HUP') { restart }
51
+ at_exit do
52
+ log ">> Exiting!"
53
+ remove_pid_file
54
+ end
55
+ end
56
+
57
+ # Change privileges of the process
58
+ # to the specified user and group.
59
+ def change_privilege(user, group=user)
60
+ log ">> Changing process privilege to #{user}:#{group}"
61
+
62
+ uid, gid = Process.euid, Process.egid
63
+ target_uid = Etc.getpwnam(user).uid
64
+ target_gid = Etc.getgrnam(group).gid
65
+
66
+ if uid != target_uid || gid != target_gid
67
+ # Change process ownership
68
+ Process.initgroups(user, target_gid)
69
+ Process::GID.change_privilege(target_gid)
70
+ Process::UID.change_privilege(target_uid)
71
+ end
72
+ rescue Errno::EPERM => e
73
+ log "Couldn't change user and group to #{user}:#{group}: #{e}"
74
+ end
75
+
76
+ # Register a proc to be called to restart the server.
77
+ def on_restart(&block)
78
+ @on_restart = block
79
+ end
80
+
81
+ # Restart the server.
82
+ def restart
83
+ raise ArgumentError, "Can't restart, no 'on_restart' proc specified" unless @on_restart
84
+ log '>> Restarting ...'
85
+ stop
86
+ remove_pid_file
87
+ @on_restart.call
88
+ exit!
89
+ end
90
+
91
+ module ClassMethods
92
+ # Send a QUIT or INT (if timeout is +0+) signal the process which
93
+ # PID is stored in +pid_file+.
94
+ # If the process is still running after +timeout+, KILL signal is
95
+ # sent.
96
+ def kill(pid_file, timeout=60)
97
+ if timeout == 0
98
+ send_signal('INT', pid_file, timeout)
99
+ else
100
+ send_signal('QUIT', pid_file, timeout)
101
+ end
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, timeout=60)
111
+ if pid = read_pid_file(pid_file)
112
+ Logging.log "Sending #{signal} signal to process #{pid} ... "
113
+ Process.kill(signal, pid)
114
+ Timeout.timeout(timeout) do
115
+ sleep 0.1 while Process.running?(pid)
116
+ end
117
+ else
118
+ Logging.log "Can't stop process, no PID found in #{pid_file}"
119
+ end
120
+ rescue Timeout::Error
121
+ Logging.log "Timeout!"
122
+ force_kill pid_file
123
+ rescue Interrupt
124
+ force_kill pid_file
125
+ rescue Errno::ESRCH # No such process
126
+ Logging.log "process not found!"
127
+ force_kill pid_file
128
+ end
129
+
130
+ def force_kill(pid_file)
131
+ if pid = read_pid_file(pid_file)
132
+ Logging.log "Sending KILL signal to process #{pid} ... "
133
+ Process.kill("KILL", pid)
134
+ File.delete(pid_file) if File.exist?(pid_file)
135
+ else
136
+ Logging.log "Can't stop process, no PID found in #{pid_file}"
137
+ end
138
+ end
139
+
140
+ def read_pid_file(file)
141
+ if File.file?(file) && pid = File.read(file)
142
+ pid.to_i
143
+ else
144
+ nil
145
+ end
146
+ end
147
+ end
148
+
149
+ protected
150
+ def remove_pid_file
151
+ File.delete(@pid_file) if @pid_file && File.exists?(@pid_file)
152
+ end
153
+
154
+ def write_pid_file
155
+ log ">> Writing PID to #{@pid_file}"
156
+ FileUtils.mkdir_p File.dirname(@pid_file)
157
+ open(@pid_file,"w") { |f| f.write(Process.pid) }
158
+ File.chmod(0644, @pid_file)
159
+ end
160
+
161
+ # If PID file is stale, remove it.
162
+ def remove_stale_pid_file
163
+ if File.exist?(@pid_file)
164
+ if pid && Process.running?(pid)
165
+ raise PidFileExist, "#{@pid_file} already exists, seems like it's already running (process ID: #{pid}). " +
166
+ "Stop the process or delete #{@pid_file}."
167
+ else
168
+ log ">> Deleting stale PID file #{@pid_file}"
169
+ remove_pid_file
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end