unicorn 3.7.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|