unicorn 3.7.0 → 4.0.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/DESIGN +0 -8
- data/GIT-VERSION-GEN +1 -1
- data/TODO +0 -2
- data/bin/unicorn +2 -1
- data/bin/unicorn_rails +2 -1
- data/ext/unicorn_http/unicorn_http.rl +66 -29
- data/lib/unicorn.rb +8 -6
- data/lib/unicorn/configurator.rb +5 -3
- data/lib/unicorn/const.rb +2 -2
- data/lib/unicorn/http_server.rb +76 -90
- data/lib/unicorn/socket_helper.rb +4 -5
- data/lib/unicorn/worker.rb +45 -4
- data/script/isolate_for_tests +3 -2
- data/t/broken-app.ru +12 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/t0001-reload-bad-config.sh +1 -0
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +4 -4
- data/test/unit/test_http_parser_ng.rb +54 -0
- data/test/unit/test_response.rb +1 -1
- data/test/unit/test_server.rb +1 -1
- data/test/unit/test_signals.rb +1 -1
- data/test/unit/test_upload.rb +1 -1
- data/unicorn.gemspec +2 -1
- metadata +34 -12
data/DESIGN
CHANGED
@@ -76,14 +76,6 @@
|
|
76
76
|
Applications that use threads continue to work if Unicorn
|
77
77
|
is only serving LAN or localhost clients.
|
78
78
|
|
79
|
-
* Timeout implementation is done via fchmod(2) in each worker
|
80
|
-
on a shared file descriptor to update st_ctime on the inode.
|
81
|
-
Master process wakeups for checking on timeouts is throttled
|
82
|
-
one a second to minimize the performance impact and simplify
|
83
|
-
the code path within the worker. Neither futimes(2) nor
|
84
|
-
pwrite(2)/pread(2) are supported by base MRI, nor are they as
|
85
|
-
portable on UNIX systems as fchmod(2).
|
86
|
-
|
87
79
|
* SIGKILL is used to terminate the timed-out workers from misbehaving apps
|
88
80
|
as reliably as possible on a UNIX system. The default timeout is a
|
89
81
|
generous 60 seconds (same default as in Mongrel).
|
data/GIT-VERSION-GEN
CHANGED
data/TODO
CHANGED
data/bin/unicorn
CHANGED
@@ -106,6 +106,7 @@ op = OptionParser.new("", 24, ' ') do |opts|
|
|
106
106
|
end
|
107
107
|
|
108
108
|
app = Unicorn.builder(ARGV[0] || 'config.ru', op)
|
109
|
+
op = nil
|
109
110
|
|
110
111
|
if $DEBUG
|
111
112
|
require 'pp'
|
@@ -117,4 +118,4 @@ if $DEBUG
|
|
117
118
|
end
|
118
119
|
|
119
120
|
Unicorn::Launcher.daemonize!(options) if rackup_opts[:daemonize]
|
120
|
-
Unicorn.
|
121
|
+
Unicorn::HttpServer.new(app, options).start.join
|
data/bin/unicorn_rails
CHANGED
@@ -186,6 +186,7 @@ def rails_builder(ru, op, daemonize)
|
|
186
186
|
end
|
187
187
|
|
188
188
|
app = rails_builder(ARGV[0], op, rackup_opts[:daemonize])
|
189
|
+
op = nil
|
189
190
|
|
190
191
|
if $DEBUG
|
191
192
|
require 'pp'
|
@@ -205,4 +206,4 @@ if rackup_opts[:daemonize]
|
|
205
206
|
options[:pid] = "tmp/pids/unicorn.pid"
|
206
207
|
Unicorn::Launcher.daemonize!(options)
|
207
208
|
end
|
208
|
-
Unicorn.
|
209
|
+
Unicorn::HttpServer.new(app, options).start.join
|
@@ -629,6 +629,34 @@ static VALUE HttpParser_clear(VALUE self)
|
|
629
629
|
return self;
|
630
630
|
}
|
631
631
|
|
632
|
+
/**
|
633
|
+
* call-seq:
|
634
|
+
* parser.dechunk! => parser
|
635
|
+
*
|
636
|
+
* Resets the parser to a state suitable for dechunking response bodies
|
637
|
+
*
|
638
|
+
*/
|
639
|
+
static VALUE HttpParser_dechunk_bang(VALUE self)
|
640
|
+
{
|
641
|
+
struct http_parser *hp = data_get(self);
|
642
|
+
|
643
|
+
http_parser_init(hp);
|
644
|
+
|
645
|
+
/*
|
646
|
+
* we don't care about trailers in dechunk-only mode,
|
647
|
+
* but if we did we'd set UH_FL_HASTRAILER and clear hp->env
|
648
|
+
*/
|
649
|
+
if (0) {
|
650
|
+
rb_funcall(hp->env, id_clear, 0);
|
651
|
+
hp->flags = UH_FL_HASTRAILER;
|
652
|
+
}
|
653
|
+
|
654
|
+
hp->flags |= UH_FL_HASBODY | UH_FL_INBODY | UH_FL_CHUNKED;
|
655
|
+
hp->cs = http_parser_en_ChunkedBody;
|
656
|
+
|
657
|
+
return self;
|
658
|
+
}
|
659
|
+
|
632
660
|
/**
|
633
661
|
* call-seq:
|
634
662
|
* parser.reset => nil
|
@@ -856,72 +884,80 @@ static VALUE HttpParser_env(VALUE self)
|
|
856
884
|
|
857
885
|
/**
|
858
886
|
* call-seq:
|
859
|
-
* parser.filter_body(
|
887
|
+
* parser.filter_body(dst, src) => nil/src
|
860
888
|
*
|
861
|
-
* Takes a String of +
|
889
|
+
* Takes a String of +src+, will modify data if dechunking is done.
|
862
890
|
* Returns +nil+ if there is more data left to process. Returns
|
863
|
-
* +
|
864
|
-
* it may modify +
|
891
|
+
* +src+ if body processing is complete. When returning +src+,
|
892
|
+
* it may modify +src+ so the start of the string points to where
|
865
893
|
* the body ended so that trailer processing can begin.
|
866
894
|
*
|
867
895
|
* Raises HttpParserError if there are dechunking errors.
|
868
|
-
* Basically this is a glorified memcpy(3) that copies +
|
896
|
+
* Basically this is a glorified memcpy(3) that copies +src+
|
869
897
|
* into +buf+ while filtering it through the dechunker.
|
870
898
|
*/
|
871
|
-
static VALUE HttpParser_filter_body(VALUE self, VALUE
|
899
|
+
static VALUE HttpParser_filter_body(VALUE self, VALUE dst, VALUE src)
|
872
900
|
{
|
873
901
|
struct http_parser *hp = data_get(self);
|
874
|
-
char *
|
875
|
-
long
|
902
|
+
char *srcptr;
|
903
|
+
long srclen;
|
876
904
|
|
877
|
-
|
878
|
-
|
905
|
+
srcptr = RSTRING_PTR(src);
|
906
|
+
srclen = RSTRING_LEN(src);
|
879
907
|
|
880
|
-
StringValue(
|
881
|
-
rb_str_modify(buf);
|
882
|
-
rb_str_resize(buf, dlen); /* we can never copy more than dlen bytes */
|
883
|
-
OBJ_TAINT(buf); /* keep weirdo $SAFE users happy */
|
908
|
+
StringValue(dst);
|
884
909
|
|
885
910
|
if (HP_FL_TEST(hp, CHUNKED)) {
|
886
911
|
if (!chunked_eof(hp)) {
|
912
|
+
rb_str_modify(dst);
|
913
|
+
rb_str_resize(dst, srclen); /* we can never copy more than srclen bytes */
|
914
|
+
|
887
915
|
hp->s.dest_offset = 0;
|
888
|
-
hp->cont =
|
889
|
-
hp->buf =
|
890
|
-
http_parser_execute(hp,
|
916
|
+
hp->cont = dst;
|
917
|
+
hp->buf = src;
|
918
|
+
http_parser_execute(hp, srcptr, srclen);
|
891
919
|
if (hp->cs == http_parser_error)
|
892
920
|
parser_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
|
893
921
|
|
894
922
|
assert(hp->s.dest_offset <= hp->offset &&
|
895
923
|
"destination buffer overflow");
|
896
|
-
advance_str(
|
897
|
-
rb_str_set_len(
|
924
|
+
advance_str(src, hp->offset);
|
925
|
+
rb_str_set_len(dst, hp->s.dest_offset);
|
898
926
|
|
899
|
-
if (RSTRING_LEN(
|
927
|
+
if (RSTRING_LEN(dst) == 0 && chunked_eof(hp)) {
|
900
928
|
assert(hp->len.chunk == 0 && "chunk at EOF but more to parse");
|
901
929
|
} else {
|
902
|
-
|
930
|
+
src = Qnil;
|
903
931
|
}
|
904
932
|
}
|
905
933
|
} else {
|
906
934
|
/* no need to enter the Ragel machine for unchunked transfers */
|
907
935
|
assert(hp->len.content >= 0 && "negative Content-Length");
|
908
936
|
if (hp->len.content > 0) {
|
909
|
-
long nr = MIN(
|
910
|
-
|
911
|
-
|
912
|
-
|
937
|
+
long nr = MIN(srclen, hp->len.content);
|
938
|
+
|
939
|
+
rb_str_modify(dst);
|
940
|
+
rb_str_resize(dst, nr);
|
941
|
+
/*
|
942
|
+
* using rb_str_replace() to avoid memcpy() doesn't help in
|
943
|
+
* most cases because a GC-aware programmer will pass an explicit
|
944
|
+
* buffer to env["rack.input"].read and reuse the buffer in a loop.
|
945
|
+
* This causes copy-on-write behavior to be triggered anyways
|
946
|
+
* when the +src+ buffer is modified (when reading off the socket).
|
947
|
+
*/
|
948
|
+
hp->buf = src;
|
949
|
+
memcpy(RSTRING_PTR(dst), srcptr, nr);
|
913
950
|
hp->len.content -= nr;
|
914
951
|
if (hp->len.content == 0) {
|
915
952
|
HP_FL_SET(hp, REQEOF);
|
916
953
|
hp->cs = http_parser_first_final;
|
917
954
|
}
|
918
|
-
advance_str(
|
919
|
-
|
920
|
-
data = Qnil;
|
955
|
+
advance_str(src, nr);
|
956
|
+
src = Qnil;
|
921
957
|
}
|
922
958
|
}
|
923
959
|
hp->offset = 0; /* for trailer parsing */
|
924
|
-
return
|
960
|
+
return src;
|
925
961
|
}
|
926
962
|
|
927
963
|
#define SET_GLOBAL(var,str) do { \
|
@@ -947,6 +983,7 @@ void Init_unicorn_http(void)
|
|
947
983
|
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
948
984
|
rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
|
949
985
|
rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
|
986
|
+
rb_define_method(cHttpParser, "dechunk!", HttpParser_dechunk_bang, 0);
|
950
987
|
rb_define_method(cHttpParser, "parse", HttpParser_parse, 0);
|
951
988
|
rb_define_method(cHttpParser, "add_parse", HttpParser_add_parse, 1);
|
952
989
|
rb_define_method(cHttpParser, "headers", HttpParser_headers, 2);
|
data/lib/unicorn.rb
CHANGED
@@ -26,17 +26,14 @@ module Unicorn
|
|
26
26
|
end
|
27
27
|
|
28
28
|
# :stopdoc:
|
29
|
-
def self.run(app, options = {})
|
30
|
-
Unicorn::HttpServer.new(app, options).start.join
|
31
|
-
end
|
32
29
|
|
33
30
|
# This returns a lambda to pass in as the app, this does not "build" the
|
34
31
|
# app (which we defer based on the outcome of "preload_app" in the
|
35
32
|
# Unicorn config). The returned lambda will be called when it is
|
36
33
|
# time to build the app.
|
37
|
-
def self.builder(ru,
|
34
|
+
def self.builder(ru, op)
|
38
35
|
# allow Configurator to parse cli switches embedded in the ru file
|
39
|
-
Unicorn::Configurator::RACKUP.
|
36
|
+
op = Unicorn::Configurator::RACKUP.merge!(:file => ru, :optparse => op)
|
40
37
|
|
41
38
|
# always called after config file parsing, may be called after forking
|
42
39
|
lambda do ||
|
@@ -44,7 +41,7 @@ module Unicorn
|
|
44
41
|
when /\.ru$/
|
45
42
|
raw = File.read(ru)
|
46
43
|
raw.sub!(/^__END__\n.*/, '')
|
47
|
-
eval("Rack::Builder.new {(#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
|
44
|
+
eval("Rack::Builder.new {(\n#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
|
48
45
|
else
|
49
46
|
require ru
|
50
47
|
Object.const_get(File.basename(ru, '.rb').capitalize)
|
@@ -80,6 +77,11 @@ module Unicorn
|
|
80
77
|
Unicorn::SocketHelper.sock_name(io)
|
81
78
|
end
|
82
79
|
end
|
80
|
+
|
81
|
+
def self.log_error(logger, message, exc)
|
82
|
+
logger.error "#{message}: #{exc.message} (#{exc.class})"
|
83
|
+
exc.backtrace.each { |line| logger.error(line) }
|
84
|
+
end
|
83
85
|
# :startdoc:
|
84
86
|
end
|
85
87
|
# :enddoc:
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -264,8 +264,9 @@ class Unicorn::Configurator
|
|
264
264
|
#
|
265
265
|
# This has no effect on UNIX sockets.
|
266
266
|
#
|
267
|
-
# Default: +
|
268
|
-
# +true+ in Rainbows!
|
267
|
+
# Default: +true+ (Nagle's algorithm disabled) in \Unicorn,
|
268
|
+
# +true+ in Rainbows! This defaulted to +false+ in \Unicorn
|
269
|
+
# 3.x
|
269
270
|
#
|
270
271
|
# [:tcp_nopush => true or false]
|
271
272
|
#
|
@@ -279,7 +280,8 @@ class Unicorn::Configurator
|
|
279
280
|
#
|
280
281
|
# This has no effect on UNIX sockets.
|
281
282
|
#
|
282
|
-
# Default: +
|
283
|
+
# Default: +false+
|
284
|
+
# This defaulted to +true+ in \Unicorn 3.4 - 3.7
|
283
285
|
#
|
284
286
|
# [:ipv6only => true or false]
|
285
287
|
#
|
data/lib/unicorn/const.rb
CHANGED
@@ -8,8 +8,8 @@
|
|
8
8
|
# improve things much compared to constants.
|
9
9
|
module Unicorn::Const
|
10
10
|
|
11
|
-
# The current version of Unicorn, currently
|
12
|
-
UNICORN_VERSION = "
|
11
|
+
# The current version of Unicorn, currently 4.0.0
|
12
|
+
UNICORN_VERSION = "4.0.0"
|
13
13
|
|
14
14
|
# default TCP listen host address (0.0.0.0, all interfaces)
|
15
15
|
DEFAULT_HOST = "0.0.0.0"
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -97,7 +97,7 @@ class Unicorn::HttpServer
|
|
97
97
|
self.reexec_pid = 0
|
98
98
|
options = options.dup
|
99
99
|
@ready_pipe = options.delete(:ready_pipe)
|
100
|
-
|
100
|
+
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
101
101
|
options[:use_defaults] = true
|
102
102
|
self.config = Unicorn::Configurator.new(options)
|
103
103
|
self.listener_opts = {}
|
@@ -118,35 +118,7 @@ class Unicorn::HttpServer
|
|
118
118
|
|
119
119
|
# Runs the thing. Returns self so you can run join on it
|
120
120
|
def start
|
121
|
-
|
122
|
-
|
123
|
-
# inherit sockets from parents, they need to be plain Socket objects
|
124
|
-
# before they become Kgio::UNIXServer or Kgio::TCPServer
|
125
|
-
inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
|
126
|
-
io = Socket.for_fd(fd.to_i)
|
127
|
-
set_server_sockopt(io, listener_opts[sock_name(io)])
|
128
|
-
IO_PURGATORY << io
|
129
|
-
logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
|
130
|
-
server_cast(io)
|
131
|
-
end
|
132
|
-
|
133
|
-
config_listeners = config[:listeners].dup
|
134
|
-
LISTENERS.replace(inherited)
|
135
|
-
|
136
|
-
# we start out with generic Socket objects that get cast to either
|
137
|
-
# Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
|
138
|
-
# objects share the same OS-level file descriptor as the higher-level
|
139
|
-
# *Server objects; we need to prevent Socket objects from being
|
140
|
-
# garbage-collected
|
141
|
-
config_listeners -= listener_names
|
142
|
-
if config_listeners.empty? && LISTENERS.empty?
|
143
|
-
config_listeners << Unicorn::Const::DEFAULT_LISTEN
|
144
|
-
init_listeners << Unicorn::Const::DEFAULT_LISTEN
|
145
|
-
START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
|
146
|
-
end
|
147
|
-
config_listeners.each { |addr| listen(addr) }
|
148
|
-
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
149
|
-
|
121
|
+
inherit_listeners!
|
150
122
|
# this pipe is used to wake us up from select(2) in #join when signals
|
151
123
|
# are trapped. See trap_deferred.
|
152
124
|
init_self_pipe!
|
@@ -160,7 +132,7 @@ class Unicorn::HttpServer
|
|
160
132
|
|
161
133
|
self.master_pid = $$
|
162
134
|
build_app! if preload_app
|
163
|
-
|
135
|
+
spawn_missing_workers
|
164
136
|
self
|
165
137
|
end
|
166
138
|
|
@@ -281,8 +253,7 @@ class Unicorn::HttpServer
|
|
281
253
|
logger.info "master process ready" # test_exec.rb relies on this message
|
282
254
|
if @ready_pipe
|
283
255
|
@ready_pipe.syswrite($$.to_s)
|
284
|
-
@ready_pipe.close rescue nil
|
285
|
-
@ready_pipe = nil
|
256
|
+
@ready_pipe = @ready_pipe.close rescue nil
|
286
257
|
end
|
287
258
|
begin
|
288
259
|
reap_all_workers
|
@@ -333,10 +304,8 @@ class Unicorn::HttpServer
|
|
333
304
|
reexec
|
334
305
|
end
|
335
306
|
end
|
336
|
-
rescue Errno::EINTR
|
337
307
|
rescue => e
|
338
|
-
logger
|
339
|
-
logger.error e.backtrace.join("\n")
|
308
|
+
Unicorn.log_error(@logger, "master loop error", e)
|
340
309
|
end while true
|
341
310
|
stop # gracefully shutdown all workers on our way out
|
342
311
|
logger.info "master complete"
|
@@ -403,7 +372,7 @@ class Unicorn::HttpServer
|
|
403
372
|
self.pid = pid.chomp('.oldbin') if pid
|
404
373
|
proc_name 'master'
|
405
374
|
else
|
406
|
-
worker = WORKERS.delete(wpid) and worker.
|
375
|
+
worker = WORKERS.delete(wpid) and worker.close rescue nil
|
407
376
|
m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
|
408
377
|
status.success? ? logger.info(m) : logger.error(m)
|
409
378
|
end
|
@@ -460,22 +429,17 @@ class Unicorn::HttpServer
|
|
460
429
|
proc_name 'master (old)'
|
461
430
|
end
|
462
431
|
|
463
|
-
# forcibly terminate all workers that haven't checked in in timeout
|
464
|
-
# seconds. The timeout is implemented using an unlinked File
|
465
|
-
# shared between the parent process and each worker. The worker
|
466
|
-
# runs File#chmod to modify the ctime of the File. If the ctime
|
467
|
-
# is stale for >timeout seconds, then we'll kill the corresponding
|
468
|
-
# worker.
|
432
|
+
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
469
433
|
def murder_lazy_workers
|
470
434
|
t = @timeout
|
471
435
|
next_sleep = 1
|
436
|
+
now = Time.now.to_i
|
472
437
|
WORKERS.dup.each_pair do |wpid, worker|
|
473
|
-
|
474
|
-
# skip workers that
|
475
|
-
|
476
|
-
|
477
|
-
if
|
478
|
-
tmp = t - diff
|
438
|
+
tick = worker.tick
|
439
|
+
0 == tick and next # skip workers that are sleeping
|
440
|
+
diff = now - tick
|
441
|
+
tmp = t - diff
|
442
|
+
if tmp >= 0
|
479
443
|
next_sleep < tmp and next_sleep = tmp
|
480
444
|
next
|
481
445
|
end
|
@@ -488,7 +452,9 @@ class Unicorn::HttpServer
|
|
488
452
|
|
489
453
|
def after_fork_internal
|
490
454
|
@ready_pipe.close if @ready_pipe
|
491
|
-
|
455
|
+
Unicorn::Configurator::RACKUP.clear
|
456
|
+
@ready_pipe = @init_listeners = @config = @before_exec = @before_fork = nil
|
457
|
+
|
492
458
|
srand # http://redmine.ruby-lang.org/issues/4338
|
493
459
|
|
494
460
|
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
@@ -497,15 +463,22 @@ class Unicorn::HttpServer
|
|
497
463
|
end
|
498
464
|
|
499
465
|
def spawn_missing_workers
|
500
|
-
|
501
|
-
|
502
|
-
|
466
|
+
worker_nr = -1
|
467
|
+
until (worker_nr += 1) == @worker_processes
|
468
|
+
WORKERS.value?(worker_nr) and next
|
469
|
+
worker = Worker.new(worker_nr)
|
503
470
|
before_fork.call(self, worker)
|
504
|
-
|
471
|
+
if pid = fork
|
472
|
+
WORKERS[pid] = worker
|
473
|
+
else
|
505
474
|
after_fork_internal
|
506
475
|
worker_loop(worker)
|
507
|
-
|
476
|
+
exit
|
477
|
+
end
|
508
478
|
end
|
479
|
+
rescue => e
|
480
|
+
@logger.error(e) rescue nil
|
481
|
+
exit!
|
509
482
|
end
|
510
483
|
|
511
484
|
def maintain_worker_count
|
@@ -531,8 +504,7 @@ class Unicorn::HttpServer
|
|
531
504
|
when Unicorn::HttpParserError # try to tell the client they're bad
|
532
505
|
Unicorn::Const::ERROR_400_RESPONSE
|
533
506
|
else
|
534
|
-
logger
|
535
|
-
logger.error e.backtrace.join("\n")
|
507
|
+
Unicorn.log_error(@logger, "app error", e)
|
536
508
|
Unicorn::Const::ERROR_500_RESPONSE
|
537
509
|
end
|
538
510
|
client.kgio_trywrite(msg)
|
@@ -568,10 +540,8 @@ class Unicorn::HttpServer
|
|
568
540
|
proc_name "worker[#{worker.nr}]"
|
569
541
|
START_CTX.clear
|
570
542
|
init_self_pipe!
|
571
|
-
WORKERS.values.each { |other| other.tmp.close rescue nil }
|
572
543
|
WORKERS.clear
|
573
544
|
LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
574
|
-
worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
575
545
|
after_fork.call(self, worker) # can drop perms
|
576
546
|
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
577
547
|
self.timeout /= 2.0 # halve it for select()
|
@@ -595,35 +565,25 @@ class Unicorn::HttpServer
|
|
595
565
|
ppid = master_pid
|
596
566
|
init_worker_process(worker)
|
597
567
|
nr = 0 # this becomes negative if we need to reopen logs
|
598
|
-
|
599
|
-
ready =
|
568
|
+
l = LISTENERS.dup
|
569
|
+
ready = l.dup
|
600
570
|
|
601
571
|
# closing anything we IO.select on will raise EBADF
|
602
572
|
trap(:USR1) { nr = -65536; SELF_PIPE[0].close rescue nil }
|
603
|
-
trap(:QUIT) {
|
573
|
+
trap(:QUIT) { worker = nil; LISTENERS.each { |s| s.close rescue nil }.clear }
|
604
574
|
[:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
|
605
575
|
logger.info "worker=#{worker.nr} ready"
|
606
|
-
m = 0
|
607
576
|
|
608
577
|
begin
|
609
578
|
nr < 0 and reopen_worker_logs(worker.nr)
|
610
579
|
nr = 0
|
611
580
|
|
612
|
-
|
613
|
-
|
614
|
-
# futimes() is not available in base Ruby and I very strongly
|
615
|
-
# prefer temporary files to be unlinked for security,
|
616
|
-
# performance and reliability reasons, so utime is out. No-op
|
617
|
-
# changes with chmod doesn't update ctime on all filesystems; so
|
618
|
-
# we change our counter each and every time (after process_client
|
619
|
-
# and before IO.select).
|
620
|
-
alive.chmod(m = 0 == m ? 1 : 0)
|
621
|
-
|
622
|
-
ready.each do |sock|
|
581
|
+
worker.tick = Time.now.to_i
|
582
|
+
while sock = ready.shift
|
623
583
|
if client = sock.kgio_tryaccept
|
624
584
|
process_client(client)
|
625
585
|
nr += 1
|
626
|
-
|
586
|
+
worker.tick = Time.now.to_i
|
627
587
|
end
|
628
588
|
break if nr < 0
|
629
589
|
end
|
@@ -632,23 +592,21 @@ class Unicorn::HttpServer
|
|
632
592
|
# we're probably reasonably busy, so avoid calling select()
|
633
593
|
# and do a speculative non-blocking accept() on ready listeners
|
634
594
|
# before we sleep again in select().
|
635
|
-
|
595
|
+
unless nr == 0 # (nr < 0) => reopen logs (unlikely)
|
596
|
+
ready = l.dup
|
597
|
+
redo
|
598
|
+
end
|
636
599
|
|
637
600
|
ppid == Process.ppid or return
|
638
|
-
alive.chmod(m = 0 == m ? 1 : 0)
|
639
601
|
|
640
602
|
# timeout used so we can detect parent death:
|
641
|
-
|
642
|
-
|
643
|
-
ready = LISTENERS
|
603
|
+
worker.tick = Time.now.to_i
|
604
|
+
ret = IO.select(l, nil, SELF_PIPE, @timeout) and ready = ret[0]
|
644
605
|
rescue Errno::EBADF
|
645
606
|
nr < 0 or return
|
646
607
|
rescue => e
|
647
|
-
if
|
648
|
-
|
649
|
-
logger.error e.backtrace.join("\n")
|
650
|
-
end
|
651
|
-
end while alive
|
608
|
+
Unicorn.log_error(@logger, "listen loop error", e) if worker
|
609
|
+
end while worker
|
652
610
|
end
|
653
611
|
|
654
612
|
# delivers a signal to a worker and fails gracefully if the worker
|
@@ -656,7 +614,7 @@ class Unicorn::HttpServer
|
|
656
614
|
def kill_worker(signal, wpid)
|
657
615
|
Process.kill(signal, wpid)
|
658
616
|
rescue Errno::ESRCH
|
659
|
-
worker = WORKERS.delete(wpid) and worker.
|
617
|
+
worker = WORKERS.delete(wpid) and worker.close rescue nil
|
660
618
|
end
|
661
619
|
|
662
620
|
# delivers a signal to each worker
|
@@ -686,7 +644,7 @@ class Unicorn::HttpServer
|
|
686
644
|
def load_config!
|
687
645
|
loaded_app = app
|
688
646
|
logger.info "reloading config_file=#{config.config_file}"
|
689
|
-
config[:listeners].replace(init_listeners)
|
647
|
+
config[:listeners].replace(@init_listeners)
|
690
648
|
config.reload
|
691
649
|
config.commit!(self)
|
692
650
|
kill_each_worker(:QUIT)
|
@@ -695,8 +653,8 @@ class Unicorn::HttpServer
|
|
695
653
|
build_app! if preload_app
|
696
654
|
logger.info "done reloading config_file=#{config.config_file}"
|
697
655
|
rescue StandardError, LoadError, SyntaxError => e
|
698
|
-
logger
|
699
|
-
|
656
|
+
Unicorn.log_error(@logger,
|
657
|
+
"error reloading config_file=#{config.config_file}", e)
|
700
658
|
self.app = loaded_app
|
701
659
|
end
|
702
660
|
|
@@ -730,5 +688,33 @@ class Unicorn::HttpServer
|
|
730
688
|
SELF_PIPE.replace(Kgio::Pipe.new)
|
731
689
|
SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
732
690
|
end
|
733
|
-
end
|
734
691
|
|
692
|
+
def inherit_listeners!
|
693
|
+
# inherit sockets from parents, they need to be plain Socket objects
|
694
|
+
# before they become Kgio::UNIXServer or Kgio::TCPServer
|
695
|
+
inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
|
696
|
+
io = Socket.for_fd(fd.to_i)
|
697
|
+
set_server_sockopt(io, listener_opts[sock_name(io)])
|
698
|
+
IO_PURGATORY << io
|
699
|
+
logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
|
700
|
+
server_cast(io)
|
701
|
+
end
|
702
|
+
|
703
|
+
config_listeners = config[:listeners].dup
|
704
|
+
LISTENERS.replace(inherited)
|
705
|
+
|
706
|
+
# we start out with generic Socket objects that get cast to either
|
707
|
+
# Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
|
708
|
+
# objects share the same OS-level file descriptor as the higher-level
|
709
|
+
# *Server objects; we need to prevent Socket objects from being
|
710
|
+
# garbage-collected
|
711
|
+
config_listeners -= listener_names
|
712
|
+
if config_listeners.empty? && LISTENERS.empty?
|
713
|
+
config_listeners << Unicorn::Const::DEFAULT_LISTEN
|
714
|
+
@init_listeners << Unicorn::Const::DEFAULT_LISTEN
|
715
|
+
START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
|
716
|
+
end
|
717
|
+
config_listeners.each { |addr| listen(addr) }
|
718
|
+
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
719
|
+
end
|
720
|
+
end
|
@@ -27,9 +27,9 @@ module Unicorn
|
|
27
27
|
# same default value as Mongrel
|
28
28
|
:backlog => 1024,
|
29
29
|
|
30
|
-
#
|
31
|
-
|
32
|
-
:
|
30
|
+
# favor latency over bandwidth savings
|
31
|
+
:tcp_nopush => false,
|
32
|
+
:tcp_nodelay => true,
|
33
33
|
}
|
34
34
|
#:startdoc:
|
35
35
|
|
@@ -101,8 +101,7 @@ module Unicorn
|
|
101
101
|
end
|
102
102
|
sock.listen(opt[:backlog])
|
103
103
|
rescue => e
|
104
|
-
logger
|
105
|
-
logger.error e.backtrace.join("\n")
|
104
|
+
Unicorn.log_error(logger, message, e)
|
106
105
|
end
|
107
106
|
|
108
107
|
def log_buffer_sizes(sock, pfx = '')
|
data/lib/unicorn/worker.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
require "raindrops"
|
2
3
|
|
3
4
|
# This class and its members can be considered a stable interface
|
4
5
|
# and will not change in a backwards-incompatible fashion between
|
@@ -7,13 +8,53 @@
|
|
7
8
|
#
|
8
9
|
# Some users may want to access it in the before_fork/after_fork hooks.
|
9
10
|
# See the Unicorn::Configurator RDoc for examples.
|
10
|
-
class Unicorn::Worker
|
11
|
+
class Unicorn::Worker
|
12
|
+
# :stopdoc:
|
13
|
+
attr_accessor :nr, :switched
|
14
|
+
attr_writer :tmp
|
15
|
+
|
16
|
+
PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
|
17
|
+
DROPS = []
|
18
|
+
|
19
|
+
def initialize(nr)
|
20
|
+
drop_index = nr / PER_DROP
|
21
|
+
@raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
|
22
|
+
@offset = nr % PER_DROP
|
23
|
+
@raindrop[@offset] = 0
|
24
|
+
@nr = nr
|
25
|
+
@tmp = @switched = false
|
26
|
+
end
|
11
27
|
|
12
28
|
# worker objects may be compared to just plain Integers
|
13
29
|
def ==(other_nr) # :nodoc:
|
14
|
-
nr == other_nr
|
30
|
+
@nr == other_nr
|
31
|
+
end
|
32
|
+
|
33
|
+
# called in the worker process
|
34
|
+
def tick=(value) # :nodoc:
|
35
|
+
@raindrop[@offset] = value
|
36
|
+
end
|
37
|
+
|
38
|
+
# called in the master process
|
39
|
+
def tick # :nodoc:
|
40
|
+
@raindrop[@offset]
|
15
41
|
end
|
16
42
|
|
43
|
+
# only exists for compatibility
|
44
|
+
def tmp # :nodoc:
|
45
|
+
@tmp ||= begin
|
46
|
+
tmp = Unicorn::TmpIO.new
|
47
|
+
tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
48
|
+
tmp
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def close # :nodoc:
|
53
|
+
@tmp.close if @tmp
|
54
|
+
end
|
55
|
+
|
56
|
+
# :startdoc:
|
57
|
+
|
17
58
|
# In most cases, you should be using the Unicorn::Configurator#user
|
18
59
|
# directive instead. This method should only be used if you need
|
19
60
|
# fine-grained control of exactly when you want to change permissions
|
@@ -36,12 +77,12 @@ class Unicorn::Worker < Struct.new(:nr, :tmp, :switched)
|
|
36
77
|
uid = Etc.getpwnam(user).uid
|
37
78
|
gid = Etc.getgrnam(group).gid if group
|
38
79
|
Unicorn::Util.chown_logs(uid, gid)
|
39
|
-
tmp.chown(uid, gid)
|
80
|
+
@tmp.chown(uid, gid) if @tmp
|
40
81
|
if gid && Process.egid != gid
|
41
82
|
Process.initgroups(user, gid)
|
42
83
|
Process::GID.change_privilege(gid)
|
43
84
|
end
|
44
85
|
Process.euid != uid and Process::UID.change_privilege(uid)
|
45
|
-
|
86
|
+
@switched = true
|
46
87
|
end
|
47
88
|
end
|
data/script/isolate_for_tests
CHANGED
data/t/broken-app.ru
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# we do not want Rack::Lint or anything to protect us
|
2
|
+
use Rack::ContentLength
|
3
|
+
use Rack::ContentType, "text/plain"
|
4
|
+
map "/" do
|
5
|
+
run lambda { |env| [ 200, {}, [ "OK\n" ] ] }
|
6
|
+
end
|
7
|
+
map "/raise" do
|
8
|
+
run lambda { |env| raise "BAD" }
|
9
|
+
end
|
10
|
+
map "/nil" do
|
11
|
+
run lambda { |env| nil }
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
use Rack::ContentLength
|
2
|
+
headers = { 'Content-Type' => 'text/plain' }
|
3
|
+
run lambda { |env|
|
4
|
+
case env['PATH_INFO']
|
5
|
+
when "/block-forever"
|
6
|
+
Process.kill(:STOP, $$)
|
7
|
+
sleep # in case STOP signal is not received in time
|
8
|
+
[ 500, headers, [ "Should never get here\n" ] ]
|
9
|
+
else
|
10
|
+
[ 200, headers, [ "#$$\n" ] ]
|
11
|
+
end
|
12
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
|
4
|
+
t_plan 11 "heartbeat/timeout test"
|
5
|
+
|
6
|
+
t_begin "setup and startup" && {
|
7
|
+
unicorn_setup
|
8
|
+
echo timeout 3 >> $unicorn_config
|
9
|
+
echo preload_app true >> $unicorn_config
|
10
|
+
unicorn -D heartbeat-timeout.ru -c $unicorn_config
|
11
|
+
unicorn_wait_start
|
12
|
+
}
|
13
|
+
|
14
|
+
t_begin "read worker PID" && {
|
15
|
+
worker_pid=$(curl -sSf http://$listen/)
|
16
|
+
t_info "worker_pid=$worker_pid"
|
17
|
+
}
|
18
|
+
|
19
|
+
t_begin "sleep for a bit, ensure worker PID does not change" && {
|
20
|
+
sleep 4
|
21
|
+
test $(curl -sSf http://$listen/) -eq $worker_pid
|
22
|
+
}
|
23
|
+
|
24
|
+
t_begin "block the worker process to force it to die" && {
|
25
|
+
rm $ok
|
26
|
+
t0=$(date +%s)
|
27
|
+
err="$(curl -sSf http://$listen/block-forever 2>&1 || > $ok)"
|
28
|
+
t1=$(date +%s)
|
29
|
+
elapsed=$(($t1 - $t0))
|
30
|
+
t_info "elapsed=$elapsed err=$err"
|
31
|
+
test x"$err" != x"Should never get here"
|
32
|
+
test x"$err" != x"$worker_pid"
|
33
|
+
}
|
34
|
+
|
35
|
+
t_begin "ensure worker was killed" && {
|
36
|
+
test -e $ok
|
37
|
+
test 1 -eq $(grep timeout $r_err | grep killing | wc -l)
|
38
|
+
}
|
39
|
+
|
40
|
+
t_begin "ensure timeout took at least 3 seconds" && {
|
41
|
+
test $elapsed -ge 3
|
42
|
+
}
|
43
|
+
|
44
|
+
t_begin "we get a fresh new worker process" && {
|
45
|
+
new_worker_pid=$(curl -sSf http://$listen/)
|
46
|
+
test $new_worker_pid -ne $worker_pid
|
47
|
+
}
|
48
|
+
|
49
|
+
t_begin "truncate the server error log" && {
|
50
|
+
> $r_err
|
51
|
+
}
|
52
|
+
|
53
|
+
t_begin "SIGSTOP and SIGCONT on unicorn master does not kill worker" && {
|
54
|
+
kill -STOP $unicorn_pid
|
55
|
+
sleep 4
|
56
|
+
kill -CONT $unicorn_pid
|
57
|
+
sleep 2
|
58
|
+
test $new_worker_pid -eq $(curl -sSf http://$listen/)
|
59
|
+
}
|
60
|
+
|
61
|
+
t_begin "stop server" && {
|
62
|
+
kill -QUIT $unicorn_pid
|
63
|
+
}
|
64
|
+
|
65
|
+
t_begin "check stderr" && check_stderr
|
66
|
+
|
67
|
+
dbgcat r_err
|
68
|
+
|
69
|
+
t_done
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
|
4
|
+
t_plan 9 "graceful handling of broken apps"
|
5
|
+
|
6
|
+
t_begin "setup and start" && {
|
7
|
+
unicorn_setup
|
8
|
+
unicorn -E none -D broken-app.ru -c $unicorn_config
|
9
|
+
unicorn_wait_start
|
10
|
+
}
|
11
|
+
|
12
|
+
t_begin "normal response is alright" && {
|
13
|
+
test xOK = x"$(curl -sSf http://$listen/)"
|
14
|
+
}
|
15
|
+
|
16
|
+
t_begin "app raised exception" && {
|
17
|
+
curl -sSf http://$listen/raise 2> $tmp || :
|
18
|
+
grep -F 500 $tmp
|
19
|
+
> $tmp
|
20
|
+
}
|
21
|
+
|
22
|
+
t_begin "app exception logged and backtrace not swallowed" && {
|
23
|
+
grep -F 'app error' $r_err
|
24
|
+
grep -A1 -F 'app error' $r_err | tail -1 | grep broken-app.ru:
|
25
|
+
dbgcat r_err
|
26
|
+
> $r_err
|
27
|
+
}
|
28
|
+
|
29
|
+
t_begin "trigger bad response" && {
|
30
|
+
curl -sSf http://$listen/nil 2> $tmp || :
|
31
|
+
grep -F 500 $tmp
|
32
|
+
> $tmp
|
33
|
+
}
|
34
|
+
|
35
|
+
t_begin "app exception logged" && {
|
36
|
+
grep -F 'app error' $r_err
|
37
|
+
> $r_err
|
38
|
+
}
|
39
|
+
|
40
|
+
t_begin "normal responses alright afterwards" && {
|
41
|
+
> $tmp
|
42
|
+
curl -sSf http://$listen/ >> $tmp &
|
43
|
+
curl -sSf http://$listen/ >> $tmp &
|
44
|
+
curl -sSf http://$listen/ >> $tmp &
|
45
|
+
curl -sSf http://$listen/ >> $tmp &
|
46
|
+
wait
|
47
|
+
test xOK = x$(sort < $tmp | uniq)
|
48
|
+
}
|
49
|
+
|
50
|
+
t_begin "teardown" && {
|
51
|
+
kill $unicorn_pid
|
52
|
+
}
|
53
|
+
|
54
|
+
t_begin "check stderr" && check_stderr
|
55
|
+
|
56
|
+
t_done
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
|
4
|
+
t_plan 5 "at_exit/END handlers work as expected"
|
5
|
+
|
6
|
+
t_begin "setup and startup" && {
|
7
|
+
unicorn_setup
|
8
|
+
cat >> $unicorn_config <<EOF
|
9
|
+
at_exit { \$stdout.syswrite("#{Process.pid} BOTH\\n") }
|
10
|
+
END { \$stdout.syswrite("#{Process.pid} END BOTH\\n") }
|
11
|
+
after_fork do |_,_|
|
12
|
+
at_exit { \$stdout.syswrite("#{Process.pid} WORKER ONLY\\n") }
|
13
|
+
END { \$stdout.syswrite("#{Process.pid} END WORKER ONLY\\n") }
|
14
|
+
end
|
15
|
+
EOF
|
16
|
+
|
17
|
+
unicorn -D pid.ru -c $unicorn_config
|
18
|
+
unicorn_wait_start
|
19
|
+
}
|
20
|
+
|
21
|
+
t_begin "read worker PID" && {
|
22
|
+
worker_pid=$(curl -sSf http://$listen/)
|
23
|
+
t_info "worker_pid=$worker_pid"
|
24
|
+
}
|
25
|
+
|
26
|
+
t_begin "issue graceful shutdown (SIGQUIT) and wait for termination" && {
|
27
|
+
kill -QUIT $unicorn_pid
|
28
|
+
|
29
|
+
while kill -0 $unicorn_pid >/dev/null 2>&1
|
30
|
+
do
|
31
|
+
sleep 1
|
32
|
+
done
|
33
|
+
}
|
34
|
+
|
35
|
+
t_begin "check stderr" && check_stderr
|
36
|
+
|
37
|
+
dbgcat r_err
|
38
|
+
dbgcat r_out
|
39
|
+
|
40
|
+
t_begin "all at_exit handlers ran" && {
|
41
|
+
grep "$worker_pid BOTH" $r_out
|
42
|
+
grep "$unicorn_pid BOTH" $r_out
|
43
|
+
grep "$worker_pid END BOTH" $r_out
|
44
|
+
grep "$unicorn_pid END BOTH" $r_out
|
45
|
+
grep "$worker_pid WORKER ONLY" $r_out
|
46
|
+
grep "$worker_pid END WORKER ONLY" $r_out
|
47
|
+
}
|
48
|
+
|
49
|
+
t_done
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'unicorn'
|
3
|
+
|
4
|
+
class TestDroplet < Test::Unit::TestCase
|
5
|
+
def test_create_many_droplets
|
6
|
+
now = Time.now.to_i
|
7
|
+
tmp = (0..1024).map do |i|
|
8
|
+
droplet = Unicorn::Worker.new(i)
|
9
|
+
assert droplet.respond_to?(:tick)
|
10
|
+
assert_equal 0, droplet.tick
|
11
|
+
assert_equal(now, droplet.tick = now)
|
12
|
+
assert_equal now, droplet.tick
|
13
|
+
assert_equal(0, droplet.tick = 0)
|
14
|
+
assert_equal 0, droplet.tick
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_shared_process
|
19
|
+
droplet = Unicorn::Worker.new(0)
|
20
|
+
_, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) })
|
21
|
+
assert status.success?, status.inspect
|
22
|
+
assert_equal 1, droplet.tick
|
23
|
+
|
24
|
+
_, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) })
|
25
|
+
assert status.success?, status.inspect
|
26
|
+
assert_equal 2, droplet.tick
|
27
|
+
end
|
28
|
+
end
|
@@ -740,7 +740,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
740
740
|
# then that large header names are caught
|
741
741
|
10.times do |c|
|
742
742
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
743
|
-
assert_raises
|
743
|
+
assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
|
744
744
|
parser.buf << get
|
745
745
|
parser.parse
|
746
746
|
parser.clear
|
@@ -750,7 +750,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
750
750
|
# then that large mangled field values are caught
|
751
751
|
10.times do |c|
|
752
752
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
753
|
-
assert_raises
|
753
|
+
assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
|
754
754
|
parser.buf << get
|
755
755
|
parser.parse
|
756
756
|
parser.clear
|
@@ -761,7 +761,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
761
761
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
|
762
762
|
get << "X-Test: test\r\n" * (80 * 1024)
|
763
763
|
parser.buf << get
|
764
|
-
assert_raises
|
764
|
+
assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
|
765
765
|
parser.parse
|
766
766
|
end
|
767
767
|
parser.clear
|
@@ -769,7 +769,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
769
769
|
# finally just that random garbage gets blocked all the time
|
770
770
|
10.times do |c|
|
771
771
|
get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
772
|
-
assert_raises
|
772
|
+
assert_raises(Unicorn::HttpParserError,Unicorn::RequestURITooLongError) do
|
773
773
|
parser.buf << get
|
774
774
|
parser.parse
|
775
775
|
parser.clear
|
@@ -237,6 +237,17 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
237
237
|
assert @parser.keepalive?
|
238
238
|
end
|
239
239
|
|
240
|
+
def test_chunked_empty
|
241
|
+
str = @parser.buf
|
242
|
+
req = @parser.env
|
243
|
+
str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
244
|
+
assert_equal req, @parser.parse, "msg=#{str}"
|
245
|
+
assert_equal 0, str.size
|
246
|
+
tmp = ""
|
247
|
+
assert_equal str, @parser.filter_body(tmp, str << "0\r\n\r\n")
|
248
|
+
assert_equal "", tmp
|
249
|
+
end
|
250
|
+
|
240
251
|
def test_two_chunks
|
241
252
|
str = @parser.buf
|
242
253
|
str << "PUT / HTTP/1.1\r\ntransfer-Encoding: chunked\r\n\r\n"
|
@@ -651,4 +662,47 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
651
662
|
assert_equal expect, @parser.parse
|
652
663
|
assert ! @parser.next?
|
653
664
|
end
|
665
|
+
|
666
|
+
def test_chunk_only
|
667
|
+
tmp = ""
|
668
|
+
assert_equal @parser, @parser.dechunk!
|
669
|
+
assert_nil @parser.filter_body(tmp, "6\r\n")
|
670
|
+
assert_equal "", tmp
|
671
|
+
assert_nil @parser.filter_body(tmp, "abcdef")
|
672
|
+
assert_equal "abcdef", tmp
|
673
|
+
assert_nil @parser.filter_body(tmp, "\r\n")
|
674
|
+
assert_equal "", tmp
|
675
|
+
src = "0\r\n\r\n"
|
676
|
+
assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
|
677
|
+
assert_equal "", tmp
|
678
|
+
end
|
679
|
+
|
680
|
+
def test_chunk_only_bad_align
|
681
|
+
tmp = ""
|
682
|
+
assert_equal @parser, @parser.dechunk!
|
683
|
+
assert_nil @parser.filter_body(tmp, "6\r\na")
|
684
|
+
assert_equal "a", tmp
|
685
|
+
assert_nil @parser.filter_body(tmp, "bcde")
|
686
|
+
assert_equal "bcde", tmp
|
687
|
+
assert_nil @parser.filter_body(tmp, "f\r")
|
688
|
+
assert_equal "f", tmp
|
689
|
+
src = "\n0\r\n\r\n"
|
690
|
+
assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
|
691
|
+
assert_equal "", tmp
|
692
|
+
end
|
693
|
+
|
694
|
+
def test_chunk_only_reset_ok
|
695
|
+
tmp = ""
|
696
|
+
assert_equal @parser, @parser.dechunk!
|
697
|
+
src = "1\r\na\r\n0\r\n\r\n"
|
698
|
+
assert_nil @parser.filter_body(tmp, src)
|
699
|
+
assert_equal "a", tmp
|
700
|
+
assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
|
701
|
+
|
702
|
+
assert_equal @parser, @parser.dechunk!
|
703
|
+
src = "0\r\n\r\n"
|
704
|
+
assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
|
705
|
+
assert_equal "", tmp
|
706
|
+
assert_equal src, @parser.filter_body(tmp, src)
|
707
|
+
end
|
654
708
|
end
|
data/test/unit/test_response.rb
CHANGED
data/test/unit/test_server.rb
CHANGED
data/test/unit/test_signals.rb
CHANGED
data/test/unit/test_upload.rb
CHANGED
data/unicorn.gemspec
CHANGED
@@ -34,7 +34,8 @@ Gem::Specification.new do |s|
|
|
34
34
|
# commented out. Nevertheless, upgrading to Rails 2.3.4 or later is
|
35
35
|
# *strongly* recommended for security reasons.
|
36
36
|
s.add_dependency(%q<rack>)
|
37
|
-
s.add_dependency(%q<kgio>, '~> 2.
|
37
|
+
s.add_dependency(%q<kgio>, '~> 2.4')
|
38
|
+
s.add_dependency(%q<raindrops>, '~> 0.6')
|
38
39
|
|
39
40
|
s.add_development_dependency('isolate', '~> 3.1')
|
40
41
|
s.add_development_dependency('wrongdoc', '~> 1.5')
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 63
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
|
-
-
|
8
|
-
- 7
|
7
|
+
- 4
|
9
8
|
- 0
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 4.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Unicorn hackers
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-06-
|
18
|
+
date: 2011-06-27 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: rack
|
@@ -39,17 +39,32 @@ dependencies:
|
|
39
39
|
requirements:
|
40
40
|
- - ~>
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
hash:
|
42
|
+
hash: 11
|
43
43
|
segments:
|
44
44
|
- 2
|
45
|
-
-
|
46
|
-
version: "2.
|
45
|
+
- 4
|
46
|
+
version: "2.4"
|
47
47
|
type: :runtime
|
48
48
|
version_requirements: *id002
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
|
-
name:
|
50
|
+
name: raindrops
|
51
51
|
prerelease: false
|
52
52
|
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 7
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
- 6
|
61
|
+
version: "0.6"
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: isolate
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
68
|
none: false
|
54
69
|
requirements:
|
55
70
|
- - ~>
|
@@ -60,11 +75,11 @@ dependencies:
|
|
60
75
|
- 1
|
61
76
|
version: "3.1"
|
62
77
|
type: :development
|
63
|
-
version_requirements: *
|
78
|
+
version_requirements: *id004
|
64
79
|
- !ruby/object:Gem::Dependency
|
65
80
|
name: wrongdoc
|
66
81
|
prerelease: false
|
67
|
-
requirement: &
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
68
83
|
none: false
|
69
84
|
requirements:
|
70
85
|
- - ~>
|
@@ -75,7 +90,7 @@ dependencies:
|
|
75
90
|
- 5
|
76
91
|
version: "1.5"
|
77
92
|
type: :development
|
78
|
-
version_requirements: *
|
93
|
+
version_requirements: *id005
|
79
94
|
description: |-
|
80
95
|
\Unicorn is an HTTP server for Rack applications designed to only serve
|
81
96
|
fast clients on low-latency, high-bandwidth connections and take
|
@@ -212,7 +227,9 @@ files:
|
|
212
227
|
- t/bin/sha1sum.rb
|
213
228
|
- t/bin/unused_listen
|
214
229
|
- t/bin/utee
|
230
|
+
- t/broken-app.ru
|
215
231
|
- t/env.ru
|
232
|
+
- t/heartbeat-timeout.ru
|
216
233
|
- t/my-tap-lib.sh
|
217
234
|
- t/oob_gc.ru
|
218
235
|
- t/oob_gc_path.ru
|
@@ -255,12 +272,14 @@ files:
|
|
255
272
|
- t/t0002-config-conflict.sh
|
256
273
|
- t/t0002-parser-error.sh
|
257
274
|
- t/t0003-working_directory.sh
|
275
|
+
- t/t0004-heartbeat-timeout.sh
|
258
276
|
- t/t0004-working_directory_broken.sh
|
259
277
|
- t/t0005-working_directory_app.rb.sh
|
260
278
|
- t/t0006-reopen-logs.sh
|
261
279
|
- t/t0006.ru
|
262
280
|
- t/t0007-working_directory_no_embed_cli.sh
|
263
281
|
- t/t0008-back_out_of_upgrade.sh
|
282
|
+
- t/t0009-broken-app.sh
|
264
283
|
- t/t0009-winch_ttin.sh
|
265
284
|
- t/t0010-reap-logging.sh
|
266
285
|
- t/t0011-active-unix-socket.sh
|
@@ -274,6 +293,7 @@ files:
|
|
274
293
|
- t/t0017-trust-x-forwarded-true.sh
|
275
294
|
- t/t0018-write-on-close.sh
|
276
295
|
- t/t0019-max_header_len.sh
|
296
|
+
- t/t0020-at_exit-handler.sh
|
277
297
|
- t/t0100-rack-input-tests.sh
|
278
298
|
- t/t0116-client_body_buffer_size.sh
|
279
299
|
- t/t0116.ru
|
@@ -291,6 +311,7 @@ files:
|
|
291
311
|
- test/aggregate.rb
|
292
312
|
- test/benchmark/README
|
293
313
|
- test/benchmark/dd.ru
|
314
|
+
- test/benchmark/stack.ru
|
294
315
|
- test/exec/README
|
295
316
|
- test/exec/test_exec.rb
|
296
317
|
- test/rails/app-1.2.3/.gitignore
|
@@ -356,6 +377,7 @@ files:
|
|
356
377
|
- test/rails/test_rails.rb
|
357
378
|
- test/test_helper.rb
|
358
379
|
- test/unit/test_configurator.rb
|
380
|
+
- test/unit/test_droplet.rb
|
359
381
|
- test/unit/test_http_parser.rb
|
360
382
|
- test/unit/test_http_parser_ng.rb
|
361
383
|
- test/unit/test_http_parser_xftrust.rb
|