giraffesoft-unicorn 0.93.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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