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 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
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v3.7.0.GIT
4
+ DEF_VER=v4.0.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/TODO CHANGED
@@ -2,6 +2,4 @@
2
2
 
3
3
  * improve test suite
4
4
 
5
- * scalability to >= 1024 worker processes for crazy NUMA systems
6
-
7
5
  * Rack 2.x support (when Rack 2.x exists)
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.run(app, options)
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.run(app, options)
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(buf, data) => nil/data
887
+ * parser.filter_body(dst, src) => nil/src
860
888
  *
861
- * Takes a String of +data+, will modify data if dechunking is done.
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
- * +data+ if body processing is complete. When returning +data+,
864
- * it may modify +data+ so the start of the string points to where
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 +data+
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 buf, VALUE data)
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 *dptr;
875
- long dlen;
902
+ char *srcptr;
903
+ long srclen;
876
904
 
877
- dptr = RSTRING_PTR(data);
878
- dlen = RSTRING_LEN(data);
905
+ srcptr = RSTRING_PTR(src);
906
+ srclen = RSTRING_LEN(src);
879
907
 
880
- StringValue(buf);
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 = buf;
889
- hp->buf = data;
890
- http_parser_execute(hp, dptr, dlen);
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(data, hp->offset);
897
- rb_str_set_len(buf, hp->s.dest_offset);
924
+ advance_str(src, hp->offset);
925
+ rb_str_set_len(dst, hp->s.dest_offset);
898
926
 
899
- if (RSTRING_LEN(buf) == 0 && chunked_eof(hp)) {
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
- data = Qnil;
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(dlen, hp->len.content);
910
-
911
- hp->buf = data;
912
- memcpy(RSTRING_PTR(buf), dptr, nr);
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(data, nr);
919
- rb_str_set_len(buf, nr);
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 data;
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, opts)
34
+ def self.builder(ru, op)
38
35
  # allow Configurator to parse cli switches embedded in the ru file
39
- Unicorn::Configurator::RACKUP.update(:file => ru, :optparse => opts)
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:
@@ -264,8 +264,9 @@ class Unicorn::Configurator
264
264
  #
265
265
  # This has no effect on UNIX sockets.
266
266
  #
267
- # Default: +false+ (Nagle's algorithm enabled) in \Unicorn,
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: +true+ in \Unicorn 3.4+, +false+ in Rainbows!
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 3.7.0
12
- UNICORN_VERSION = "3.7.0"
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"
@@ -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
- self.init_listeners = options[:listeners] ? options[:listeners].dup : []
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
- BasicSocket.do_not_reverse_lookup = true
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
- maintain_worker_count
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.error "Unhandled master loop exception #{e.inspect}."
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.tmp.close rescue nil
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
- stat = worker.tmp.stat
474
- # skip workers that disable fchmod or have never fchmod-ed
475
- stat.mode == 0100600 and next
476
- diff = Time.now - stat.ctime
477
- if diff <= t
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
- self.ready_pipe = nil # XXX Rainbows! compat, change for Unicorn 4.x
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
- (0...worker_processes).each do |worker_nr|
501
- WORKERS.values.include?(worker_nr) and next
502
- worker = Worker.new(worker_nr, Unicorn::TmpIO.new)
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
- WORKERS[fork {
471
+ if pid = fork
472
+ WORKERS[pid] = worker
473
+ else
505
474
  after_fork_internal
506
475
  worker_loop(worker)
507
- }] = worker
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.error "Read error: #{e.inspect}"
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
- alive = worker.tmp # tmp is our lifeline to the master process
599
- ready = LISTENERS
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) { alive = nil; LISTENERS.each { |s| s.close rescue nil }.clear }
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
- # we're a goner in timeout seconds anyways if alive.chmod
613
- # breaks, so don't trap the exception. Using fchmod() since
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
- alive.chmod(m = 0 == m ? 1 : 0)
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
- redo unless nr == 0 # (nr < 0) => reopen logs
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
- ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) and ready = ret[0]
642
- rescue Errno::EINTR
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 alive
648
- logger.error "Unhandled listen loop exception #{e.inspect}."
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.tmp.close rescue nil
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.error "error reloading config_file=#{config.config_file}: " \
699
- "#{e.class} #{e.message} #{e.backtrace}"
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
- # since we don't do keepalive, we'll always flush-on-close and
31
- # this saves packets for everyone.
32
- :tcp_nopush => true,
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.error "error setting socket options: #{e.inspect}"
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 = '')
@@ -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 < Struct.new(:nr, :tmp, :switched)
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
- self.switched = true
86
+ @switched = true
46
87
  end
