unicorn 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.manifest +2 -0
  3. data/.olddoc.yml +1 -1
  4. data/Application_Timeouts +4 -4
  5. data/Documentation/unicorn.1.txt +1 -1
  6. data/Documentation/unicorn_rails.1.txt +1 -1
  7. data/GIT-VERSION-FILE +1 -1
  8. data/GIT-VERSION-GEN +1 -1
  9. data/ISSUES +5 -2
  10. data/LATEST +25 -8
  11. data/LICENSE +2 -2
  12. data/Links +9 -7
  13. data/NEWS +71 -0
  14. data/README +13 -6
  15. data/Sandbox +2 -2
  16. data/bin/unicorn +3 -1
  17. data/examples/logrotate.conf +1 -1
  18. data/examples/nginx.conf +3 -2
  19. data/ext/unicorn_http/common_field_optimization.h +24 -6
  20. data/ext/unicorn_http/extconf.rb +30 -0
  21. data/ext/unicorn_http/global_variables.h +2 -2
  22. data/ext/unicorn_http/httpdate.c +2 -2
  23. data/ext/unicorn_http/unicorn_http.c +4 -9
  24. data/ext/unicorn_http/unicorn_http.rl +4 -9
  25. data/lib/unicorn.rb +19 -8
  26. data/lib/unicorn/configurator.rb +12 -1
  27. data/lib/unicorn/http_request.rb +1 -2
  28. data/lib/unicorn/http_server.rb +19 -20
  29. data/lib/unicorn/launcher.rb +1 -1
  30. data/lib/unicorn/socket_helper.rb +3 -2
  31. data/lib/unicorn/util.rb +3 -3
  32. data/lib/unicorn/version.rb +1 -1
  33. data/lib/unicorn/worker.rb +16 -2
  34. data/man/man1/unicorn.1 +7 -5
  35. data/man/man1/unicorn_rails.1 +6 -3
  36. data/t/README +4 -4
  37. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  38. data/t/t0301.ru +13 -0
  39. data/test/exec/test_exec.rb +6 -7
  40. data/test/unit/test_ccc.rb +1 -1
  41. data/test/unit/test_http_parser.rb +16 -0
  42. data/test/unit/test_server.rb +5 -5
  43. data/test/unit/test_signals.rb +2 -2
  44. data/test/unit/test_socket_helper.rb +4 -4
  45. data/test/unit/test_util.rb +25 -0
  46. data/unicorn.gemspec +1 -1
  47. metadata +5 -4
@@ -8,4 +8,34 @@
8
8
  have_func("rb_hash_clear", "ruby.h") # Ruby 2.0+
9
9
  have_func("gmtime_r", "time.h")
10
10
 
