giraffesoft-unicorn 0.93.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +16 -0
  3. data/.gitignore +20 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +31 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +167 -0
  11. data/Documentation/unicorn_rails.1.txt +169 -0
  12. data/GIT-VERSION-GEN +40 -0
  13. data/GNUmakefile +270 -0
  14. data/HACKING +113 -0
  15. data/KNOWN_ISSUES +40 -0
  16. data/LICENSE +55 -0
  17. data/PHILOSOPHY +144 -0
  18. data/README +153 -0
  19. data/Rakefile +108 -0
  20. data/SIGNALS +97 -0
  21. data/TODO +16 -0
  22. data/TUNING +70 -0
  23. data/bin/unicorn +165 -0
  24. data/bin/unicorn_rails +208 -0
  25. data/examples/echo.ru +27 -0
  26. data/examples/git.ru +13 -0
  27. data/examples/init.sh +53 -0
  28. data/ext/unicorn_http/c_util.h +107 -0
  29. data/ext/unicorn_http/common_field_optimization.h +111 -0
  30. data/ext/unicorn_http/ext_help.h +73 -0
  31. data/ext/unicorn_http/extconf.rb +14 -0
  32. data/ext/unicorn_http/global_variables.h +91 -0
  33. data/ext/unicorn_http/unicorn_http.rl +715 -0
  34. data/ext/unicorn_http/unicorn_http_common.rl +74 -0
  35. data/lib/unicorn.rb +730 -0
  36. data/lib/unicorn/app/exec_cgi.rb +150 -0
  37. data/lib/unicorn/app/inetd.rb +109 -0
  38. data/lib/unicorn/app/old_rails.rb +31 -0
  39. data/lib/unicorn/app/old_rails/static.rb +60 -0
  40. data/lib/unicorn/cgi_wrapper.rb +145 -0
  41. data/lib/unicorn/configurator.rb +403 -0
  42. data/lib/unicorn/const.rb +37 -0
  43. data/lib/unicorn/http_request.rb +74 -0
  44. data/lib/unicorn/http_response.rb +74 -0
  45. data/lib/unicorn/launcher.rb +39 -0
  46. data/lib/unicorn/socket_helper.rb +138 -0
  47. data/lib/unicorn/tee_input.rb +174 -0
  48. data/lib/unicorn/util.rb +64 -0
  49. data/local.mk.sample +53 -0
  50. data/setup.rb +1586 -0
  51. data/test/aggregate.rb +15 -0
  52. data/test/benchmark/README +50 -0
  53. data/test/benchmark/dd.ru +18 -0
  54. data/test/exec/README +5 -0
  55. data/test/exec/test_exec.rb +855 -0
  56. data/test/rails/app-1.2.3/.gitignore +2 -0
  57. data/test/rails/app-1.2.3/Rakefile +7 -0
  58. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  59. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  60. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  61. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  62. data/test/rails/app-1.2.3/config/database.yml +12 -0
  63. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  64. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  65. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  66. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  67. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  68. data/test/rails/app-1.2.3/public/404.html +1 -0
  69. data/test/rails/app-1.2.3/public/500.html +1 -0
  70. data/test/rails/app-2.0.2/.gitignore +2 -0
  71. data/test/rails/app-2.0.2/Rakefile +7 -0
  72. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  73. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  74. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  75. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  76. data/test/rails/app-2.0.2/config/database.yml +12 -0
  77. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  78. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  79. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  80. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  81. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  82. data/test/rails/app-2.0.2/public/404.html +1 -0
  83. data/test/rails/app-2.0.2/public/500.html +1 -0
  84. data/test/rails/app-2.1.2/.gitignore +2 -0
  85. data/test/rails/app-2.1.2/Rakefile +7 -0
  86. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  87. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  88. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  89. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  90. data/test/rails/app-2.1.2/config/database.yml +12 -0
  91. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  92. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  93. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  94. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  95. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  96. data/test/rails/app-2.1.2/public/404.html +1 -0
  97. data/test/rails/app-2.1.2/public/500.html +1 -0
  98. data/test/rails/app-2.2.2/.gitignore +2 -0
  99. data/test/rails/app-2.2.2/Rakefile +7 -0
  100. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  101. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  102. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  103. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  104. data/test/rails/app-2.2.2/config/database.yml +12 -0
  105. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  106. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  107. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  108. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  109. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  110. data/test/rails/app-2.2.2/public/404.html +1 -0
  111. data/test/rails/app-2.2.2/public/500.html +1 -0
  112. data/test/rails/app-2.3.3.1/.gitignore +2 -0
  113. data/test/rails/app-2.3.3.1/Rakefile +7 -0
  114. data/test/rails/app-2.3.3.1/app/controllers/application_controller.rb +5 -0
  115. data/test/rails/app-2.3.3.1/app/controllers/foo_controller.rb +36 -0
  116. data/test/rails/app-2.3.3.1/app/helpers/application_helper.rb +4 -0
  117. data/test/rails/app-2.3.3.1/config/boot.rb +109 -0
  118. data/test/rails/app-2.3.3.1/config/database.yml +12 -0
  119. data/test/rails/app-2.3.3.1/config/environment.rb +17 -0
  120. data/test/rails/app-2.3.3.1/config/environments/development.rb +7 -0
  121. data/test/rails/app-2.3.3.1/config/environments/production.rb +6 -0
  122. data/test/rails/app-2.3.3.1/config/routes.rb +6 -0
  123. data/test/rails/app-2.3.3.1/db/.gitignore +0 -0
  124. data/test/rails/app-2.3.3.1/public/404.html +1 -0
  125. data/test/rails/app-2.3.3.1/public/500.html +1 -0
  126. data/test/rails/app-2.3.3.1/public/x.txt +1 -0
  127. data/test/rails/test_rails.rb +280 -0
  128. data/test/test_helper.rb +296 -0
  129. data/test/unit/test_configurator.rb +150 -0
  130. data/test/unit/test_http_parser.rb +492 -0
  131. data/test/unit/test_http_parser_ng.rb +308 -0
  132. data/test/unit/test_request.rb +184 -0
  133. data/test/unit/test_response.rb +110 -0
  134. data/test/unit/test_server.rb +188 -0
  135. data/test/unit/test_signals.rb +202 -0
  136. data/test/unit/test_socket_helper.rb +133 -0
  137. data/test/unit/test_tee_input.rb +229 -0
  138. data/test/unit/test_upload.rb +297 -0
  139. data/test/unit/test_util.rb +96 -0
  140. data/unicorn.gemspec +42 -0
  141. metadata +228 -0