47
88
  end
@@ -17,8 +17,9 @@ opts = {
17
17
  pid = fork do
18
18
  Isolate.now!(opts) do
19
19
  gem 'sqlite3-ruby', '1.2.5'
20
- gem 'kgio', '2.3.3'
21
- gem 'rack', '1.2.2'
20
+ gem 'raindrops', '0.6.1'
21
+ gem 'kgio', '2.5.0'
22
+ gem 'rack', '1.3.0'
22
23
  end
23
24
  end
24
25
  _, status = Process.waitpid2(pid)
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
+ }
@@ -34,6 +34,7 @@ t_begin "reload signal succeeds" && {
34
34
  done
35
35
 
36
36
  grep 'error reloading' $r_err >/dev/null
37
+ > $r_err
37
38
  }
38
39
 
39
40
  t_begin "hit with curl" && {
@@ -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,8 @@
1
+ run(lambda { |env|
2
+ body = "#{caller.size}\n"
3
+ h = {
4
+ "Content-Length" => body.size.to_s,
5
+ "Content-Type" => "text/plain",
6
+ }
7
+ [ 200, h, [ body ] ]
8
+ })
@@ -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 Unicorn::HttpParserError do
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 Unicorn::HttpParserError do
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 Unicorn::HttpParserError do
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 Unicorn::HttpParserError do
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
@@ -15,7 +15,7 @@ class ResponseTest < Test::Unit::TestCase
15
15
  include Unicorn::HttpResponse
16
16
 
17
17
  def test_httpdate
18
- before = Time.now.to_i
18
+ before = Time.now.to_i - 1
19
19
  str = httpdate
20
20
  assert_kind_of(String, str)
21
21
  middle = Time.parse(str).to_i
@@ -39,7 +39,7 @@ class WebServerTest < Test::Unit::TestCase
39
39
  redirect_test_io do
40
40
  wait_workers_ready("test_stderr.#$$.log", 1)
41
41
  File.truncate("test_stderr.#$$.log", 0)
42
- @server.stop(true)
42
+ @server.stop(false)
43
43
  end
44
44
  reset_sig_handlers
45
45
  end
@@ -124,7 +124,7 @@ class SignalsTest < Test::Unit::TestCase
124
124
  assert diff > 1.0, "diff was #{diff.inspect}"
125
125
  assert diff < 60.0
126
126
  ensure
127
- Process.kill(:QUIT, pid) rescue nil
127
+ Process.kill(:TERM, pid) rescue nil
128
128
  end
129
129
 
130
130
  def test_response_write
@@ -53,7 +53,7 @@ class UploadTest < Test::Unit::TestCase
53
53
  end
54
54
 
55
55
  def teardown
56
- redirect_test_io { @server.stop(true) } if @server
56
+ redirect_test_io { @server.stop(false) } if @server
57
57
  @random.close
58
58
  reset_sig_handlers
59
59
  end
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.3')
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: 27
4
+ hash: 63
5
5
  prerelease:
6
6
  segments:
7
- - 3
8
- - 7
7
+ - 4
9
8
  - 0
10
- version: 3.7.0
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-09 00:00:00 Z
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: 5
42
+ hash: 11
43
43
  segments:
44
44
  - 2
45
- - 3
46
- version: "2.3"
45
+ - 4
46
+ version: "2.4"
47
47
  type: :runtime
48
48
  version_requirements: *id002
49
49
  - !ruby/object:Gem::Dependency
50
- name: isolate
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: *id003
78
+ version_requirements: *id004
64
79
  - !ruby/object:Gem::Dependency
65
80
  name: wrongdoc
66
81
  prerelease: false
67
- requirement: &id004 !ruby/object:Gem::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: *id004
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