11
+ message('checking if String#-@ (str_uminus) dedupes... ')
12
+ begin
13
+ a = -(%w(t e s t).join)
14
+ b = -(%w(t e s t).join)
15
+ if a.equal?(b)
16
+ $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=1 '
17
+ message("yes\n")
18
+ else
19
+ $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 '
20
+ message("no, needs Ruby 2.5+\n")
21
+ end
22
+ rescue NoMethodError
23
+ $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 '
24
+ message("no, String#-@ not available\n")
25
+ end
26
+
27
+ message('checking if Hash#[]= (rb_hash_aset) dedupes... ')
28
+ h = {}
29
+ x = {}
30
+ r = rand.to_s
31
+ h[%W(#{r}).join('')] = :foo
32
+ x[%W(#{r}).join('')] = :foo
33
+ if x.keys[0].equal?(h.keys[0])
34
+ $CPPFLAGS += ' -DHASH_ASET_DEDUPE=1 '
35
+ message("yes\n")
36
+ else
37
+ $CPPFLAGS += ' -DHASH_ASET_DEDUPE=0 '
38
+ message("no, needs Ruby 2.6+\n")
39
+ end
40
+
11
41
  create_makefile("unicorn_http")
@@ -56,7 +56,7 @@ NORETURN(static void parser_raise(VALUE klass, const char *));
56
56
  /** Defines global strings in the init method. */
57
57
  #define DEF_GLOBAL(N, val) do { \
58
58
  g_##N = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \
59
- rb_ary_push(mark_ary, g_##N); \
59
+ rb_gc_register_mark_object(g_##N); \
60
60
  } while (0)
61
61
 
62
62
  /* Defines the maximum allowed lengths for various input elements.*/
@@ -67,7 +67,7 @@ DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewh
67
67
  DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */
68
68
  DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
69
69
 
70
- static void init_globals(VALUE mark_ary)
70
+ static void init_globals(void)
71
71
  {
72
72
  DEF_GLOBAL(rack_url_scheme, "rack.url_scheme");
73
73
  DEF_GLOBAL(request_method, "REQUEST_METHOD");
@@ -64,13 +64,13 @@ static VALUE httpdate(VALUE self)
64
64
  return buf;
65
65
  }
66
66
 
67
- void init_unicorn_httpdate(VALUE mark_ary)
67
+ void init_unicorn_httpdate(void)
68
68
  {
69
69
  VALUE mod = rb_define_module("Unicorn");
70
70
  mod = rb_define_module_under(mod, "HttpResponse");
71
71
 
72
72
  buf = rb_str_new(0, buf_capa - 1);
73
- rb_ary_push(mark_ary, buf);
73
+ rb_gc_register_mark_object(buf);
74
74
  buf_ptr = RSTRING_PTR(buf);
75
75
  httpdate(Qnil);
76
76
 
@@ -15,7 +15,7 @@
15
15
  #include "global_variables.h"
16
16
  #include "c_util.h"
17
17
 
18
- void init_unicorn_httpdate(VALUE mark_ary);
18
+ void init_unicorn_httpdate(void);
19
19
 
20
20
  #define UH_FL_CHUNKED 0x1
21
21
  #define UH_FL_HASBODY 0x2
@@ -4225,11 +4225,8 @@ static VALUE HttpParser_rssget(VALUE self)
4225
4225
 
4226
4226
  void Init_unicorn_http(void)
4227
4227
  {
4228
- static VALUE mark_ary;
4229
4228
  VALUE mUnicorn, cHttpParser;
4230
4229
 
4231
- mark_ary = rb_ary_new();
4232
- rb_global_variable(&mark_ary);
4233
4230
  mUnicorn = rb_define_module("Unicorn");
4234
4231
  cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
4235
4232
  eHttpParserError =
@@ -4239,7 +4236,7 @@ void Init_unicorn_http(void)
4239
4236
  e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
4240
4237
  eHttpParserError);
4241
4238
 
4242
- init_globals(mark_ary);
4239
+ init_globals();
4243
4240
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
4244
4241
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
4245
4242
  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
@@ -4276,16 +4273,14 @@ void Init_unicorn_http(void)
4276
4273
 
4277
4274
  rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
4278
4275
 
4279
- init_common_fields(mark_ary);
4276
+ init_common_fields();
4280
4277
  SET_GLOBAL(g_http_host, "HOST");
4281
4278
  SET_GLOBAL(g_http_trailer, "TRAILER");
4282
4279
  SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
4283
4280
  SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
4284
4281
  SET_GLOBAL(g_http_connection, "CONNECTION");
4285
4282
  id_set_backtrace = rb_intern("set_backtrace");
4286
- init_unicorn_httpdate(mark_ary);
4287
-
4288
- OBJ_FREEZE(mark_ary);
4283
+ init_unicorn_httpdate();
4289
4284
 
4290
4285
  #ifndef HAVE_RB_HASH_CLEAR
4291
4286
  id_clear = rb_intern("clear");
@@ -13,7 +13,7 @@
13
13
  #include "global_variables.h"
14
14
  #include "c_util.h"
15
15
 
16
- void init_unicorn_httpdate(VALUE mark_ary);
16
+ void init_unicorn_httpdate(void);
17
17
 
18
18
  #define UH_FL_CHUNKED 0x1
19
19
  #define UH_FL_HASBODY 0x2
@@ -931,11 +931,8 @@ static VALUE HttpParser_rssget(VALUE self)
931
931
 
932
932
  void Init_unicorn_http(void)
933
933
  {
934
- static VALUE mark_ary;
935
934
  VALUE mUnicorn, cHttpParser;
936
935
 
937
- mark_ary = rb_ary_new();
938
- rb_global_variable(&mark_ary);
939
936
  mUnicorn = rb_define_module("Unicorn");
940
937
  cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
941
938
  eHttpParserError =
@@ -945,7 +942,7 @@ void Init_unicorn_http(void)
945
942
  e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
946
943
  eHttpParserError);
947
944
 
948
- init_globals(mark_ary);
945
+ init_globals();
949
946
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
950
947
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
951
948
  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
@@ -982,16 +979,14 @@ void Init_unicorn_http(void)
982
979
 
983
980
  rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
984
981
 
985
- init_common_fields(mark_ary);
982
+ init_common_fields();
986
983
  SET_GLOBAL(g_http_host, "HOST");
987
984
  SET_GLOBAL(g_http_trailer, "TRAILER");
988
985
  SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
989
986
  SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
990
987
  SET_GLOBAL(g_http_connection, "CONNECTION");
991
988
  id_set_backtrace = rb_intern("set_backtrace");
992
- init_unicorn_httpdate(mark_ary);
993
-
994
- OBJ_FREEZE(mark_ary);
989
+ init_unicorn_httpdate();
995
990
 
996
991
  #ifndef HAVE_RB_HASH_CLEAR
997
992
  id_clear = rb_intern("clear");
data/lib/unicorn.rb CHANGED
@@ -2,6 +2,8 @@
2
2
  require 'etc'
3
3
  require 'stringio'
4
4
  require 'kgio'
5
+ require 'raindrops'
6
+ require 'io/wait'
5
7
 
6
8
  begin
7
9
  require 'rack'
@@ -43,12 +45,8 @@ def self.builder(ru, op)
43
45
  abort "rack and Rack::Builder must be available for processing #{ru}"
44
46
  end
45
47
 
46
- # Op is going to get cleared before the returned lambda is called, so
47
- # save this value so that it's still there when we need it:
48
- no_default_middleware = op[:no_default_middleware]
49
-
50
48
  # always called after config file parsing, may be called after forking
51
- lambda do ||
49
+ lambda do |_, server|
52
50
  inner_app = case ru
53
51
  when /\.ru$/
54
52
  raw = File.read(ru)
@@ -64,7 +62,7 @@ def self.builder(ru, op)
64
62
  pp({ :inner_app => inner_app })
65
63
  end
66
64
 
67
- return inner_app if no_default_middleware
65
+ return inner_app unless server.default_middleware
68
66
 
69
67
  middleware = { # order matters
70
68
  ContentLength: nil,
@@ -112,9 +110,22 @@ def self.log_error(logger, prefix, exc)
112
110
  exc.backtrace.each { |line| logger.error(line) }
113
111
  end
114
112
 
115
- # remove this when we only support Ruby >= 2.0
113
+ F_SETPIPE_SZ = 1031 if RUBY_PLATFORM =~ /linux/
114
+
116
115
  def self.pipe # :nodoc:
117
- Kgio::Pipe.new.each { |io| io.close_on_exec = true }
116
+ Kgio::Pipe.new.each do |io|
117
+ io.close_on_exec = true # remove this when we only support Ruby >= 2.0
118
+
119
+ # shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft
120
+ # limits.
121
+ if defined?(F_SETPIPE_SZ)
122
+ begin
123
+ io.fcntl(F_SETPIPE_SZ, Raindrops::PAGE_SIZE)
124
+ rescue Errno::EINVAL
125
+ # old kernel
126
+ end
127
+ end
128
+ end
118
129
  end
119
130
  # :startdoc:
120
131
  end
@@ -88,6 +88,9 @@ def reload(merge_defaults = true) #:nodoc:
88
88
  RACKUP[:set_listener] and
89
89
  set[:listeners] << "#{RACKUP[:host]}:#{RACKUP[:port]}"
90
90
 
91
+ RACKUP[:no_default_middleware] and
92
+ set[:default_middleware] = false
93
+
91
94
  # unicorn_rails creates dirs here after working_directory is bound
92
95
  after_reload.call if after_reload
93
96
 
@@ -235,7 +238,7 @@ def before_exec(*args, &block)
235
238
  # server 192.168.0.9:8080 fail_timeout=0;
236
239
  # }
237
240
  #
238
- # See http://nginx.org/en/docs/http/ngx_http_upstream_module.html
241
+ # See https://nginx.org/en/docs/http/ngx_http_upstream_module.html
239
242
  # for more details on nginx upstream configuration.
240
243
  def timeout(seconds)
241
244
  set_int(:timeout, seconds, 3)
@@ -265,6 +268,14 @@ def worker_processes(nr)
265
268
  set_int(:worker_processes, nr, 1)
266
269
  end
267
270
 
271
+ # sets whether to add default middleware in the development and
272
+ # deployment RACK_ENVs.
273
+ #
274
+ # default_middleware is only available in unicorn 5.5.0+
275
+ def default_middleware(bool)
276
+ set_bool(:default_middleware, bool)
277
+ end
278
+
268
279
  # sets listeners to the given +addresses+, replacing or augmenting the
269
280
  # current set. This is for the global listener pool shared by all
270
281
  # worker processes. For per-worker listeners, see the after_fork example
@@ -2,7 +2,6 @@
2
2
  # :enddoc:
3
3
  # no stable API here
4
4
  require 'unicorn_http'
5
- require 'raindrops'
6
5
 
7
6
  # TODO: remove redundant names
8
7
  Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
@@ -66,7 +65,7 @@ def read(socket)
66
65
  clear
67
66
  e = env
68
67
 
69
- # From http://www.ietf.org/rfc/rfc3875:
68
+ # From https://www.ietf.org/rfc/rfc3875:
70
69
  # "Script authors should be aware that the REMOTE_ADDR and
71
70
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
72
71
  # may not identify the ultimate source of the request. They
@@ -14,7 +14,8 @@ class Unicorn::HttpServer
14
14
  attr_accessor :app, :timeout, :worker_processes,
15
15
  :before_fork, :after_fork, :before_exec,
16
16
  :listener_opts, :preload_app,
17
- :orig_app, :config, :ready_pipe, :user
17
+ :orig_app, :config, :ready_pipe, :user,
18
+ :default_middleware
18
19
  attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
19
20
 
20
21
  attr_reader :pid, :logger
@@ -70,6 +71,7 @@ def initialize(app, options = {})
70
71
  @app = app
71
72
  @request = Unicorn::HttpRequest.new
72
73
  @reexec_pid = 0
74
+ @default_middleware = true
73
75
  options = options.dup
74
76
  @ready_pipe = options.delete(:ready_pipe)
75
77
  @init_listeners = options[:listeners] ? options[:listeners].dup : []
@@ -82,7 +84,7 @@ def initialize(app, options = {})
82
84
  # * The master process never closes or reinitializes this once
83
85
  # initialized. Signal handlers in the master process will write to
84
86
  # it to wake up the master from IO.select in exactly the same manner
85
- # djb describes in http://cr.yp.to/docs/selfpipe.html
87
+ # djb describes in https://cr.yp.to/docs/selfpipe.html
86
88
  #
87
89
  # * The workers immediately close the pipe they inherit. See the
88
90
  # Unicorn::Worker class for the pipe workers use.
@@ -380,7 +382,7 @@ def check_client_connection=(bool)
380
382
 
381
383
  # wait for a signal hander to wake us up and then consume the pipe
382
384
  def master_sleep(sec)
383
- @self_pipe[0].kgio_wait_readable(sec) or return
385
+ @self_pipe[0].wait(sec) or return
384
386
  # 11 bytes is the maximum string length which can be embedded within
385
387
  # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
386
388
  # Most reads are only one byte here and uncommon, so it's not worth a
@@ -520,9 +522,6 @@ def after_fork_internal
520
522
  Unicorn::Configurator::RACKUP.clear
521
523
  @ready_pipe = @init_listeners = @before_exec = @before_fork = nil
522
524
 
523
- # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450
524
- srand # remove in unicorn 6
525
-
526
525
  # The OpenSSL PRNG is seeded with only the pid, and apps with frequently
527
526
  # dying workers can recycle pids
528
527
  OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
@@ -553,9 +552,9 @@ def spawn_missing_workers
553
552
  @workers[pid] = worker
554
553
  worker.atfork_parent
555
554
  end
556
- rescue => e
557
- @logger.error(e) rescue nil
558
- exit!
555
+ rescue => e
556
+ @logger.error(e) rescue nil
557
+ exit!
559
558
  end
560
559
 
561
560
  def maintain_worker_count
@@ -586,7 +585,7 @@ def handle_error(client, e)
586
585
  client.kgio_trywrite(err_response(code, @request.response_start_sent))
587
586
  end
588
587
  client.close
589
- rescue
588
+ rescue
590
589
  end
591
590
 
592
591
  def e100_response_write(client, env)
@@ -669,9 +668,9 @@ def reopen_worker_logs(worker_nr)
669
668
  logger.info "worker=#{worker_nr} reopening logs..."
670
669
  Unicorn::Util.reopen_logs
671
670
  logger.info "worker=#{worker_nr} done reopening logs"
672
- rescue => e
673
- logger.error(e) rescue nil
674
- exit!(77) # EX_NOPERM in sysexits.h
671
+ rescue => e
672
+ logger.error(e) rescue nil
673
+ exit!(77) # EX_NOPERM in sysexits.h
675
674
  end
676
675
 
677
676
  # runs inside each forked worker, this sits around and waits
@@ -757,11 +756,11 @@ def valid_pid?(path)
757
756
  wpid <= 0 and return
758
757
  Process.kill(0, wpid)
759
758
  wpid
760
- rescue Errno::EPERM
761
- logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
762
- nil
763
- rescue Errno::ESRCH, Errno::ENOENT
764
- # don't unlink stale pid files, racy without non-portable locking...
759
+ rescue Errno::EPERM
760
+ logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
761
+ nil
762
+ rescue Errno::ESRCH, Errno::ENOENT
763
+ # don't unlink stale pid files, racy without non-portable locking...
765
764
  end
766
765
 
767
766
  def load_config!
@@ -787,12 +786,12 @@ def listener_names(listeners = LISTENERS)
787
786
  end
788
787
 
789
788
  def build_app!
790
- if app.respond_to?(:arity) && app.arity == 0
789
+ if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
791
790
  if defined?(Gem) && Gem.respond_to?(:refresh)
792
791
  logger.info "Refreshing Gem list"
793
792
  Gem.refresh
794
793
  end
795
- self.app = app.call
794
+ self.app = app.arity == 0 ? app.call : app.call(nil, self)
796
795
  end
797
796
  end
798
797
 
@@ -31,7 +31,7 @@ def self.daemonize!(options)
31
31
  # \_ parent - exits immediately ASAP
32
32
  # \_ unicorn master - writes to pipe when ready
33
33
 
34
- rd, wr = IO.pipe
34
+ rd, wr = Unicorn.pipe
35
35
  grandparent = $$
36
36
  if fork
37
37
  wr.close # grandparent does not write
@@ -83,6 +83,7 @@ def set_tcp_sockopt(sock, opt)
83
83
  rescue => e
84
84
  logger.error("#{sock_name(sock)} " \
85
85
  "failed to set accept_filter=#{name} (#{e.inspect})")
86
+ logger.error("perhaps accf_http(9) needs to be loaded".freeze)
86
87
  end if arg != got
87
88
  end
88
89
  end
@@ -100,8 +101,8 @@ def set_server_sockopt(sock, opt)
100
101
  log_buffer_sizes(sock, " after: ")
101
102
  end
102
103
  sock.listen(opt[:backlog])
103
- rescue => e
104
- Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
104
+ rescue => e
105
+ Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
105
106
  end
106
107
 
107
108
  def log_buffer_sizes(sock, pfx = '')
data/lib/unicorn/util.rb CHANGED
@@ -11,8 +11,8 @@ def self.is_log?(fp)
11
11
  fp.stat.file? &&
12
12
  fp.sync &&
13
13
  (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
14
- rescue IOError, Errno::EBADF
15
- false
14
+ rescue IOError, Errno::EBADF
15
+ false
16
16
  end
17
17
 
18
18
  def self.chown_logs(uid, gid)
@@ -64,7 +64,7 @@ def self.reopen_logs
64
64
  fp.reopen(fp.path, "a")
65
65
  else
66
66
  # We should not need this workaround, Ruby can be fixed:
67
- # http://bugs.ruby-lang.org/issues/9036
67
+ # https://bugs.ruby-lang.org/issues/9036
68
68
  # MRI will not call call fclose(3) or freopen(3) here
69
69
  # since there's no associated std{in,out,err} FILE * pointer
70
70
  # This should atomically use dup3(2) (or dup2(2)) syscall
@@ -1 +1 @@
1
- Unicorn::Const::UNICORN_VERSION = '5.4.0'
1
+ Unicorn::Const::UNICORN_VERSION = '5.5.0'
@@ -122,6 +122,11 @@ def close # :nodoc:
122
122
  # the +after_fork+ hook after any privileged functions need to be
123
123
  # run (e.g. to set per-worker CPU affinity, niceness, etc)
124
124
  #
125
+ # +group+ can be specified as a string, or as an array of two
126
+ # strings. If an array of two strings is given, the first string
127
+ # is used as the primary group of the process, and the second is
128
+ # used as the group of the log files.
129
+ #
125
130
  # Any and all errors raised within this method will be propagated
126
131
  # directly back to the caller (usually the +after_fork+ hook.
127
132
  # These errors commonly include ArgumentError for specifying an
@@ -134,8 +139,17 @@ def user(user, group = nil, chroot = false)
134
139
  # insufficient because modern systems have fine-grained
135
140
  # capabilities. Let the caller handle any and all errors.
136
141
  uid = Etc.getpwnam(user).uid
137
- gid = Etc.getgrnam(group).gid if group
138
- Unicorn::Util.chown_logs(uid, gid)
142
+
143
+ if group
144
+ if group.is_a?(Array)
145
+ group, log_group = group
146
+ log_gid = Etc.getgrnam(log_group).gid
147
+ end
148
+ gid = Etc.getgrnam(group).gid
149
+ log_gid ||= gid
150
+ end
151
+
152
+ Unicorn::Util.chown_logs(uid, log_gid)
139
153
  if gid && Process.egid != gid
140
154
  Process.initgroups(user, gid)
141
155
  Process::GID.change_privilege(gid)