unicorn 0.4.2 → 0.5.0
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.
- data/CHANGELOG +1 -0
- data/DESIGN +14 -10
- data/GNUmakefile +13 -3
- data/README +28 -21
- data/SIGNALS +11 -4
- data/TODO +0 -2
- data/ext/unicorn/http11/http11.c +17 -34
- data/lib/unicorn.rb +38 -37
- data/lib/unicorn/app/old_rails.rb +9 -3
- data/lib/unicorn/cgi_wrapper.rb +2 -4
- data/lib/unicorn/configurator.rb +29 -19
- data/lib/unicorn/const.rb +1 -1
- data/lib/unicorn/http_request.rb +0 -1
- data/lib/unicorn/socket.rb +17 -57
- data/local.mk.sample +1 -0
- data/test/exec/test_exec.rb +76 -6
- data/test/test_helper.rb +3 -2
- data/test/unit/test_configurator.rb +16 -1
- data/test/unit/test_http_parser.rb +1 -1
- data/test/unit/test_request.rb +53 -0
- data/test/unit/test_server.rb +38 -3
- data/test/unit/test_signals.rb +108 -0
- data/test/unit/test_socket_helper.rb +19 -7
- data/unicorn.gemspec +4 -4
- metadata +3 -2
data/CHANGELOG
CHANGED
data/DESIGN
CHANGED
@@ -37,10 +37,11 @@
|
|
37
37
|
|
38
38
|
* The number of worker processes should be scaled to the number of
|
39
39
|
CPUs, memory or even spindles you have. If you have an existing
|
40
|
-
Mongrel cluster, using the same amount of
|
41
|
-
Let a full-HTTP-request-buffering reverse
|
42
|
-
concurrency to thousands of slow clients for
|
43
|
-
should only be concerned about limits of your
|
40
|
+
Mongrel cluster on a single-threaded app, using the same amount of
|
41
|
+
processes should work. Let a full-HTTP-request-buffering reverse
|
42
|
+
proxy like nginx manage concurrency to thousands of slow clients for
|
43
|
+
you. Unicorn scaling should only be concerned about limits of your
|
44
|
+
backend system(s).
|
44
45
|
|
45
46
|
* Load balancing between worker processes is done by the OS kernel.
|
46
47
|
All workers share a common set of listener sockets and does
|
@@ -56,8 +57,8 @@
|
|
56
57
|
|
57
58
|
* Blocking I/O is used for clients. This allows a simpler code path
|
58
59
|
to be followed within the Ruby interpreter and fewer syscalls.
|
59
|
-
Applications that use threads
|
60
|
-
is serving LAN or localhost clients.
|
60
|
+
Applications that use threads continue to work if Unicorn
|
61
|
+
is only serving LAN or localhost clients.
|
61
62
|
|
62
63
|
* Timeout implementation is done via fchmod(2) in each worker
|
63
64
|
on a shared file descriptor to update st_ctime on the inode.
|
@@ -67,8 +68,9 @@
|
|
67
68
|
pwrite(2)/pread(2) are supported by base MRI, nor are they as
|
68
69
|
portable on UNIX systems as fchmod(2).
|
69
70
|
|
70
|
-
* SIGKILL is used to terminate the timed-out workers
|
71
|
-
as possible on a UNIX system.
|
71
|
+
* SIGKILL is used to terminate the timed-out workers from misbehaving apps
|
72
|
+
as reliably as possible on a UNIX system. The default timeout is a
|
73
|
+
generous 60 seconds (same default as in Mongrel).
|
72
74
|
|
73
75
|
* The poor performance of select() on large FD sets is avoided
|
74
76
|
as few file descriptors are used in each worker.
|
@@ -81,5 +83,7 @@
|
|
81
83
|
the master to its death.
|
82
84
|
|
83
85
|
* There is never any explicit real-time dependency or communication
|
84
|
-
between the worker processes
|
85
|
-
Synchronization is handled entirely by the OS kernel
|
86
|
+
between the worker processes nor to the master process.
|
87
|
+
Synchronization is handled entirely by the OS kernel and shared
|
88
|
+
resources are never accessed by the worker when it is servicing
|
89
|
+
a client.
|
data/GNUmakefile
CHANGED
@@ -66,8 +66,19 @@ $(slow_tests):
|
|
66
66
|
@$(MAKE) $(shell $(awk_slow) $@)
|
67
67
|
|
68
68
|
TEST_OPTS = -v
|
69
|
-
|
70
|
-
|
69
|
+
TEST_OPTS = -v
|
70
|
+
ifndef V
|
71
|
+
quiet_pre = @echo '* $(arg)$(extra)';
|
72
|
+
quiet_post = >$(t) 2>&1
|
73
|
+
else
|
74
|
+
# we can't rely on -o pipefail outside of bash 3+,
|
75
|
+
# so we use a stamp file to indicate success and
|
76
|
+
# have rm fail if the stamp didn't get created
|
77
|
+
stamp = $@$(log_suffix).ok
|
78
|
+
quiet_pre = @echo $(ruby) $(arg) $(TEST_OPTS); ! test -f $(stamp) && (
|
79
|
+
quiet_post = && > $(stamp) )>&2 | tee $(t); rm $(stamp) 2>/dev/null
|
80
|
+
endif
|
81
|
+
run_test = $(quiet_pre) setsid $(ruby) -w $(arg) $(TEST_OPTS) $(quiet_post) || \
|
71
82
|
(sed "s,^,$(extra): ," >&2 < $(t); exit 1)
|
72
83
|
|
73
84
|
%.n: arg = $(subst .n,,$(subst --, -n ,$@))
|
@@ -134,6 +145,5 @@ $(T_r).%.r: export UNICORN_RAILS_TEST_VERSION = $(rv)
|
|
134
145
|
$(T_r).%.r: export RAILS_GIT_REPO = $(CURDIR)/$(rails_git)
|
135
146
|
$(T_r).%.r: $(test_prefix)/.stamp $(rails_git)/info/cloned-stamp
|
136
147
|
$(run_test)
|
137
|
-
@sed 's,^,$(rv): ,' < $(t)
|
138
148
|
|
139
149
|
.PHONY: doc $(T) $(slow_tests) Manifest
|
data/README
CHANGED
@@ -1,33 +1,35 @@
|
|
1
|
-
= Unicorn: Unix + LAN/localhost-
|
2
|
-
|
3
|
-
Unicorn is designed to only serve fast clients. See the PHILOSOPHY
|
4
|
-
and DESIGN documents for more details regarding this.
|
1
|
+
= Unicorn: Unix + LAN/localhost-only fork of Mongrel
|
5
2
|
|
6
3
|
== Features
|
7
4
|
|
8
|
-
*
|
9
|
-
|
5
|
+
* Designed for Rack, Unix, fast clients, and ease-of-debugging. We
|
6
|
+
cut out all things that are better-supported by nginx or Rack.
|
10
7
|
|
11
8
|
* Mostly written in Ruby, only the HTTP parser (stolen and trimmed
|
12
9
|
down from Mongrel) is written in C. Unicorn is compatible with
|
13
|
-
both Ruby 1.8 and 1.9.
|
10
|
+
both Ruby 1.8 and 1.9. A pure-Ruby (but still Unix-only) version
|
11
|
+
is planned.
|
14
12
|
|
15
13
|
* Process management: Unicorn will reap and restart workers that
|
16
14
|
die from broken apps. There is no need to manage multiple processes
|
17
|
-
yourself.
|
15
|
+
or ports yourself. Unicorn can spawn and manage any fixed number of
|
16
|
+
worker processes you choose to scale to your backend.
|
18
17
|
|
19
18
|
* Load balancing is done entirely by the operating system kernel.
|
20
|
-
Requests never pile up behind a busy worker.
|
19
|
+
Requests never pile up behind a busy worker process.
|
21
20
|
|
22
21
|
* Does not care if your application is thread-safe or not, workers
|
23
22
|
all run within their own isolated address space and only serve one
|
24
|
-
client at a time
|
23
|
+
client at a time.
|
25
24
|
|
26
25
|
* Supports all Rack applications, along with pre-Rack versions of
|
27
26
|
Ruby on Rails via a Rack wrapper.
|
28
27
|
|
29
|
-
* Builtin
|
30
|
-
signal.
|
28
|
+
* Builtin reopening of all log files in your application via
|
29
|
+
USR1 signal. This allows logrotate to rotate files atomically and
|
30
|
+
quickly via rename instead of the racy and slow copytruncate method.
|
31
|
+
Unicorn also takes steps to ensure multi-line log entries from one
|
32
|
+
request all stay within the same file.
|
31
33
|
|
32
34
|
* nginx-style binary re-execution without losing connections.
|
33
35
|
You can upgrade Unicorn, your entire application, libraries
|
@@ -35,10 +37,12 @@ and DESIGN documents for more details regarding this.
|
|
35
37
|
installed in the same path.
|
36
38
|
|
37
39
|
* before_fork and after_fork hooks in case your application
|
38
|
-
has special needs when dealing with forked processes.
|
40
|
+
has special needs when dealing with forked processes. These
|
41
|
+
should not be needed when the "preload_app" directive is
|
42
|
+
false (the default).
|
39
43
|
|
40
44
|
* Can be used with copy-on-write-friendly memory management
|
41
|
-
to save memory.
|
45
|
+
to save memory (by setting "preload_app" to true).
|
42
46
|
|
43
47
|
* Able to listen on multiple interfaces including UNIX sockets,
|
44
48
|
each worker process can also bind to a private port via the
|
@@ -50,7 +54,7 @@ Unicorn is copyright 2009 Eric Wong and contributors.
|
|
50
54
|
It is based on Mongrel and carries the same license:
|
51
55
|
|
52
56
|
Mongrel is copyright 2007 Zed A. Shaw and contributors. It is licensed
|
53
|
-
under the Ruby license and the GPL2. See the
|
57
|
+
under the Ruby license and the GPL2. See the included LICENSE file for
|
54
58
|
details.
|
55
59
|
|
56
60
|
Unicorn is 100% Free Software.
|
@@ -81,8 +85,8 @@ If you have web browser software for the World Wide Web
|
|
81
85
|
(on the Information Superhighway), you may browse the code from
|
82
86
|
your web browser and download the latest snapshot tarballs here:
|
83
87
|
|
84
|
-
* http://git.bogomips.org/cgit/unicorn.git (
|
85
|
-
* http://repo.or.cz/w/unicorn.git (gitweb
|
88
|
+
* http://git.bogomips.org/cgit/unicorn.git (cgit)
|
89
|
+
* http://repo.or.cz/w/unicorn.git (gitweb)
|
86
90
|
|
87
91
|
== Usage
|
88
92
|
|
@@ -118,9 +122,12 @@ options.
|
|
118
122
|
|
119
123
|
== Disclaimer
|
120
124
|
|
121
|
-
Like the creatures themselves, production deployments of Unicorn are
|
122
|
-
There is NO WARRANTY whatsoever if anything
|
123
|
-
we'll try our best to fix it.
|
125
|
+
Like the creatures themselves, production deployments of Unicorn are
|
126
|
+
rare or even non-existent. There is NO WARRANTY whatsoever if anything
|
127
|
+
goes wrong, but let us know and we'll try our best to fix it.
|
128
|
+
|
129
|
+
Unicorn is designed to only serve fast clients. See the PHILOSOPHY
|
130
|
+
and DESIGN documents for more details regarding this.
|
124
131
|
|
125
132
|
Rainbows are NOT included.
|
126
133
|
|
@@ -137,5 +144,5 @@ Rainbows are NOT included.
|
|
137
144
|
|
138
145
|
== Contact
|
139
146
|
|
140
|
-
Newsgroup and mailing list maybe coming...
|
141
147
|
Email Eric Wong at normalperson@yhbt.net for now.
|
148
|
+
Newsgroup and mailing list maybe coming...
|
data/SIGNALS
CHANGED
@@ -7,7 +7,7 @@ processes are documented here as well.
|
|
7
7
|
=== Master Process
|
8
8
|
|
9
9
|
* HUP - reload config file and gracefully restart all workers
|
10
|
-
If preload_app is false (the default), the application code
|
10
|
+
If "preload_app" is false (the default), the application code
|
11
11
|
will be reloaded when workers are restarted as well.
|
12
12
|
|
13
13
|
* INT/TERM - quick shutdown, kills all workers immediately
|
@@ -31,12 +31,19 @@ Sending signals directly to the worker processes should not normally be
|
|
31
31
|
needed. If the master process is running, any exited worker will be
|
32
32
|
automatically respawned.
|
33
33
|
|
34
|
-
* INT/TERM -
|
34
|
+
* INT/TERM - Quick shutdown, immediately exit.
|
35
|
+
Unless WINCH has been sent to the master (or the master is killed),
|
36
|
+
the master process will respawn a worker to replace this one.
|
35
37
|
|
36
|
-
* QUIT -
|
38
|
+
* QUIT - Gracefully exit after finishing the current request.
|
39
|
+
Unless WINCH has been sent to the master (or the master is killed),
|
40
|
+
the master process will respawn a worker to replace this one.
|
37
41
|
|
38
|
-
* USR1 -
|
42
|
+
* USR1 - Reopen all logs owned by the worker process.
|
39
43
|
See Unicorn::Util.reopen_logs for what is considered a log.
|
44
|
+
Log files are not reopened until it is done processing
|
45
|
+
the current request, so multiple log lines for one request
|
46
|
+
(as done by Rails) will not be split across multiple logs.
|
40
47
|
|
41
48
|
=== Procedure to replace a running unicorn executable
|
42
49
|
|
data/TODO
CHANGED
data/ext/unicorn/http11/http11.c
CHANGED
@@ -24,6 +24,7 @@ static VALUE sym_http_body;
|
|
24
24
|
#define HTTP_PREFIX "HTTP_"
|
25
25
|
#define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
|
26
26
|
|
27
|
+
static VALUE global_rack_url_scheme;
|
27
28
|
static VALUE global_request_method;
|
28
29
|
static VALUE global_request_uri;
|
29
30
|
static VALUE global_fragment;
|
@@ -37,8 +38,10 @@ static VALUE global_server_port;
|
|
37
38
|
static VALUE global_server_protocol;
|
38
39
|
static VALUE global_server_protocol_value;
|
39
40
|
static VALUE global_http_host;
|
41
|
+
static VALUE global_http_x_forwarded_proto;
|
40
42
|
static VALUE global_port_80;
|
41
43
|
static VALUE global_localhost;
|
44
|
+
static VALUE global_http;
|
42
45
|
|
43
46
|
/** Defines common length and error messages for input length validation. */
|
44
47
|
#define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length."
|
@@ -106,26 +109,12 @@ static struct common_field common_http_fields[] = {
|
|
106
109
|
f("USER_AGENT"),
|
107
110
|
f("VIA"),
|
108
111
|
f("X_FORWARDED_FOR"), /* common for proxies */
|
112
|
+
f("X_FORWARDED_PROTO"), /* common for proxies */
|
109
113
|
f("X_REAL_IP"), /* common for proxies */
|
110
114
|
f("WARNING")
|
111
115
|
# undef f
|
112
116
|
};
|
113
117
|
|
114
|
-
/*
|
115
|
-
* qsort(3) and bsearch(3) improve average performance slightly, but may
|
116
|
-
* not be worth it for lack of portability to certain platforms...
|
117
|
-
*/
|
118
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
119
|
-
/* sort by length, then by name if there's a tie */
|
120
|
-
static int common_field_cmp(const void *a, const void *b)
|
121
|
-
{
|
122
|
-
struct common_field *cfa = (struct common_field *)a;
|
123
|
-
struct common_field *cfb = (struct common_field *)b;
|
124
|
-
signed long diff = cfa->len - cfb->len;
|
125
|
-
return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
|
126
|
-
}
|
127
|
-
#endif /* HAVE_QSORT_BSEARCH */
|
128
|
-
|
129
118
|
/* this function is not performance-critical */
|
130
119
|
static void init_common_fields(void)
|
131
120
|
{
|
@@ -146,28 +135,10 @@ static void init_common_fields(void)
|
|
146
135
|
cf->value = rb_obj_freeze(cf->value);
|
147
136
|
rb_global_variable(&cf->value);
|
148
137
|
}
|
149
|
-
|
150
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
151
|
-
qsort(common_http_fields,
|
152
|
-
ARRAY_SIZE(common_http_fields),
|
153
|
-
sizeof(struct common_field),
|
154
|
-
common_field_cmp);
|
155
|
-
#endif /* HAVE_QSORT_BSEARCH */
|
156
138
|
}
|
157
139
|
|
158
140
|
static VALUE find_common_field_value(const char *field, size_t flen)
|
159
141
|
{
|
160
|
-
#if defined(HAVE_QSORT_BSEARCH)
|
161
|
-
struct common_field key;
|
162
|
-
struct common_field *found;
|
163
|
-
key.name = field;
|
164
|
-
key.len = (signed long)flen;
|
165
|
-
found = (struct common_field *)bsearch(&key, common_http_fields,
|
166
|
-
ARRAY_SIZE(common_http_fields),
|
167
|
-
sizeof(struct common_field),
|
168
|
-
common_field_cmp);
|
169
|
-
return found ? found->value : Qnil;
|
170
|
-
#else /* !HAVE_QSORT_BSEARCH */
|
171
142
|
int i;
|
172
143
|
struct common_field *cf = common_http_fields;
|
173
144
|
for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
|
@@ -175,7 +146,6 @@ static VALUE find_common_field_value(const char *field, size_t flen)
|
|
175
146
|
return cf->value;
|
176
147
|
}
|
177
148
|
return Qnil;
|
178
|
-
#endif /* !HAVE_QSORT_BSEARCH */
|
179
149
|
}
|
180
150
|
|
181
151
|
static void http_field(void *data, const char *field,
|
@@ -282,6 +252,16 @@ static void header_done(void *data, const char *at, size_t length)
|
|
282
252
|
VALUE temp = Qnil;
|
283
253
|
char *colon = NULL;
|
284
254
|
|
255
|
+
/* set rack.url_scheme to "https" or "http", no others are allowed by Rack */
|
256
|
+
temp = rb_hash_aref(req, global_http_x_forwarded_proto);
|
257
|
+
switch (temp == Qnil ? 0 : RSTRING_LEN(temp)) {
|
258
|
+
case 5: if (!memcmp("https", RSTRING_PTR(temp), 5)) break;
|
259
|
+
case 4: if (!memcmp("http", RSTRING_PTR(temp), 4)) break;
|
260
|
+
default: temp = global_http;
|
261
|
+
}
|
262
|
+
rb_hash_aset(req, global_rack_url_scheme, temp);
|
263
|
+
|
264
|
+
/* set the SERVER_NAME and SERVER_PORT variables */
|
285
265
|
if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
|
286
266
|
colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
|
287
267
|
if(colon != NULL) {
|
@@ -406,6 +386,7 @@ void Init_http11()
|
|
406
386
|
|
407
387
|
mUnicorn = rb_define_module("Unicorn");
|
408
388
|
|
389
|
+
DEF_GLOBAL(rack_url_scheme, "rack.url_scheme");
|
409
390
|
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
410
391
|
DEF_GLOBAL(request_uri, "REQUEST_URI");
|
411
392
|
DEF_GLOBAL(fragment, "FRAGMENT");
|
@@ -419,8 +400,10 @@ void Init_http11()
|
|
419
400
|
DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
|
420
401
|
DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
|
421
402
|
DEF_GLOBAL(http_host, "HTTP_HOST");
|
403
|
+
DEF_GLOBAL(http_x_forwarded_proto, "HTTP_X_FORWARDED_PROTO");
|
422
404
|
DEF_GLOBAL(port_80, "80");
|
423
405
|
DEF_GLOBAL(localhost, "localhost");
|
406
|
+
DEF_GLOBAL(http, "http");
|
424
407
|
|
425
408
|
eHttpParserError = rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError);
|
426
409
|
|
data/lib/unicorn.rb
CHANGED
@@ -26,14 +26,13 @@ module Unicorn
|
|
26
26
|
attr_reader :logger
|
27
27
|
include ::Unicorn::SocketHelper
|
28
28
|
|
29
|
+
SIG_QUEUE = []
|
29
30
|
DEFAULT_START_CTX = {
|
30
31
|
:argv => ARGV.map { |arg| arg.dup },
|
31
32
|
# don't rely on Dir.pwd here since it's not symlink-aware, and
|
32
33
|
# symlink dirs are the default with Capistrano...
|
33
34
|
:cwd => `/bin/sh -c pwd`.chomp("\n"),
|
34
35
|
:zero => $0.dup,
|
35
|
-
:environ => {}.merge!(ENV),
|
36
|
-
:umask => File.umask,
|
37
36
|
}.freeze
|
38
37
|
|
39
38
|
Worker = Struct.new(:nr, :tempfile) unless defined?(Worker)
|
@@ -53,12 +52,12 @@ module Unicorn
|
|
53
52
|
@start_ctx = DEFAULT_START_CTX.dup
|
54
53
|
@start_ctx.merge!(start_ctx) if start_ctx
|
55
54
|
@app = app
|
56
|
-
@sig_queue = []
|
57
55
|
@master_pid = $$
|
58
56
|
@workers = Hash.new
|
59
57
|
@io_purgatory = [] # prevents IO objects in here from being GC-ed
|
60
58
|
@request = @rd_sig = @wr_sig = nil
|
61
59
|
@reexec_pid = 0
|
60
|
+
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
62
61
|
@config = Configurator.new(options.merge(:use_defaults => true))
|
63
62
|
@listener_opts = {}
|
64
63
|
@config.commit!(self, :skip => [:listeners, :pid])
|
@@ -73,9 +72,9 @@ module Unicorn
|
|
73
72
|
# before they become UNIXServer or TCPServer
|
74
73
|
inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
|
75
74
|
io = Socket.for_fd(fd.to_i)
|
76
|
-
set_server_sockopt(io)
|
75
|
+
set_server_sockopt(io, @listener_opts[sock_name(io)])
|
77
76
|
@io_purgatory << io
|
78
|
-
logger.info "inherited
|
77
|
+
logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
|
79
78
|
server_cast(io)
|
80
79
|
end
|
81
80
|
|
@@ -104,15 +103,27 @@ module Unicorn
|
|
104
103
|
# replaces current listener set with +listeners+. This will
|
105
104
|
# close the socket if it will not exist in the new listener set
|
106
105
|
def listeners=(listeners)
|
107
|
-
cur_names =
|
106
|
+
cur_names, dead_names = [], []
|
107
|
+
listener_names.each do |name|
|
108
|
+
if "/" == name[0..0]
|
109
|
+
# mark unlinked sockets as dead so we can rebind them
|
110
|
+
(File.socket?(name) ? cur_names : dead_names) << name
|
111
|
+
else
|
112
|
+
cur_names << name
|
113
|
+
end
|
114
|
+
end
|
108
115
|
set_names = listener_names(listeners)
|
109
|
-
dead_names
|
116
|
+
dead_names += cur_names - set_names
|
117
|
+
dead_names.uniq!
|
110
118
|
|
111
119
|
@listeners.delete_if do |io|
|
112
120
|
if dead_names.include?(sock_name(io))
|
113
|
-
@io_purgatory.delete_if
|
114
|
-
|
121
|
+
@io_purgatory.delete_if do |pio|
|
122
|
+
pio.fileno == io.fileno && (pio.close rescue nil).nil? # true
|
123
|
+
end
|
124
|
+
(io.close rescue nil).nil? # true
|
115
125
|
else
|
126
|
+
set_server_sockopt(io, @listener_opts[sock_name(io)])
|
116
127
|
false
|
117
128
|
end
|
118
129
|
end
|
@@ -141,12 +152,11 @@ module Unicorn
|
|
141
152
|
return if String === address && listener_names.include?(address)
|
142
153
|
|
143
154
|
if io = bind_listen(address, opt)
|
144
|
-
|
155
|
+
unless TCPServer === io || UNIXServer === io
|
145
156
|
@io_purgatory << io
|
146
157
|
io = server_cast(io)
|
147
158
|
end
|
148
|
-
logger.info "
|
149
|
-
"fd=#{io.fileno} addr=#{sock_name(io)}"
|
159
|
+
logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
|
150
160
|
@listeners << io
|
151
161
|
else
|
152
162
|
logger.error "adding listener failed addr=#{address} (in use)"
|
@@ -172,7 +182,7 @@ module Unicorn
|
|
172
182
|
begin
|
173
183
|
loop do
|
174
184
|
reap_all_workers
|
175
|
-
case (mode =
|
185
|
+
case (mode = SIG_QUEUE.shift)
|
176
186
|
when nil
|
177
187
|
murder_lazy_workers
|
178
188
|
spawn_missing_workers if respawn
|
@@ -219,7 +229,7 @@ module Unicorn
|
|
219
229
|
retry
|
220
230
|
end
|
221
231
|
stop # gracefully shutdown all workers on our way out
|
222
|
-
logger.info "master
|
232
|
+
logger.info "master complete"
|
223
233
|
unlink_pid_safe(@pid) if @pid
|
224
234
|
end
|
225
235
|
|
@@ -247,11 +257,11 @@ module Unicorn
|
|
247
257
|
# defer a signal for later processing in #join (master process)
|
248
258
|
def trap_deferred(signal)
|
249
259
|
trap(signal) do |sig_nr|
|
250
|
-
if
|
251
|
-
|
260
|
+
if SIG_QUEUE.size < 5
|
261
|
+
SIG_QUEUE << signal
|
252
262
|
awaken_master
|
253
263
|
else
|
254
|
-
logger.error "ignoring SIG#{signal}, queue=#{
|
264
|
+
logger.error "ignoring SIG#{signal}, queue=#{SIG_QUEUE.inspect}"
|
255
265
|
end
|
256
266
|
end
|
257
267
|
end
|
@@ -326,10 +336,8 @@ module Unicorn
|
|
326
336
|
end
|
327
337
|
|
328
338
|
@reexec_pid = fork do
|
329
|
-
ENV.replace(@start_ctx[:environ])
|
330
339
|
listener_fds = @listeners.map { |sock| sock.fileno }
|
331
340
|
ENV['UNICORN_FD'] = listener_fds.join(',')
|
332
|
-
File.umask(@start_ctx[:umask])
|
333
341
|
Dir.chdir(@start_ctx[:cwd])
|
334
342
|
cmd = [ @start_ctx[:zero] ] + @start_ctx[:argv]
|
335
343
|
|
@@ -375,14 +383,13 @@ module Unicorn
|
|
375
383
|
Dir.chdir(@start_ctx[:cwd])
|
376
384
|
rescue Errno::ENOENT => err
|
377
385
|
logger.fatal "#{err.inspect} (#{@start_ctx[:cwd]})"
|
378
|
-
|
386
|
+
SIG_QUEUE << :QUIT # forcibly emulate SIGQUIT
|
379
387
|
return
|
380
388
|
end
|
381
389
|
tempfile = Tempfile.new('') # as short as possible to save dir space
|
382
390
|
tempfile.unlink # don't allow other processes to find or see it
|
383
|
-
tempfile.sync = true
|
384
391
|
worker = Worker.new(worker_nr, tempfile)
|
385
|
-
@before_fork.call(self, worker
|
392
|
+
@before_fork.call(self, worker)
|
386
393
|
pid = fork { worker_loop(worker) }
|
387
394
|
@workers[pid] = worker
|
388
395
|
end
|
@@ -391,11 +398,9 @@ module Unicorn
|
|
391
398
|
# once a client is accepted, it is processed in its entirety here
|
392
399
|
# in 3 easy steps: read request, call app, write app response
|
393
400
|
def process_client(client)
|
394
|
-
client.nonblock = false
|
395
|
-
|
396
|
-
|
397
|
-
app_response = @app.call(env)
|
398
|
-
HttpResponse.write(client, app_response)
|
401
|
+
# one syscall less than "client.nonblock = false":
|
402
|
+
client.fcntl(Fcntl::F_SETFL, File::RDWR)
|
403
|
+
HttpResponse.write(client, @app.call(@request.read(client)))
|
399
404
|
# if we get any error, try to write something back to the client
|
400
405
|
# assuming we haven't closed the socket, but don't get hung up
|
401
406
|
# if the socket is already closed or broken. We'll always ensure
|
@@ -423,23 +428,19 @@ module Unicorn
|
|
423
428
|
# traps for USR1, USR2, and HUP may be set in the @after_fork Proc
|
424
429
|
# by the user.
|
425
430
|
def init_worker_process(worker)
|
426
|
-
|
427
|
-
@sig_queue.clear
|
428
|
-
QUEUE_SIGS.each { |sig| trap(sig, 'IGNORE') }
|
431
|
+
QUEUE_SIGS.each { |sig| trap(sig, 'DEFAULT') }
|
429
432
|
trap(:CHLD, 'DEFAULT')
|
430
|
-
|
433
|
+
SIG_QUEUE.clear
|
431
434
|
proc_name "worker[#{worker.nr}]"
|
432
435
|
@rd_sig.close if @rd_sig
|
433
436
|
@wr_sig.close if @wr_sig
|
434
437
|
@workers.values.each { |other| other.tempfile.close rescue nil }
|
435
|
-
@workers.clear
|
436
|
-
@start_ctx.clear
|
437
438
|
@start_ctx = @workers = @rd_sig = @wr_sig = nil
|
438
439
|
@listeners.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
439
|
-
ENV.delete('UNICORN_FD')
|
440
|
-
@after_fork.call(self, worker.nr) if @after_fork
|
441
440
|
worker.tempfile.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
441
|
+
@after_fork.call(self, worker) if @after_fork # can drop perms
|
442
442
|
@request = HttpRequest.new(logger)
|
443
|
+
build_app! unless @preload_app
|
443
444
|
end
|
444
445
|
|
445
446
|
# runs inside each forked worker, this sits around and waits
|
@@ -452,9 +453,8 @@ module Unicorn
|
|
452
453
|
alive = true
|
453
454
|
ready = @listeners
|
454
455
|
client = nil
|
455
|
-
[:TERM, :INT].each { |sig| trap(sig) { exit(0) } } # instant shutdown
|
456
456
|
trap(:QUIT) do
|
457
|
-
alive = false
|
457
|
+
alive = false # graceful shutdown
|
458
458
|
@listeners.each { |sock| sock.close rescue nil } # break IO.select
|
459
459
|
end
|
460
460
|
reopen_logs, (rd, wr) = false, IO.pipe
|
@@ -569,6 +569,7 @@ module Unicorn
|
|
569
569
|
def load_config!
|
570
570
|
begin
|
571
571
|
logger.info "reloading config_file=#{@config.config_file}"
|
572
|
+
@config[:listeners].replace(@init_listeners)
|
572
573
|
@config.reload
|
573
574
|
@config.commit!(self)
|
574
575
|
kill_each_worker(:QUIT)
|