@@ -0,0 +1,74 @@
1
+ %%{
2
+
3
+ machine unicorn_http_common;
4
+
5
+ #### HTTP PROTOCOL GRAMMAR
6
+ # line endings
7
+ CRLF = "\r\n";
8
+
9
+ # character types
10
+ CTL = (cntrl | 127);
11
+ safe = ("$" | "-" | "_" | ".");
12
+ extra = ("!" | "*" | "'" | "(" | ")" | ",");
13
+ reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+");
14
+ sorta_safe = ("\"" | "<" | ">");
15
+ unsafe = (CTL | " " | "#" | "%" | sorta_safe);
16
+ national = any -- (alpha | digit | reserved | extra | safe | unsafe);
17
+ unreserved = (alpha | digit | safe | extra | national);
18
+ escape = ("%" xdigit xdigit);
19
+ uchar = (unreserved | escape | sorta_safe);
20
+ pchar = (uchar | ":" | "@" | "&" | "=" | "+");
21
+ tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
22
+ lws = (" " | "\t");
23
+
24
+ # elements
25
+ token = (ascii -- (CTL | tspecials));
26
+
27
+ # URI schemes and absolute paths
28
+ scheme = ( "http"i ("s"i)? ) $downcase_char >mark %scheme;
29
+ hostname = (alnum | "-" | "." | "_")+;
30
+ host_with_port = (hostname (":" digit*)?) >mark %host;
31
+
32
+ path = ( pchar+ ( "/" pchar* )* ) ;
33
+ query = ( uchar | reserved )* %query_string ;
34
+ param = ( pchar | "/" )* ;
35
+ params = ( param ( ";" param )* ) ;
36
+ rel_path = ( path? %request_path (";" params)? ) ("?" %start_query query)?;
37
+ absolute_path = ( "/"+ rel_path );
38
+ path_uri = absolute_path > mark %request_uri;
39
+ Absolute_URI = (scheme "://" host_with_port path_uri);
40
+
41
+ Request_URI = ((absolute_path | "*") >mark %request_uri) | Absolute_URI;
42
+ Fragment = ( uchar | reserved )* >mark %fragment;
43
+ Method = (token){1,20} >mark %request_method;
44
+ GetOnly = "GET" >mark %request_method;
45
+
46
+ http_number = ( digit+ "." digit+ ) ;
47
+ HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ;
48
+ Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ;
49
+
50
+ field_name = ( token -- ":" )+ >start_field $snake_upcase_field %write_field;
51
+
52
+ field_value = any* >start_value %write_value;
53
+
54
+ value_cont = lws+ any* >start_value %write_cont_value;
55
+
56
+ message_header = ((field_name ":" " "* field_value)|value_cont) :> CRLF;
57
+ chunk_ext_val = token*;
58
+ chunk_ext_name = token*;
59
+ chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
60
+ last_chunk = "0"+ chunk_extension CRLF;
61
+ chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size;
62
+ chunk_end = CRLF;
63
+ chunk_body = any >skip_chunk_data;
64
+ chunk_begin = chunk_size chunk_extension CRLF;
65
+ chunk = chunk_begin chunk_body chunk_end;
66
+ ChunkedBody := chunk* last_chunk @end_chunked_body;
67
+ Trailers := (message_header)* CRLF @end_trailers;
68
+
69
+ FullRequest = Request_Line (message_header)* CRLF @header_done;
70
+ SimpleRequest = GetOnly " " Request_URI ("#"Fragment){0,1} CRLF @header_done;
71
+
72
+ main := FullRequest | SimpleRequest;
73
+
74
+ }%%
data/lib/unicorn.rb ADDED
@@ -0,0 +1,730 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ require 'fcntl'
4
+ require 'unicorn/socket_helper'
5
+ autoload :Rack, 'rack'
6
+
7
+ # Unicorn module containing all of the classes (include C extensions) for running
8
+ # a Unicorn web server. It contains a minimalist HTTP server with just enough
9
+ # functionality to service web application requests fast as possible.
10
+ module Unicorn
11
+ autoload :Const, 'unicorn/const'
12
+ autoload :HttpRequest, 'unicorn/http_request'
13
+ autoload :HttpResponse, 'unicorn/http_response'
14
+ autoload :Configurator, 'unicorn/configurator'
15
+ autoload :TeeInput, 'unicorn/tee_input'
16
+ autoload :Util, 'unicorn/util'
17
+
18
+ class << self
19
+ def run(app, options = {})
20
+ HttpServer.new(app, options).start.join
21
+ end
22
+ end
23
+
24
+ # This is the process manager of Unicorn. This manages worker
25
+ # processes which in turn handle the I/O and application process.
26
+ # Listener sockets are started in the master process and shared with
27
+ # forked worker children.
28
+
29
+ class HttpServer < Struct.new(:listener_opts, :timeout, :worker_processes,
30
+ :before_fork, :after_fork, :before_exec,
31
+ :logger, :pid, :app, :preload_app,
32
+ :reexec_pid, :orig_app, :init_listeners,
33
+ :master_pid, :config)
34
+ include ::Unicorn::SocketHelper
35
+
36
+ # prevents IO objects in here from being GC-ed
37
+ IO_PURGATORY = []
38
+
39
+ # all bound listener sockets
40
+ LISTENERS = []
41
+
42
+ # This hash maps PIDs to Workers
43
+ WORKERS = {}
44
+
45
+ # We use SELF_PIPE differently in the master and worker processes:
46
+ #
47
+ # * The master process never closes or reinitializes this once
48
+ # initialized. Signal handlers in the master process will write to
49
+ # it to wake up the master from IO.select in exactly the same manner
50
+ # djb describes in http://cr.yp.to/docs/selfpipe.html
51
+ #
52
+ # * The workers immediately close the pipe they inherit from the
53
+ # master and replace it with a new pipe after forking. This new
54
+ # pipe is also used to wakeup from IO.select from inside (worker)
55
+ # signal handlers. However, workers *close* the pipe descriptors in
56
+ # the signal handlers to raise EBADF in IO.select instead of writing
57
+ # like we do in the master. We cannot easily use the reader set for
58
+ # IO.select because LISTENERS is already that set, and it's extra
59
+ # work (and cycles) to distinguish the pipe FD from the reader set
60
+ # once IO.select returns. So we're lazy and just close the pipe when
61
+ # a (rare) signal arrives in the worker and reinitialize the pipe later.
62
+ SELF_PIPE = []
63
+
64
+ # signal queue used for self-piping
65
+ SIG_QUEUE = []
66
+
67
+ # constant lookups are faster and we're single-threaded/non-reentrant
68
+ REQUEST = HttpRequest.new
69
+
70
+ # We populate this at startup so we can figure out how to reexecute
71
+ # and upgrade the currently running instance of Unicorn
72
+ # This Hash is considered a stable interface and changing its contents
73
+ # will allow you to switch between different installations of Unicorn
74
+ # or even different installations of the same applications without
75
+ # downtime. Keys of this constant Hash are described as follows:
76
+ #
77
+ # * 0 - the path to the unicorn/unicorn_rails executable
78
+ # * :argv - a deep copy of the ARGV array the executable originally saw
79
+ # * :cwd - the working directory of the application, this is where
80
+ # you originally started Unicorn.
81
+ #
82
+ # The following example may be used in your Unicorn config file to
83
+ # change your working directory during a config reload (HUP) without
84
+ # upgrading or restarting:
85
+ #
86
+ # Dir.chdir(Unicorn::HttpServer::START_CTX[:cwd] = path)
87
+ #
88
+ # To change your unicorn executable to a different path without downtime,
89
+ # you can set the following in your Unicorn config file, HUP and then
90
+ # continue with the traditional USR2 + QUIT upgrade steps:
91
+ #
92
+ # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/1.9.2/bin/unicorn"
93
+ START_CTX = {
94
+ :argv => ARGV.map { |arg| arg.dup },
95
+ :cwd => lambda {
96
+ # favor ENV['PWD'] since it is (usually) symlink aware for
97
+ # Capistrano and like systems
98
+ begin
99
+ a = File.stat(pwd = ENV['PWD'])
100
+ b = File.stat(Dir.pwd)
101
+ a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
102
+ rescue
103
+ Dir.pwd
104
+ end
105
+ }.call,
106
+ 0 => $0.dup,
107
+ }
108
+
109
+ # This class and its members can be considered a stable interface
110
+ # and will not change in a backwards-incompatible fashion between
111
+ # releases of Unicorn. You may need to access it in the
112
+ # before_fork/after_fork hooks. See the Unicorn::Configurator RDoc
113
+ # for examples.
114
+ class Worker < Struct.new(:nr, :tmp)
115
+
116
+ # worker objects may be compared to just plain numbers
117
+ def ==(other_nr)
118
+ self.nr == other_nr
119
+ end
120
+ end
121
+
122
+ # Creates a working server on host:port (strange things happen if
123
+ # port isn't a Number). Use HttpServer::run to start the server and
124
+ # HttpServer.run.join to join the thread that's processing
125
+ # incoming requests on the socket.
126
+ def initialize(app, options = {})
127
+ self.app = app
128
+ self.reexec_pid = 0
129
+ self.init_listeners = options[:listeners] ? options[:listeners].dup : []
130
+ self.config = Configurator.new(options.merge(:use_defaults => true))
131
+ self.listener_opts = {}
132
+
133
+ # we try inheriting listeners first, so we bind them later.
134
+ # we don't write the pid file until we've bound listeners in case
135
+ # unicorn was started twice by mistake. Even though our #pid= method
136
+ # checks for stale/existing pid files, race conditions are still
137
+ # possible (and difficult/non-portable to avoid) and can be likely
138
+ # to clobber the pid if the second start was in quick succession
139
+ # after the first, so we rely on the listener binding to fail in
140
+ # that case. Some tests (in and outside of this source tree) and
141
+ # monitoring tools may also rely on pid files existing before we
142
+ # attempt to connect to the listener(s)
143
+ config.commit!(self, :skip => [:listeners, :pid])
144
+ self.orig_app = app
145
+ end
146
+
147
+ # Runs the thing. Returns self so you can run join on it
148
+ def start
149
+ BasicSocket.do_not_reverse_lookup = true
150
+
151
+ # inherit sockets from parents, they need to be plain Socket objects
152
+ # before they become UNIXServer or TCPServer
153
+ inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
154
+ io = Socket.for_fd(fd.to_i)
155
+ set_server_sockopt(io, listener_opts[sock_name(io)])
156
+ IO_PURGATORY << io
157
+ logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
158
+ server_cast(io)
159
+ end
160
+
161
+ config_listeners = config[:listeners].dup
162
+ LISTENERS.replace(inherited)
163
+
164
+ # we start out with generic Socket objects that get cast to either
165
+ # TCPServer or UNIXServer objects; but since the Socket objects
166
+ # share the same OS-level file descriptor as the higher-level *Server
167
+ # objects; we need to prevent Socket objects from being garbage-collected
168
+ config_listeners -= listener_names
169
+ if config_listeners.empty? && LISTENERS.empty?
170
+ config_listeners << Unicorn::Const::DEFAULT_LISTEN
171
+ init_listeners << Unicorn::Const::DEFAULT_LISTEN
172
+ START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
173
+ end
174
+ config_listeners.each { |addr| listen(addr) }
175
+ raise ArgumentError, "no listeners" if LISTENERS.empty?
176
+ self.pid = config[:pid]
177
+ self.master_pid = $$
178
+ build_app! if preload_app
179
+ maintain_worker_count
180
+ self
181
+ end
182
+
183
+ # replaces current listener set with +listeners+. This will
184
+ # close the socket if it will not exist in the new listener set
185
+ def listeners=(listeners)
186
+ cur_names, dead_names = [], []
187
+ listener_names.each do |name|
188
+ if ?/ == name[0]
189
+ # mark unlinked sockets as dead so we can rebind them
190
+ (File.socket?(name) ? cur_names : dead_names) << name
191
+ else
192
+ cur_names << name
193
+ end
194
+ end
195
+ set_names = listener_names(listeners)
196
+ dead_names.concat(cur_names - set_names).uniq!
197
+
198
+ LISTENERS.delete_if do |io|
199
+ if dead_names.include?(sock_name(io))
200
+ IO_PURGATORY.delete_if do |pio|
201
+ pio.fileno == io.fileno && (pio.close rescue nil).nil? # true
202
+ end
203
+ (io.close rescue nil).nil? # true
204
+ else
205
+ set_server_sockopt(io, listener_opts[sock_name(io)])
206
+ false
207
+ end
208
+ end
209
+
210
+ (set_names - cur_names).each { |addr| listen(addr) }
211
+ end
212
+
213
+ def stdout_path=(path); redirect_io($stdout, path); end
214
+ def stderr_path=(path); redirect_io($stderr, path); end
215
+
216
+ alias_method :set_pid, :pid=
217
+ undef_method :pid=
218
+
219
+ # sets the path for the PID file of the master process
220
+ def pid=(path)
221
+ if path
222
+ if x = valid_pid?(path)
223
+ return path if pid && path == pid && x == $$
224
+ raise ArgumentError, "Already running on PID:#{x} " \
225
+ "(or pid=#{path} is stale)"
226
+ end
227
+ end
228
+ unlink_pid_safe(pid) if pid
229
+
230
+ if path
231
+ fp = begin
232
+ tmp = "#{File.dirname(path)}/#{rand}.#$$"
233
+ File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
234
+ rescue Errno::EEXIST
235
+ retry
236
+ end
237
+ fp.syswrite("#$$\n")
238
+ File.rename(fp.path, path)
239
+ fp.close
240
+ end
241
+ self.set_pid(path)
242
+ end
243
+
244
+ # add a given address to the +listeners+ set, idempotently
245
+ # Allows workers to add a private, per-process listener via the
246
+ # after_fork hook. Very useful for debugging and testing.
247
+ # +:tries+ may be specified as an option for the number of times
248
+ # to retry, and +:delay+ may be specified as the time in seconds
249
+ # to delay between retries.
250
+ # A negative value for +:tries+ indicates the listen will be
251
+ # retried indefinitely, this is useful when workers belonging to
252
+ # different masters are spawned during a transparent upgrade.
253
+ def listen(address, opt = {}.merge(listener_opts[address] || {}))
254
+ address = config.expand_addr(address)
255
+ return if String === address && listener_names.include?(address)
256
+
257
+ delay = opt[:delay] || 0.5
258
+ tries = opt[:tries] || 5
259
+ begin
260
+ io = bind_listen(address, opt)
261
+ unless TCPServer === io || UNIXServer === io
262
+ IO_PURGATORY << io
263
+ io = server_cast(io)
264
+ end
265
+ logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
266
+ LISTENERS << io
267
+ return io
268
+ rescue Errno::EADDRINUSE => err
269
+ logger.error "adding listener failed addr=#{address} (in use)"
270
+ raise err if tries == 0
271
+ tries -= 1
272
+ logger.error "retrying in #{delay} seconds " \
273
+ "(#{tries < 0 ? 'infinite' : tries} tries left)"
274
+ sleep(delay)
275
+ retry
276
+ end
277
+ end
278
+
279
+ # monitors children and receives signals forever
280
+ # (or until a termination signal is sent). This handles signals
281
+ # one-at-a-time time and we'll happily drop signals in case somebody
282
+ # is signalling us too often.
283
+ def join
284
+ # this pipe is used to wake us up from select(2) in #join when signals
285
+ # are trapped. See trap_deferred
286
+ init_self_pipe!
287
+ respawn = true
288
+ last_check = Time.now
289
+
290
+ QUEUE_SIGS.each { |sig| trap_deferred(sig) }
291
+ trap(:CHLD) { |sig_nr| awaken_master }
292
+ proc_name 'master'
293
+ logger.info "master process ready" # test_exec.rb relies on this message
294
+ begin
295
+ loop do
296
+ reap_all_workers
297
+ case SIG_QUEUE.shift
298
+ when nil
299
+ # avoid murdering workers after our master process (or the
300
+ # machine) comes out of suspend/hibernation
301
+ if (last_check + timeout) >= (last_check = Time.now)
302
+ murder_lazy_workers
303
+ end
304
+ maintain_worker_count if respawn
305
+ master_sleep
306
+ when :QUIT # graceful shutdown
307
+ break
308
+ when :TERM, :INT # immediate shutdown
309
+ stop(false)
310
+ break
311
+ when :USR1 # rotate logs
312
+ logger.info "master reopening logs..."
313
+ Unicorn::Util.reopen_logs
314
+ logger.info "master done reopening logs"
315
+ kill_each_worker(:USR1)
316
+ when :USR2 # exec binary, stay alive in case something went wrong
317
+ reexec
318
+ when :WINCH
319
+ if Process.ppid == 1 || Process.getpgrp != $$
320
+ respawn = false
321
+ logger.info "gracefully stopping all workers"
322
+ kill_each_worker(:QUIT)
323
+ else
324
+ logger.info "SIGWINCH ignored because we're not daemonized"
325
+ end
326
+ when :TTIN
327
+ self.worker_processes += 1
328
+ when :TTOU
329
+ self.worker_processes -= 1 if self.worker_processes > 0
330
+ when :HUP
331
+ respawn = true
332
+ if config.config_file
333
+ load_config!
334
+ redo # immediate reaping since we may have QUIT workers
335
+ else # exec binary and exit if there's no config file
336
+ logger.info "config_file not present, reexecuting binary"
337
+ reexec
338
+ break
339
+ end
340
+ end
341
+ end
342
+ rescue Errno::EINTR
343
+ retry
344
+ rescue Object => e
345
+ logger.error "Unhandled master loop exception #{e.inspect}."
346
+ logger.error e.backtrace.join("\n")
347
+ retry
348
+ end
349
+ stop # gracefully shutdown all workers on our way out
350
+ logger.info "master complete"
351
+ unlink_pid_safe(pid) if pid
352
+ end
353
+
354
+ # Terminates all workers, but does not exit master process
355
+ def stop(graceful = true)
356
+ self.listeners = []
357
+ limit = Time.now + timeout
358
+ until WORKERS.empty? || Time.now > limit
359
+ kill_each_worker(graceful ? :QUIT : :TERM)
360
+ sleep(0.1)
361
+ reap_all_workers
362
+ end
363
+ kill_each_worker(:KILL)
364
+ end
365
+
366
+ private
367
+
368
+ # list of signals we care about and trap in master.
369
+ QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP,
370
+ :TTIN, :TTOU ]
371
+
372
+ # defer a signal for later processing in #join (master process)
373
+ def trap_deferred(signal)
374
+ trap(signal) do |sig_nr|
375
+ if SIG_QUEUE.size < 5
376
+ SIG_QUEUE << signal
377
+ awaken_master
378
+ else
379
+ logger.error "ignoring SIG#{signal}, queue=#{SIG_QUEUE.inspect}"
380
+ end
381
+ end
382
+ end
383
+
384
+ # wait for a signal hander to wake us up and then consume the pipe
385
+ # Wake up every second anyways to run murder_lazy_workers
386
+ def master_sleep
387
+ begin
388
+ ready = IO.select([SELF_PIPE.first], nil, nil, 1) or return
389
+ ready.first && ready.first.first or return
390
+ loop { SELF_PIPE.first.read_nonblock(Const::CHUNK_SIZE) }
391
+ rescue Errno::EAGAIN, Errno::EINTR
392
+ end
393
+ end
394
+
395
+ def awaken_master
396
+ begin
397
+ SELF_PIPE.last.write_nonblock('.') # wakeup master process from select
398
+ rescue Errno::EAGAIN, Errno::EINTR
399
+ # pipe is full, master should wake up anyways
400
+ retry
401
+ end
402
+ end
403
+
404
+ # reaps all unreaped workers
405
+ def reap_all_workers
406
+ begin
407
+ loop do
408
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
409
+ wpid or break
410
+ if reexec_pid == wpid
411
+ logger.error "reaped #{status.inspect} exec()-ed"
412
+ self.reexec_pid = 0
413
+ self.pid = pid.chomp('.oldbin') if pid
414
+ proc_name 'master'
415
+ else
416
+ worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
417
+ logger.info "reaped #{status.inspect} " \
418
+ "worker=#{worker.nr rescue 'unknown'}"
419
+ end
420
+ end
421
+ rescue Errno::ECHILD
422
+ end
423
+ end
424
+
425
+ # reexecutes the START_CTX with a new binary
426
+ def reexec
427
+ if reexec_pid > 0
428
+ begin
429
+ Process.kill(0, reexec_pid)
430
+ logger.error "reexec-ed child already running PID:#{reexec_pid}"
431
+ return
432
+ rescue Errno::ESRCH
433
+ self.reexec_pid = 0
434
+ end
435
+ end
436
+
437
+ if pid
438
+ old_pid = "#{pid}.oldbin"
439
+ prev_pid = pid.dup
440
+ begin
441
+ self.pid = old_pid # clear the path for a new pid file
442
+ rescue ArgumentError
443
+ logger.error "old PID:#{valid_pid?(old_pid)} running with " \
444
+ "existing pid=#{old_pid}, refusing rexec"
445
+ return
446
+ rescue Object => e
447
+ logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
448
+ return
449
+ end
450
+ end
451
+
452
+ self.reexec_pid = fork do
453
+ listener_fds = LISTENERS.map { |sock| sock.fileno }
454
+ ENV['UNICORN_FD'] = listener_fds.join(',')
455
+ Dir.chdir(START_CTX[:cwd])
456
+ cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
457
+
458
+ # avoid leaking FDs we don't know about, but let before_exec
459
+ # unset FD_CLOEXEC, if anything else in the app eventually
460
+ # relies on FD inheritence.
461
+ (3..1024).each do |io|
462
+ next if listener_fds.include?(io)
463
+ io = IO.for_fd(io) rescue nil
464
+ io or next
465
+ IO_PURGATORY << io
466
+ io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
467
+ end
468
+ logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
469
+ before_exec.call(self)
470
+ exec(*cmd)
471
+ end
472
+ proc_name 'master (old)'
473
+ end
474
+
475
+ # forcibly terminate all workers that haven't checked in in timeout
476
+ # seconds. The timeout is implemented using an unlinked File
477
+ # shared between the parent process and each worker. The worker
478
+ # runs File#chmod to modify the ctime of the File. If the ctime
479
+ # is stale for >timeout seconds, then we'll kill the corresponding
480
+ # worker.
481
+ def murder_lazy_workers
482
+ WORKERS.dup.each_pair do |wpid, worker|
483
+ (diff = (Time.now - worker.tmp.stat.ctime)) <= timeout and next
484
+ logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \
485
+ "(#{diff}s > #{timeout}s), killing"
486
+ kill_worker(:KILL, wpid) # take no prisoners for timeout violations
487
+ end
488
+ end
489
+
490
+ def spawn_missing_workers
491
+ (0...worker_processes).each do |worker_nr|
492
+ WORKERS.values.include?(worker_nr) and next
493
+ begin
494
+ Dir.chdir(START_CTX[:cwd])
495
+ rescue Errno::ENOENT => err
496
+ logger.fatal "#{err.inspect} (#{START_CTX[:cwd]})"
497
+ SIG_QUEUE << :QUIT # forcibly emulate SIGQUIT
498
+ return
499
+ end
500
+ worker = Worker.new(worker_nr, Unicorn::Util.tmpio)
501
+ before_fork.call(self, worker)
502
+ WORKERS[fork { worker_loop(worker) }] = worker
503
+ end
504
+ end
505
+
506
+ def maintain_worker_count
507
+ (off = WORKERS.size - worker_processes) == 0 and return
508
+ off < 0 and return spawn_missing_workers
509
+ WORKERS.dup.each_pair { |wpid,w|
510
+ w.nr >= worker_processes and kill_worker(:QUIT, wpid) rescue nil
511
+ }
512
+ end
513
+
514
+ # if we get any error, try to write something back to the client
515
+ # assuming we haven't closed the socket, but don't get hung up
516
+ # if the socket is already closed or broken. We'll always ensure
517
+ # the socket is closed at the end of this function
518
+ def handle_error(client, e)
519
+ msg = case e
520
+ when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
521
+ Const::ERROR_500_RESPONSE
522
+ when HttpParserError # try to tell the client they're bad
523
+ Const::ERROR_400_RESPONSE
524
+ else
525
+ logger.error "Read error: #{e.inspect}"
526
+ logger.error e.backtrace.join("\n")
527
+ Const::ERROR_500_RESPONSE
528
+ end
529
+ client.write_nonblock(msg)
530
+ client.close
531
+ rescue
532
+ nil
533
+ end
534
+
535
+ # once a client is accepted, it is processed in its entirety here
536
+ # in 3 easy steps: read request, call app, write app response
537
+ def process_client(client)
538
+ client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
539
+ response = app.call(env = REQUEST.read(client))
540
+
541
+ if 100 == response.first.to_i
542
+ client.write(Const::EXPECT_100_RESPONSE)
543
+ env.delete(Const::HTTP_EXPECT)
544
+ response = app.call(env)
545
+ end
546
+ HttpResponse.write(client, response, HttpRequest::PARSER.headers?)
547
+ rescue => e
548
+ handle_error(client, e)
549
+ end
550
+
551
+ # gets rid of stuff the worker has no business keeping track of
552
+ # to free some resources and drops all sig handlers.
553
+ # traps for USR1, USR2, and HUP may be set in the after_fork Proc
554
+ # by the user.
555
+ def init_worker_process(worker)
556
+ QUEUE_SIGS.each { |sig| trap(sig, nil) }
557
+ trap(:CHLD, 'DEFAULT')
558
+ SIG_QUEUE.clear
559
+ proc_name "worker[#{worker.nr}]"
560
+ START_CTX.clear
561
+ init_self_pipe!
562
+ WORKERS.values.each { |other| other.tmp.close rescue nil }
563
+ WORKERS.clear
564
+ LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
565
+ worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
566
+ after_fork.call(self, worker) # can drop perms
567
+ self.timeout /= 2.0 # halve it for select()
568
+ build_app! unless preload_app
569
+ end
570
+
571
+ def reopen_worker_logs(worker_nr)
572
+ logger.info "worker=#{worker_nr} reopening logs..."
573
+ Unicorn::Util.reopen_logs
574
+ logger.info "worker=#{worker_nr} done reopening logs"
575
+ init_self_pipe!
576
+ end
577
+
578
+ # runs inside each forked worker, this sits around and waits
579
+ # for connections and doesn't die until the parent dies (or is
580
+ # given a INT, QUIT, or TERM signal)
581
+ def worker_loop(worker)
582
+ ppid = master_pid
583
+ init_worker_process(worker)
584
+ nr = 0 # this becomes negative if we need to reopen logs
585
+ alive = worker.tmp # tmp is our lifeline to the master process
586
+ ready = LISTENERS
587
+
588
+ # closing anything we IO.select on will raise EBADF
589
+ trap(:USR1) { nr = -65536; SELF_PIPE.first.close rescue nil }
590
+ trap(:QUIT) { alive = nil; LISTENERS.each { |s| s.close rescue nil } }
591
+ [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
592
+ logger.info "worker=#{worker.nr} ready"
593
+ m = 0
594
+
595
+ begin
596
+ nr < 0 and reopen_worker_logs(worker.nr)
597
+ nr = 0
598
+
599
+ # we're a goner in timeout seconds anyways if alive.chmod
600
+ # breaks, so don't trap the exception. Using fchmod() since
601
+ # futimes() is not available in base Ruby and I very strongly
602
+ # prefer temporary files to be unlinked for security,
603
+ # performance and reliability reasons, so utime is out. No-op
604
+ # changes with chmod doesn't update ctime on all filesystems; so
605
+ # we change our counter each and every time (after process_client
606
+ # and before IO.select).
607
+ alive.chmod(m = 0 == m ? 1 : 0)
608
+
609
+ ready.each do |sock|
610
+ begin
611
+ process_client(sock.accept_nonblock)
612
+ nr += 1
613
+ alive.chmod(m = 0 == m ? 1 : 0)
614
+ rescue Errno::EAGAIN, Errno::ECONNABORTED
615
+ end
616
+ break if nr < 0
617
+ end
618
+
619
+ # make the following bet: if we accepted clients this round,
620
+ # we're probably reasonably busy, so avoid calling select()
621
+ # and do a speculative accept_nonblock on ready listeners
622
+ # before we sleep again in select().
623
+ redo unless nr == 0 # (nr < 0) => reopen logs
624
+
625
+ ppid == Process.ppid or return
626
+ alive.chmod(m = 0 == m ? 1 : 0)
627
+ begin
628
+ # timeout used so we can detect parent death:
629
+ ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) or redo
630
+ ready = ret.first
631
+ rescue Errno::EINTR
632
+ ready = LISTENERS
633
+ rescue Errno::EBADF
634
+ nr < 0 or return
635
+ end
636
+ rescue Object => e
637
+ if alive
638
+ logger.error "Unhandled listen loop exception #{e.inspect}."
639
+ logger.error e.backtrace.join("\n")
640
+ end
641
+ end while alive
642
+ end
643
+
644
+ # delivers a signal to a worker and fails gracefully if the worker
645
+ # is no longer running.
646
+ def kill_worker(signal, wpid)
647
+ begin
648
+ Process.kill(signal, wpid)
649
+ rescue Errno::ESRCH
650
+ worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
651
+ end
652
+ end
653
+
654
+ # delivers a signal to each worker
655
+ def kill_each_worker(signal)
656
+ WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
657
+ end
658
+
659
+ # unlinks a PID file at given +path+ if it contains the current PID
660
+ # still potentially racy without locking the directory (which is
661
+ # non-portable and may interact badly with other programs), but the
662
+ # window for hitting the race condition is small
663
+ def unlink_pid_safe(path)
664
+ (File.read(path).to_i == $$ and File.unlink(path)) rescue nil
665
+ end
666
+
667
+ # returns a PID if a given path contains a non-stale PID file,
668
+ # nil otherwise.
669
+ def valid_pid?(path)
670
+ wpid = File.read(path).to_i
671
+ wpid <= 0 and return nil
672
+ begin
673
+ Process.kill(0, wpid)
674
+ return wpid
675
+ rescue Errno::ESRCH
676
+ # don't unlink stale pid files, racy without non-portable locking...
677
+ end
678
+ rescue Errno::ENOENT
679
+ end
680
+
681
+ def load_config!
682
+ begin
683
+ logger.info "reloading config_file=#{config.config_file}"
684
+ config[:listeners].replace(init_listeners)
685
+ config.reload
686
+ config.commit!(self)
687
+ kill_each_worker(:QUIT)
688
+ Unicorn::Util.reopen_logs
689
+ self.app = orig_app
690
+ build_app! if preload_app
691
+ logger.info "done reloading config_file=#{config.config_file}"
692
+ rescue Object => e
693
+ logger.error "error reloading config_file=#{config.config_file}: " \
694
+ "#{e.class} #{e.message}"
695
+ end
696
+ end
697
+
698
+ # returns an array of string names for the given listener array
699
+ def listener_names(listeners = LISTENERS)
700
+ listeners.map { |io| sock_name(io) }
701
+ end
702
+
703
+ def build_app!
704
+ if app.respond_to?(:arity) && app.arity == 0
705
+ if defined?(Gem) && Gem.respond_to?(:refresh)
706
+ logger.info "Refreshing Gem list"
707
+ Gem.refresh
708
+ end
709
+ self.app = app.call
710
+ end
711
+ end
712
+
713
+ def proc_name(tag)
714
+ $0 = ([ File.basename(START_CTX[0]), tag
715
+ ]).concat(START_CTX[:argv]).join(' ')
716
+ end
717
+
718
+ def redirect_io(io, path)
719
+ File.open(path, 'ab') { |fp| io.reopen(fp) } if path
720
+ io.sync = true
721
+ end
722
+
723
+ def init_self_pipe!
724
+ SELF_PIPE.each { |io| io.close rescue nil }
725
+ SELF_PIPE.replace(IO.pipe)
726
+ SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
727
+ end
728
+
729
+ end
730
+ end