unicorn 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,4 +1,6 @@
1
+ v0.9.1 - FD_CLOEXEC portability fix (v0.8.2 port)
1
2
  v0.9.0 - bodies: "Transfer-Encoding: chunked", rewindable streaming
3
+ v0.8.2 - socket handling bugfixes and usability tweaks
2
4
  v0.8.1 - safer timeout handling, more consistent reload behavior
3
5
  v0.8.0 - enforce Rack dependency, minor performance improvements and fixes
4
6
  v0.7.1 - minor fixes, cleanups and documentation improvements
@@ -8,25 +8,20 @@
8
8
  #
9
9
  # Then type random stuff in your terminal to watch it get echoed back!
10
10
 
11
- class EchoBody
12
- def initialize(input)
13
- @input = input
14
- end
11
+ class EchoBody < Struct.new(:input)
15
12
 
16
13
  def each(&block)
17
- while buf = @input.read(4096)
14
+ while buf = input.read(4096)
18
15
  yield buf
19
16
  end
20
17
  self
21
18
  end
22
19
 
23
- def close
24
- @input = nil
25
- end
26
20
  end
27
21
 
28
22
  use Rack::Chunked
29
23
  run lambda { |env|
24
+ /\A100-continue\z/ =~ env['HTTP_EXPECT'] and return [100, {}, []]
30
25
  [ 200, { 'Content-Type' => 'application/octet-stream' },
31
26
  EchoBody.new(env['rack.input']) ]
32
27
  }
@@ -30,8 +30,12 @@ module Unicorn
30
30
  # processes which in turn handle the I/O and application process.
31
31
  # Listener sockets are started in the master process and shared with
32
32
  # forked worker children.
33
- class HttpServer
34
- attr_reader :logger
33
+
34
+ class HttpServer < Struct.new(:listener_opts, :timeout, :worker_processes,
35
+ :before_fork, :after_fork, :before_exec,
36
+ :logger, :pid, :app, :preload_app,
37
+ :reexec_pid, :orig_app, :init_listeners,
38
+ :master_pid, :config)
35
39
  include ::Unicorn::SocketHelper
36
40
 
37
41
  # prevents IO objects in here from being GC-ed
@@ -59,11 +63,10 @@ module Unicorn
59
63
  # don't rely on Dir.pwd here since it's not symlink-aware, and
60
64
  # symlink dirs are the default with Capistrano...
61
65
  :cwd => `/bin/sh -c pwd`.chomp("\n"),
62
- :zero => $0.dup,
66
+ 0 => $0.dup,
63
67
  }
64
68
 
65
- Worker = Struct.new(:nr, :tempfile) unless defined?(Worker)
66
- class Worker
69
+ class Worker < Struct.new(:nr, :tempfile)
67
70
  # worker objects may be compared to just plain numbers
68
71
  def ==(other_nr)
69
72
  self.nr == other_nr
@@ -75,14 +78,13 @@ module Unicorn
75
78
  # HttpServer.run.join to join the thread that's processing
76
79
  # incoming requests on the socket.
77
80
  def initialize(app, options = {})
78
- @app = app
79
- @pid = nil
80
- @reexec_pid = 0
81
- @init_listeners = options[:listeners] ? options[:listeners].dup : []
82
- @config = Configurator.new(options.merge(:use_defaults => true))
83
- @listener_opts = {}
84
- @config.commit!(self, :skip => [:listeners, :pid])
85
- @orig_app = app
81
+ self.app = app
82
+ self.reexec_pid = 0
83
+ self.init_listeners = options[:listeners] ? options[:listeners].dup : []
84
+ self.config = Configurator.new(options.merge(:use_defaults => true))
85
+ self.listener_opts = {}
86
+ config.commit!(self, :skip => [:listeners, :pid])
87
+ self.orig_app = app
86
88
  end
87
89
 
88
90
  # Runs the thing. Returns self so you can run join on it
@@ -93,13 +95,13 @@ module Unicorn
93
95
  # before they become UNIXServer or TCPServer
94
96
  inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
95
97
  io = Socket.for_fd(fd.to_i)
96
- set_server_sockopt(io, @listener_opts[sock_name(io)])
98
+ set_server_sockopt(io, listener_opts[sock_name(io)])
97
99
  IO_PURGATORY << io
98
100
  logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
99
101
  server_cast(io)
100
102
  end
101
103
 
102
- config_listeners = @config[:listeners].dup
104
+ config_listeners = config[:listeners].dup
103
105
  LISTENERS.replace(inherited)
104
106
 
105
107
  # we start out with generic Socket objects that get cast to either
@@ -112,8 +114,9 @@ module Unicorn
112
114
  end
113
115
  config_listeners.each { |addr| listen(addr) }
114
116
  raise ArgumentError, "no listeners" if LISTENERS.empty?
115
- self.pid = @config[:pid]
116
- build_app! if @preload_app
117
+ self.pid = config[:pid]
118
+ self.master_pid = $$
119
+ build_app! if preload_app
117
120
  maintain_worker_count
118
121
  self
119
122
  end
@@ -131,8 +134,7 @@ module Unicorn
131
134
  end
132
135
  end
133
136
  set_names = listener_names(listeners)
134
- dead_names += cur_names - set_names
135
- dead_names.uniq!
137
+ dead_names.concat(cur_names - set_names).uniq!
136
138
 
137
139
  LISTENERS.delete_if do |io|
138
140
  if dead_names.include?(sock_name(io))
@@ -141,7 +143,7 @@ module Unicorn
141
143
  end
142
144
  (io.close rescue nil).nil? # true
143
145
  else
144
- set_server_sockopt(io, @listener_opts[sock_name(io)])
146
+ set_server_sockopt(io, listener_opts[sock_name(io)])
145
147
  false
146
148
  end
147
149
  end
@@ -152,28 +154,27 @@ module Unicorn
152
154
  def stdout_path=(path); redirect_io($stdout, path); end
153
155
  def stderr_path=(path); redirect_io($stderr, path); end
154
156
 
155
- def logger=(obj)
156
- REQUEST.logger = @logger = obj
157
- end
157
+ alias_method :set_pid, :pid=
158
+ undef_method :pid=
158
159
 
159
160
  # sets the path for the PID file of the master process
160
161
  def pid=(path)
161
162
  if path
162
163
  if x = valid_pid?(path)
163
- return path if @pid && path == @pid && x == $$
164
+ return path if pid && path == pid && x == $$
164
165
  raise ArgumentError, "Already running on PID:#{x} " \
165
166
  "(or pid=#{path} is stale)"
166
167
  end
167
168
  end
168
- unlink_pid_safe(@pid) if @pid
169
+ unlink_pid_safe(pid) if pid
169
170
  File.open(path, 'wb') { |fp| fp.syswrite("#$$\n") } if path
170
- @pid = path
171
+ self.set_pid(path)
171
172
  end
172
173
 
173
174
  # add a given address to the +listeners+ set, idempotently
174
175
  # Allows workers to add a private, per-process listener via the
175
- # @after_fork hook. Very useful for debugging and testing.
176
- def listen(address, opt = {}.merge(@listener_opts[address] || {}))
176
+ # after_fork hook. Very useful for debugging and testing.
177
+ def listen(address, opt = {}.merge(listener_opts[address] || {}))
177
178
  return if String === address && listener_names.include?(address)
178
179
 
179
180
  delay, tries = 0.5, 5
@@ -239,12 +240,12 @@ module Unicorn
239
240
  logger.info "SIGWINCH ignored because we're not daemonized"
240
241
  end
241
242
  when :TTIN
242
- @worker_processes += 1
243
+ self.worker_processes += 1
243
244
  when :TTOU
244
- @worker_processes -= 1 if @worker_processes > 0
245
+ self.worker_processes -= 1 if self.worker_processes > 0
245
246
  when :HUP
246
247
  respawn = true
247
- if @config.config_file
248
+ if config.config_file
248
249
  load_config!
249
250
  redo # immediate reaping since we may have QUIT workers
250
251
  else # exec binary and exit if there's no config file
@@ -263,14 +264,14 @@ module Unicorn
263
264
  end
264
265
  stop # gracefully shutdown all workers on our way out
265
266
  logger.info "master complete"
266
- unlink_pid_safe(@pid) if @pid
267
+ unlink_pid_safe(pid) if pid
267
268
  end
268
269
 
269
270
  # Terminates all workers, but does not exit master process
270
271
  def stop(graceful = true)
271
272
  self.listeners = []
272
273
  kill_each_worker(graceful ? :QUIT : :TERM)
273
- timeleft = @timeout
274
+ timeleft = timeout
274
275
  step = 0.2
275
276
  reap_all_workers
276
277
  until WORKERS.empty?
@@ -323,15 +324,15 @@ module Unicorn
323
324
  def reap_all_workers
324
325
  begin
325
326
  loop do
326
- pid, status = Process.waitpid2(-1, Process::WNOHANG)
327
- pid or break
328
- if @reexec_pid == pid
327
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
328
+ wpid or break
329
+ if reexec_pid == wpid
329
330
  logger.error "reaped #{status.inspect} exec()-ed"
330
- @reexec_pid = 0
331
- self.pid = @pid.chomp('.oldbin') if @pid
331
+ self.reexec_pid = 0
332
+ self.pid = pid.chomp('.oldbin') if pid
332
333
  proc_name 'master'
333
334
  else
334
- worker = WORKERS.delete(pid) and worker.tempfile.close rescue nil
335
+ worker = WORKERS.delete(wpid) and worker.tempfile.close rescue nil
335
336
  logger.info "reaped #{status.inspect} " \
336
337
  "worker=#{worker.nr rescue 'unknown'}"
337
338
  end
@@ -342,19 +343,19 @@ module Unicorn
342
343
 
343
344
  # reexecutes the START_CTX with a new binary
344
345
  def reexec
345
- if @reexec_pid > 0
346
+ if reexec_pid > 0
346
347
  begin
347
- Process.kill(0, @reexec_pid)
348
- logger.error "reexec-ed child already running PID:#{@reexec_pid}"
348
+ Process.kill(0, reexec_pid)
349
+ logger.error "reexec-ed child already running PID:#{reexec_pid}"
349
350
  return
350
351
  rescue Errno::ESRCH
351
- @reexec_pid = 0
352
+ reexec_pid = 0
352
353
  end
353
354
  end
354
355
 
355
- if @pid
356
- old_pid = "#{@pid}.oldbin"
357
- prev_pid = @pid.dup
356
+ if pid
357
+ old_pid = "#{pid}.oldbin"
358
+ prev_pid = pid.dup
358
359
  begin
359
360
  self.pid = old_pid # clear the path for a new pid file
360
361
  rescue ArgumentError
@@ -367,11 +368,11 @@ module Unicorn
367
368
  end
368
369
  end
369
370
 
370
- @reexec_pid = fork do
371
+ self.reexec_pid = fork do
371
372
  listener_fds = LISTENERS.map { |sock| sock.fileno }
372
373
  ENV['UNICORN_FD'] = listener_fds.join(',')
373
374
  Dir.chdir(START_CTX[:cwd])
374
- cmd = [ START_CTX[:zero] ] + START_CTX[:argv]
375
+ cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
375
376
 
376
377
  # avoid leaking FDs we don't know about, but let before_exec
377
378
  # unset FD_CLOEXEC, if anything else in the app eventually
@@ -384,38 +385,38 @@ module Unicorn
384
385
  io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
385
386
  end
386
387
  logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
387
- @before_exec.call(self)
388
+ before_exec.call(self)
388
389
  exec(*cmd)
389
390
  end
390
391
  proc_name 'master (old)'
391
392
  end
392
393
 
393
- # forcibly terminate all workers that haven't checked in in @timeout
394
+ # forcibly terminate all workers that haven't checked in in timeout
394
395
  # seconds. The timeout is implemented using an unlinked tempfile
395
396
  # shared between the parent process and each worker. The worker
396
397
  # runs File#chmod to modify the ctime of the tempfile. If the ctime
397
- # is stale for >@timeout seconds, then we'll kill the corresponding
398
+ # is stale for >timeout seconds, then we'll kill the corresponding
398
399
  # worker.
399
400
  def murder_lazy_workers
400
401
  diff = stat = nil
401
- WORKERS.dup.each_pair do |pid, worker|
402
+ WORKERS.dup.each_pair do |wpid, worker|
402
403
  stat = begin
403
404
  worker.tempfile.stat
404
405
  rescue => e
405
- logger.warn "worker=#{worker.nr} PID:#{pid} stat error: #{e.inspect}"
406
- kill_worker(:QUIT, pid)
406
+ logger.warn "worker=#{worker.nr} PID:#{wpid} stat error: #{e.inspect}"
407
+ kill_worker(:QUIT, wpid)
407
408
  next
408
409
  end
409
410
  stat.mode == 0100000 and next
410
- (diff = (Time.now - stat.ctime)) <= @timeout and next
411
- logger.error "worker=#{worker.nr} PID:#{pid} timeout " \
412
- "(#{diff}s > #{@timeout}s), killing"
413
- kill_worker(:KILL, pid) # take no prisoners for @timeout violations
411
+ (diff = (Time.now - stat.ctime)) <= timeout and next
412
+ logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \
413
+ "(#{diff}s > #{timeout}s), killing"
414
+ kill_worker(:KILL, wpid) # take no prisoners for timeout violations
414
415
  end
415
416
  end
416
417
 
417
418
  def spawn_missing_workers
418
- (0...@worker_processes).each do |worker_nr|
419
+ (0...worker_processes).each do |worker_nr|
419
420
  WORKERS.values.include?(worker_nr) and next
420
421
  begin
421
422
  Dir.chdir(START_CTX[:cwd])
@@ -427,23 +428,23 @@ module Unicorn
427
428
  tempfile = Tempfile.new(nil) # as short as possible to save dir space
428
429
  tempfile.unlink # don't allow other processes to find or see it
429
430
  worker = Worker.new(worker_nr, tempfile)
430
- @before_fork.call(self, worker)
431
- pid = fork { worker_loop(worker) }
432
- WORKERS[pid] = worker
431
+ before_fork.call(self, worker)
432
+ WORKERS[fork { worker_loop(worker) }] = worker
433
433
  end
434
434
  end
435
435
 
436
436
  def maintain_worker_count
437
- (off = WORKERS.size - @worker_processes) == 0 and return
437
+ (off = WORKERS.size - worker_processes) == 0 and return
438
438
  off < 0 and return spawn_missing_workers
439
- WORKERS.dup.each_pair { |pid,w|
440
- w.nr >= @worker_processes and kill_worker(:QUIT, pid) rescue nil
439
+ WORKERS.dup.each_pair { |wpid,w|
440
+ w.nr >= worker_processes and kill_worker(:QUIT, wpid) rescue nil
441
441
  }
442
442
  end
443
443
 
444
444
  # once a client is accepted, it is processed in its entirety here
445
445
  # in 3 easy steps: read request, call app, write app response
446
- def process_client(app, client)
446
+ def process_client(client)
447
+ client.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
447
448
  response = app.call(env = REQUEST.read(client))
448
449
 
449
450
  if 100 == response.first.to_i
@@ -472,10 +473,10 @@ module Unicorn
472
473
 
473
474
  # gets rid of stuff the worker has no business keeping track of
474
475
  # to free some resources and drops all sig handlers.
475
- # traps for USR1, USR2, and HUP may be set in the @after_fork Proc
476
+ # traps for USR1, USR2, and HUP may be set in the after_fork Proc
476
477
  # by the user.
477
478
  def init_worker_process(worker)
478
- QUEUE_SIGS.each { |sig| trap(sig, 'IGNORE') }
479
+ QUEUE_SIGS.each { |sig| trap(sig, nil) }
479
480
  trap(:CHLD, 'DEFAULT')
480
481
  SIG_QUEUE.clear
481
482
  proc_name "worker[#{worker.nr}]"
@@ -485,15 +486,15 @@ module Unicorn
485
486
  WORKERS.clear
486
487
  LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
487
488
  worker.tempfile.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
488
- @after_fork.call(self, worker) # can drop perms
489
- @timeout /= 2.0 # halve it for select()
490
- build_app! unless @preload_app
489
+ after_fork.call(self, worker) # can drop perms
490
+ self.timeout /= 2.0 # halve it for select()
491
+ build_app! unless preload_app
491
492
  end
492
493
 
493
494
  def reopen_worker_logs(worker_nr)
494
- @logger.info "worker=#{worker_nr} reopening logs..."
495
+ logger.info "worker=#{worker_nr} reopening logs..."
495
496
  Unicorn::Util.reopen_logs
496
- @logger.info "worker=#{worker_nr} done reopening logs"
497
+ logger.info "worker=#{worker_nr} done reopening logs"
497
498
  init_self_pipe!
498
499
  end
499
500
 
@@ -501,7 +502,7 @@ module Unicorn
501
502
  # for connections and doesn't die until the parent dies (or is
502
503
  # given a INT, QUIT, or TERM signal)
503
504
  def worker_loop(worker)
504
- master_pid = Process.ppid # slightly racy, but less memory usage
505
+ ppid = master_pid
505
506
  init_worker_process(worker)
506
507
  nr = 0 # this becomes negative if we need to reopen logs
507
508
  alive = worker.tempfile # tempfile is our lifeline to the master process
@@ -512,14 +513,13 @@ module Unicorn
512
513
  trap(:USR1) { nr = -65536; SELF_PIPE.first.close rescue nil }
513
514
  trap(:QUIT) { alive = nil; LISTENERS.each { |s| s.close rescue nil } }
514
515
  [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
515
- @logger.info "worker=#{worker.nr} ready"
516
- app = @app
516
+ logger.info "worker=#{worker.nr} ready"
517
517
 
518
518
  begin
519
519
  nr < 0 and reopen_worker_logs(worker.nr)
520
520
  nr = 0
521
521
 
522
- # we're a goner in @timeout seconds anyways if alive.chmod
522
+ # we're a goner in timeout seconds anyways if alive.chmod
523
523
  # breaks, so don't trap the exception. Using fchmod() since
524
524
  # futimes() is not available in base Ruby and I very strongly
525
525
  # prefer temporary files to be unlinked for security,
@@ -531,7 +531,7 @@ module Unicorn
531
531
 
532
532
  ready.each do |sock|
533
533
  begin
534
- process_client(app, sock.accept_nonblock)
534
+ process_client(sock.accept_nonblock)
535
535
  nr += 1
536
536
  t == (ti = Time.now.to_i) or alive.chmod(t = ti)
537
537
  rescue Errno::EAGAIN, Errno::ECONNABORTED
@@ -545,11 +545,11 @@ module Unicorn
545
545
  # before we sleep again in select().
546
546
  redo unless nr == 0 # (nr < 0) => reopen logs
547
547
 
548
- master_pid == Process.ppid or return
548
+ ppid == Process.ppid or return
549
549
  alive.chmod(t = 0)
550
550
  begin
551
551
  # timeout used so we can detect parent death:
552
- ret = IO.select(LISTENERS, nil, SELF_PIPE, @timeout) or redo
552
+ ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) or redo
553
553
  ready = ret.first
554
554
  rescue Errno::EINTR
555
555
  ready = LISTENERS
@@ -566,17 +566,17 @@ module Unicorn
566
566
 
567
567
  # delivers a signal to a worker and fails gracefully if the worker
568
568
  # is no longer running.
569
- def kill_worker(signal, pid)
569
+ def kill_worker(signal, wpid)
570
570
  begin
571
- Process.kill(signal, pid)
571
+ Process.kill(signal, wpid)
572
572
  rescue Errno::ESRCH
573
- worker = WORKERS.delete(pid) and worker.tempfile.close rescue nil
573
+ worker = WORKERS.delete(wpid) and worker.tempfile.close rescue nil
574
574
  end
575
575
  end
576
576
 
577
577
  # delivers a signal to each worker
578
578
  def kill_each_worker(signal)
579
- WORKERS.keys.each { |pid| kill_worker(signal, pid) }
579
+ WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
580
580
  end
581
581
 
582
582
  # unlinks a PID file at given +path+ if it contains the current PID
@@ -588,10 +588,10 @@ module Unicorn
588
588
  # returns a PID if a given path contains a non-stale PID file,
589
589
  # nil otherwise.
590
590
  def valid_pid?(path)
591
- if File.exist?(path) && (pid = File.read(path).to_i) > 1
591
+ if File.exist?(path) && (wpid = File.read(path).to_i) > 1
592
592
  begin
593
- Process.kill(0, pid)
594
- return pid
593
+ Process.kill(0, wpid)
594
+ return wpid
595
595
  rescue Errno::ESRCH
596
596
  end
597
597
  end
@@ -600,17 +600,17 @@ module Unicorn
600
600
 
601
601
  def load_config!
602
602
  begin
603
- logger.info "reloading config_file=#{@config.config_file}"
604
- @config[:listeners].replace(@init_listeners)
605
- @config.reload
606
- @config.commit!(self)
603
+ logger.info "reloading config_file=#{config.config_file}"
604
+ config[:listeners].replace(init_listeners)
605
+ config.reload
606
+ config.commit!(self)
607
607
  kill_each_worker(:QUIT)
608
608
  Unicorn::Util.reopen_logs
609
- @app = @orig_app
610
- build_app! if @preload_app
611
- logger.info "done reloading config_file=#{@config.config_file}"
609
+ self.app = orig_app
610
+ build_app! if preload_app
611
+ logger.info "done reloading config_file=#{config.config_file}"
612
612
  rescue Object => e
613
- logger.error "error reloading config_file=#{@config.config_file}: " \
613
+ logger.error "error reloading config_file=#{config.config_file}: " \
614
614
  "#{e.class} #{e.message}"
615
615
  end
616
616
  end
@@ -621,18 +621,18 @@ module Unicorn
621
621
  end
622
622
 
623
623
  def build_app!
624
- if @app.respond_to?(:arity) && @app.arity == 0
624
+ if app.respond_to?(:arity) && app.arity == 0
625
625
  if defined?(Gem) && Gem.respond_to?(:refresh)
626
626
  logger.info "Refreshing Gem list"
627
627
  Gem.refresh
628
628
  end
629
- @app = @app.call
629
+ self.app = app.call
630
630
  end
631
631
  end
632
632
 
633
633
  def proc_name(tag)
634
- $0 = ([ File.basename(START_CTX[:zero]), tag ] +
635
- START_CTX[:argv]).join(' ')
634
+ $0 = ([ File.basename(START_CTX[0]), tag
635
+ ]).concat(START_CTX[:argv]).join(' ')
636
636
  end
637
637
 
638
638
  def redirect_io(io, path)
@@ -5,7 +5,7 @@ module Unicorn::App
5
5
 
6
6
  # This class is highly experimental (even more so than the rest of Unicorn)
7
7
  # and has never run anything other than cgit.
8
- class ExecCgi
8
+ class ExecCgi < Struct.new(:args)
9
9
 
10
10
  CHUNK_SIZE = 16384
11
11
  PASS_VARS = %w(
@@ -32,12 +32,12 @@ module Unicorn::App
32
32
  # run Unicorn::App::ExecCgi.new("/path/to/cgit.cgi")
33
33
  # end
34
34
  def initialize(*args)
35
- @args = args.dup
36
- first = @args[0] or
35
+ self.args = args
36
+ first = args[0] or
37
37
  raise ArgumentError, "need path to executable"
38
- first[0..0] == "/" or @args[0] = ::File.expand_path(first)
39
- File.executable?(@args[0]) or
40
- raise ArgumentError, "#{@args[0]} is not executable"
38
+ first[0..0] == "/" or args[0] = ::File.expand_path(first)
39
+ File.executable?(args[0]) or
40
+ raise ArgumentError, "#{args[0]} is not executable"
41
41
  end
42
42
 
43
43
  # Calls the app
@@ -65,14 +65,14 @@ module Unicorn::App
65
65
  val = env[key] or next
66
66
  ENV[key] = val
67
67
  end
68
- ENV['SCRIPT_NAME'] = @args[0]
68
+ ENV['SCRIPT_NAME'] = args[0]
69
69
  ENV['GATEWAY_INTERFACE'] = 'CGI/1.1'
70
70
  env.keys.grep(/^HTTP_/) { |key| ENV[key] = env[key] }
71
71
 
72
72
  a = IO.new(0).reopen(inp)
73
73
  b = IO.new(1).reopen(out)
74
74
  c = IO.new(2).reopen(err)
75
- exec(*@args)
75
+ exec(*args)
76
76
  end
77
77
 
78
78
  # Extracts headers from CGI out, will change the offset of out.
@@ -89,23 +89,24 @@ module Unicorn::App
89
89
  offset = 4
90
90
  end
91
91
  offset += head.length
92
- out.instance_variable_set('@unicorn_app_exec_cgi_offset', offset)
93
- size -= offset
94
92
 
95
93
  # Allows +out+ to be used as a Rack body.
96
- def out.each
97
- sysseek(@unicorn_app_exec_cgi_offset)
98
-
99
- # don't use a preallocated buffer for sysread since we can't
100
- # guarantee an actual socket is consuming the yielded string
101
- # (or if somebody is pushing to an array for eventual concatenation
102
- begin
103
- yield(sysread(CHUNK_SIZE))
104
- rescue EOFError
105
- return
106
- end while true
107
- end
94
+ out.instance_eval { class << self; self; end }.instance_eval {
95
+ define_method(:each) { |&blk|
96
+ sysseek(offset)
97
+
98
+ # don't use a preallocated buffer for sysread since we can't
99
+ # guarantee an actual socket is consuming the yielded string
100
+ # (or if somebody is pushing to an array for eventual concatenation
101
+ begin
102
+ blk.call(sysread(CHUNK_SIZE))
103
+ rescue EOFError
104
+ break
105
+ end while true
106
+ }
107
+ }
108
108
 
109
+ size -= offset
109
110
  prev = nil
110
111
  headers = Rack::Utils::HeaderHash.new
111
112
  head.split(/\r?\n/).each do |line|
@@ -144,7 +145,7 @@ module Unicorn::App
144
145
  err.seek(0)
145
146
  dst = env['rack.errors']
146
147
  pid = status.pid
147
- dst.write("#{pid}: #{@args.inspect} status=#{status} stderr:\n")
148
+ dst.write("#{pid}: #{args.inspect} status=#{status} stderr:\n")
148
149
  err.each_line { |line| dst.write("#{pid}: #{line}") }
149
150
  dst.flush
150
151
  end
@@ -4,65 +4,66 @@
4
4
  # this class *must* be used with Rack::Chunked
5
5
 
6
6
  module Unicorn::App
7
- class Inetd
7
+ class Inetd < Struct.new(:cmd)
8
8
 
9
- class CatBody
9
+ class CatBody < Struct.new(:errors, :err_rd, :out_rd, :pid_map)
10
10
  def initialize(env, cmd)
11
- @cmd = cmd
12
- @input, @errors = env['rack.input'], env['rack.errors']
11
+ self.errors = env['rack.errors']
13
12
  in_rd, in_wr = IO.pipe
14
- @err_rd, err_wr = IO.pipe
15
- @out_rd, out_wr = IO.pipe
13
+ self.err_rd, err_wr = IO.pipe
14
+ self.out_rd, out_wr = IO.pipe
16
15
 
17
- @cmd_pid = fork {
16
+ cmd_pid = fork {
18
17
  inp, out, err = (0..2).map { |i| IO.new(i) }
19
18
  inp.reopen(in_rd)
20
19
  out.reopen(out_wr)
21
20
  err.reopen(err_wr)
22
- [ in_rd, in_wr, @err_rd, err_wr, @out_rd, out_wr ].each { |io|
23
- io.close
24
- }
21
+ [ in_rd, in_wr, err_rd, err_wr, out_rd, out_wr ].each { |i| i.close }
25
22
  exec(*cmd)
26
23
  }
27
24
  [ in_rd, err_wr, out_wr ].each { |io| io.close }
28
- [ in_wr, @err_rd, @out_rd ].each { |io| io.binmode }
25
+ [ in_wr, err_rd, out_rd ].each { |io| io.binmode }
29
26
  in_wr.sync = true
30
27
 
31
28
  # Unfortunately, input here must be processed inside a seperate
32
29
  # thread/process using blocking I/O since env['rack.input'] is not
33
30
  # IO.select-able and attempting to make it so would trip Rack::Lint
34
- @inp_pid = fork {
35
- [ @err_rd, @out_rd ].each { |io| io.close }
31
+ inp_pid = fork {
32
+ input = env['rack.input']
33
+ [ err_rd, out_rd ].each { |io| io.close }
36
34
  buf = Unicorn::Z.dup
37
35
 
38
- # this is dependent on @input.read having readpartial semantics:
39
- while @input.read(16384, buf)
36
+ # this is dependent on input.read having readpartial semantics:
37
+ while input.read(16384, buf)
40
38
  in_wr.write(buf)
41
39
  end
42
40
  in_wr.close
43
41
  }
44
42
  in_wr.close
43
+ self.pid_map = {
44
+ inp_pid => 'input streamer',
45
+ cmd_pid => cmd.inspect,
46
+ }
45
47
  end
46
48
 
47
49
  def each(&block)
48
- buf = Unicorn::Z.dup
49
50
  begin
50
- rd, = IO.select([@err_rd, @out_rd])
51
+ rd, = IO.select([err_rd, out_rd])
51
52
  rd && rd.first or next
52
53
 
53
- if rd.include?(@err_rd)
54
+ if rd.include?(err_rd)
54
55
  begin
55
- @errors.write(@err_rd.read_nonblock(16384, buf))
56
+ errors.write(err_rd.read_nonblock(16384))
56
57
  rescue Errno::EINTR
57
58
  rescue Errno::EAGAIN
58
59
  break
59
60
  end while true
60
61
  end
61
62
 
62
- rd.include?(@out_rd) or next
63
+ rd.include?(out_rd) or next
63
64
 
64
65
  begin
65
- yield @out_rd.read_nonblock(16384, buf)
66
+ yield out_rd.read_nonblock(16384)
66
67
  rescue Errno::EINTR
67
68
  rescue Errno::EAGAIN
68
69
  break
@@ -75,15 +76,13 @@ module Unicorn::App
75
76
  end
76
77
 
77
78
  def close
78
- @input = nil
79
- [ [ @cmd.inspect, @cmd_pid ], [ 'input streamer', @inp_pid ]
80
- ].each { |str, pid|
79
+ pid_map.each { |pid, str|
81
80
  begin
82
81
  pid, status = Process.waitpid2(pid)
83
82
  status.success? or
84
- @errors.write("#{str}: #{status.inspect} (PID:#{pid})\n")
83
+ errors.write("#{str}: #{status.inspect} (PID:#{pid})\n")
85
84
  rescue Errno::ECHILD
86
- @errors.write("Failed to reap #{str} (PID:#{pid})\n")
85
+ errors.write("Failed to reap #{str} (PID:#{pid})\n")
87
86
  end
88
87
  }
89
88
  end
@@ -91,16 +90,15 @@ module Unicorn::App
91
90
  end
92
91
 
93
92
  def initialize(*cmd)
94
- @cmd = cmd
93
+ self.cmd = cmd
95
94
  end
96
95
 
97
96
  def call(env)
98
- expect = env[Unicorn::Const::HTTP_EXPECT] and
99
- /\A100-continue\z/i =~ expect and
97
+ /\A100-continue\z/i =~ env[Unicorn::Const::HTTP_EXPECT] and
100
98
  return [ 100, {} , [] ]
101
99
 
102
100
  [ 200, { 'Content-Type' => 'application/octet-stream' },
103
- CatBody.new(env, @cmd) ]
101
+ CatBody.new(env, cmd) ]
104
102
  end
105
103
 
106
104
  end
@@ -19,28 +19,28 @@ require 'rack/file'
19
19
  # This means that if you are using page caching it will actually work
20
20
  # with Unicorn and you should see a decent speed boost (but not as
21
21
  # fast as if you use a static server like nginx).
22
- class Unicorn::App::OldRails::Static
22
+ class Unicorn::App::OldRails::Static < Struct.new(:app, :root, :file_server)
23
23
  FILE_METHODS = { 'GET' => true, 'HEAD' => true }.freeze
24
24
  REQUEST_METHOD = 'REQUEST_METHOD'.freeze
25
25
  REQUEST_URI = 'REQUEST_URI'.freeze
26
26
  PATH_INFO = 'PATH_INFO'.freeze
27
27
 
28
28
  def initialize(app)
29
- @app = app
30
- @root = "#{::RAILS_ROOT}/public"
31
- @file_server = ::Rack::File.new(@root)
29
+ self.app = app
30
+ self.root = "#{::RAILS_ROOT}/public"
31
+ self.file_server = ::Rack::File.new(root)
32
32
  end
33
33
 
34
34
  def call(env)
35
35
  # short circuit this ASAP if serving non-file methods
36
- FILE_METHODS.include?(env[REQUEST_METHOD]) or return @app.call(env)
36
+ FILE_METHODS.include?(env[REQUEST_METHOD]) or return app.call(env)
37
37
 
38
38
  # first try the path as-is
39
39
  path_info = env[PATH_INFO].chomp("/")
40
- if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
40
+ if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
41
41
  # File exists as-is so serve it up
42
42
  env[PATH_INFO] = path_info
43
- return @file_server.call(env)
43
+ return file_server.call(env)
44
44
  end
45
45
 
46
46
  # then try the cached version:
@@ -50,11 +50,11 @@ class Unicorn::App::OldRails::Static
50
50
  env[REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/
51
51
  path_info << "#$1#{ActionController::Base.page_cache_extension}"
52
52
 
53
- if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
53
+ if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}")
54
54
  env[PATH_INFO] = path_info
55
- return @file_server.call(env)
55
+ return file_server.call(env)
56
56
  end
57
57
 
58
- @app.call(env) # call OldRails
58
+ app.call(env) # call OldRails
59
59
  end
60
60
  end if defined?(Unicorn::App::OldRails)
@@ -14,14 +14,13 @@ module Unicorn
14
14
  # after_fork do |server,worker|
15
15
  # server.listen("127.0.0.1:#{9293 + worker.nr}") rescue nil
16
16
  # end
17
- class Configurator
17
+ class Configurator < Struct.new(:set, :config_file)
18
18
  # The default logger writes its output to $stderr
19
- DEFAULT_LOGGER = Logger.new($stderr) unless defined?(DEFAULT_LOGGER)
19
+ DEFAULT_LOGGER = Logger.new($stderr)
20
20
 
21
21
  # Default settings for Unicorn
22
22
  DEFAULTS = {
23
23
  :timeout => 60,
24
- :listeners => [],
25
24
  :logger => DEFAULT_LOGGER,
26
25
  :worker_processes => 1,
27
26
  :after_fork => lambda { |server, worker|
@@ -42,42 +41,32 @@ module Unicorn
42
41
  },
43
42
  :pid => nil,
44
43
  :preload_app => false,
45
- :stderr_path => nil,
46
- :stdout_path => nil,
47
44
  }
48
45
 
49
- attr_reader :config_file #:nodoc:
50
-
51
46
  def initialize(defaults = {}) #:nodoc:
52
- @set = Hash.new(:unset)
47
+ self.set = Hash.new(:unset)
53
48
  use_defaults = defaults.delete(:use_defaults)
54
- @config_file = defaults.delete(:config_file)
55
- @config_file.freeze
56
- @set.merge!(DEFAULTS) if use_defaults
49
+ self.config_file = defaults.delete(:config_file)
50
+ set.merge!(DEFAULTS) if use_defaults
57
51
  defaults.each { |key, value| self.send(key, value) }
58
52
  reload
59
53
  end
60
54
 
61
55
  def reload #:nodoc:
62
- instance_eval(File.read(@config_file)) if @config_file
56
+ instance_eval(File.read(config_file)) if config_file
63
57
  end
64
58
 
65
59
  def commit!(server, options = {}) #:nodoc:
66
60
  skip = options[:skip] || []
67
- @set.each do |key, value|
61
+ set.each do |key, value|
68
62
  value == :unset and next
69
63
  skip.include?(key) and next
70
- setter = "#{key}="
71
- if server.respond_to?(setter)
72
- server.send(setter, value)
73
- else
74
- server.instance_variable_set("@#{key}", value)
75
- end
64
+ server.__send__("#{key}=", value)
76
65
  end
77
66
  end
78
67
 
79
68
  def [](key) # :nodoc:
80
- @set[key]
69
+ set[key]
81
70
  end
82
71
 
83
72
  # sets object to the +new+ Logger-like object. The new logger-like
@@ -89,7 +78,7 @@ module Unicorn
89
78
  raise ArgumentError, "logger=#{new} does not respond to method=#{m}"
90
79
  end
91
80
 
92
- @set[:logger] = new
81
+ set[:logger] = new
93
82
  end
94
83
 
95
84
  # sets after_fork hook to a given block. This block will be called by
@@ -151,7 +140,7 @@ module Unicorn
151
140
  "not numeric: timeout=#{seconds.inspect}"
152
141
  seconds >= 3 or raise ArgumentError,
153
142
  "too low: timeout=#{seconds.inspect}"
154
- @set[:timeout] = seconds
143
+ set[:timeout] = seconds
155
144
  end
156
145
 
157
146
  # sets the current number of worker_processes to +nr+. Each worker
@@ -161,7 +150,7 @@ module Unicorn
161
150
  "not an integer: worker_processes=#{nr.inspect}"
162
151
  nr >= 0 or raise ArgumentError,
163
152
  "not non-negative: worker_processes=#{nr.inspect}"
164
- @set[:worker_processes] = nr
153
+ set[:worker_processes] = nr
165
154
  end
166
155
 
167
156
  # sets listeners to the given +addresses+, replacing or augmenting the
@@ -172,7 +161,7 @@ module Unicorn
172
161
  def listeners(addresses) # :nodoc:
173
162
  Array === addresses or addresses = Array(addresses)
174
163
  addresses.map! { |addr| expand_addr(addr) }
175
- @set[:listeners] = addresses
164
+ set[:listeners] = addresses
176
165
  end
177
166
 
178
167
  # adds an +address+ to the existing listener set.
@@ -227,8 +216,8 @@ module Unicorn
227
216
  def listen(address, opt = {})
228
217
  address = expand_addr(address)
229
218
  if String === address
230
- Hash === @set[:listener_opts] or
231
- @set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
219
+ Hash === set[:listener_opts] or
220
+ set[:listener_opts] = Hash.new { |hash,key| hash[key] = {} }
232
221
  [ :backlog, :sndbuf, :rcvbuf ].each do |key|
233
222
  value = opt[key] or next
234
223
  Integer === value or
@@ -239,11 +228,11 @@ module Unicorn
239
228
  TrueClass === value || FalseClass === value or
240
229
  raise ArgumentError, "not boolean: #{key}=#{value.inspect}"
241
230
  end
242
- @set[:listener_opts][address].merge!(opt)
231
+ set[:listener_opts][address].merge!(opt)
243
232
  end
244
233
 
245
- @set[:listeners] = [] unless Array === @set[:listeners]
246
- @set[:listeners] << address
234
+ set[:listeners] = [] unless Array === set[:listeners]
235
+ set[:listeners] << address
247
236
  end
248
237
 
249
238
  # sets the +path+ for the PID file of the unicorn master process
@@ -265,7 +254,7 @@ module Unicorn
265
254
  def preload_app(bool)
266
255
  case bool
267
256
  when TrueClass, FalseClass
268
- @set[:preload_app] = bool
257
+ set[:preload_app] = bool
269
258
  else
270
259
  raise ArgumentError, "preload_app=#{bool.inspect} not a boolean"
271
260
  end
@@ -298,7 +287,7 @@ module Unicorn
298
287
  else
299
288
  raise ArgumentError
300
289
  end
301
- @set[var] = path
290
+ set[var] = path
302
291
  end
303
292
 
304
293
  def set_hook(var, my_proc, req_arity = 2) #:nodoc:
@@ -314,7 +303,7 @@ module Unicorn
314
303
  else
315
304
  raise ArgumentError, "invalid type: #{var}=#{my_proc.inspect}"
316
305
  end
317
- @set[var] = my_proc
306
+ set[var] = my_proc
318
307
  end
319
308
 
320
309
  # expands "unix:path/to/foo" to a socket relative to the current path
@@ -5,7 +5,7 @@ module Unicorn
5
5
  # gave about a 3% to 10% performance improvement over using the strings directly.
6
6
  # Symbols did not really improve things much compared to constants.
7
7
  module Const
8
- UNICORN_VERSION="0.9.0".freeze
8
+ UNICORN_VERSION="0.9.1".freeze
9
9
 
10
10
  DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
11
11
  DEFAULT_PORT = "8080".freeze # default TCP listen port
@@ -6,8 +6,6 @@ require 'unicorn/http11'
6
6
  module Unicorn
7
7
  class HttpRequest
8
8
 
9
- attr_accessor :logger
10
-
11
9
  # default parameters we merge into the request env for Rack handlers
12
10
  DEFAULTS = {
13
11
  "rack.errors" => $stderr,
@@ -24,6 +22,9 @@ module Unicorn
24
22
  NULL_IO = StringIO.new(Z)
25
23
  LOCALHOST = '127.0.0.1'.freeze
26
24
 
25
+ def initialize
26
+ end
27
+
27
28
  # Being explicitly single-threaded, we have certain advantages in
28
29
  # not having to worry about variables being clobbered :)
29
30
  BUFFER = ' ' * Const::CHUNK_SIZE # initial size, may grow
@@ -31,10 +32,6 @@ module Unicorn
31
32
  PARSER = HttpParser.new
32
33
  PARAMS = Hash.new
33
34
 
34
- def initialize(logger = Configurator::DEFAULT_LOGGER)
35
- @logger = logger
36
- end
37
-
38
35
  # Does the majority of the IO processing. It has been written in
39
36
  # Ruby using about 8 different IO processing strategies.
40
37
  #
@@ -74,13 +71,6 @@ module Unicorn
74
71
  data << socket.readpartial(Const::CHUNK_SIZE, BUFFER)
75
72
  PARSER.execute(PARAMS, data) and return handle_body(socket)
76
73
  end while true
77
- rescue HttpParserError => e
78
- @logger.error "HTTP parse error, malformed request " \
79
- "(#{PARAMS[Const::HTTP_X_FORWARDED_FOR] ||
80
- PARAMS[Const::REMOTE_ADDR]}): #{e.inspect}"
81
- @logger.error "REQUEST DATA: #{data.inspect}\n---\n" \
82
- "PARAMS: #{PARAMS.inspect}\n---\n"
83
- raise e
84
74
  end
85
75
 
86
76
  private
@@ -2,6 +2,8 @@ require 'test/unit'
2
2
  require 'tempfile'
3
3
  require 'unicorn'
4
4
 
5
+ TestStruct = Struct.new(
6
+ *(Unicorn::Configurator::DEFAULTS.keys + %w(listener_opts listeners)))
5
7
  class TestConfigurator < Test::Unit::TestCase
6
8
 
7
9
  def test_config_init
@@ -51,22 +53,23 @@ class TestConfigurator < Test::Unit::TestCase
51
53
 
52
54
  def test_config_defaults
53
55
  cfg = Unicorn::Configurator.new(:use_defaults => true)
54
- assert_nothing_raised { cfg.commit!(self) }
56
+ test_struct = TestStruct.new
57
+ assert_nothing_raised { cfg.commit!(test_struct) }
55
58
  Unicorn::Configurator::DEFAULTS.each do |key,value|
56
- assert_equal value, instance_variable_get("@#{key.to_s}")
59
+ assert_equal value, test_struct.__send__(key)
57
60
  end
58
61
  end
59
62
 
60
63
  def test_config_defaults_skip
61
64
  cfg = Unicorn::Configurator.new(:use_defaults => true)
62
65
  skip = [ :logger ]
63
- assert_nothing_raised { cfg.commit!(self, :skip => skip) }
64
- @logger = nil
66
+ test_struct = TestStruct.new
67
+ assert_nothing_raised { cfg.commit!(test_struct, :skip => skip) }
65
68
  Unicorn::Configurator::DEFAULTS.each do |key,value|
66
69
  next if skip.include?(key)
67
- assert_equal value, instance_variable_get("@#{key.to_s}")
70
+ assert_equal value, test_struct.__send__(key)
68
71
  end
69
- assert_nil @logger
72
+ assert_nil test_struct.logger
70
73
  end
71
74
 
72
75
  def test_listen_options
@@ -78,8 +81,9 @@ class TestConfigurator < Test::Unit::TestCase
78
81
  assert_nothing_raised do
79
82
  cfg = Unicorn::Configurator.new(:config_file => tmp.path)
80
83
  end
81
- assert_nothing_raised { cfg.commit!(self) }
82
- assert(listener_opts = instance_variable_get("@listener_opts"))
84
+ test_struct = TestStruct.new
85
+ assert_nothing_raised { cfg.commit!(test_struct) }
86
+ assert(listener_opts = test_struct.listener_opts)
83
87
  assert_equal expect, listener_opts[listener]
84
88
  end
85
89
 
@@ -94,9 +98,10 @@ class TestConfigurator < Test::Unit::TestCase
94
98
  end
95
99
 
96
100
  def test_after_fork_proc
101
+ test_struct = TestStruct.new
97
102
  [ proc { |a,b| }, Proc.new { |a,b| }, lambda { |a,b| } ].each do |my_proc|
98
- Unicorn::Configurator.new(:after_fork => my_proc).commit!(self)
99
- assert_equal my_proc, @after_fork
103
+ Unicorn::Configurator.new(:after_fork => my_proc).commit!(test_struct)
104
+ assert_equal my_proc, test_struct.after_fork
100
105
  end
101
106
  end
102
107
 
@@ -20,7 +20,7 @@ class RequestTest < Test::Unit::TestCase
20
20
  end
21
21
 
22
22
  def setup
23
- @request = HttpRequest.new(Logger.new($stderr))
23
+ @request = HttpRequest.new
24
24
  @app = lambda do |env|
25
25
  [ 200, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
26
26
  end
@@ -2,23 +2,22 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{unicorn}
5
- s.version = "0.9.0"
5
+ s.version = "0.9.1"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Eric Wong"]
9
- s.date = %q{2009-07-01}
9
+ s.date = %q{2009-07-09}
10
10
  s.description = %q{Rack HTTP server for Unix, fast clients and nothing else}
11
11
  s.email = %q{normalperson@yhbt.net}
12
12
  s.executables = ["unicorn", "unicorn_rails"]
13
13
  s.extensions = ["ext/unicorn/http11/extconf.rb"]
14
14
  s.extra_rdoc_files = ["CHANGELOG", "COPYING", "LICENSE", "README", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/app/inetd.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/chunked_reader.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/tee_input.rb", "lib/unicorn/trailer_parser.rb", "lib/unicorn/util.rb"]
15
15
  s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "COPYING", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "PHILOSOPHY", "README", "Rakefile", "SIGNALS", "TODO", "TUNING", "bin/unicorn", "bin/unicorn_rails", "examples/echo.ru", "examples/git.ru", "examples/init.sh", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/app/inetd.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/chunked_reader.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/tee_input.rb", "lib/unicorn/trailer_parser.rb", "lib/unicorn/util.rb", "local.mk.sample", "setup.rb", "test/aggregate.rb", "test/benchmark/README", "test/benchmark/big_request.rb", "test/benchmark/dd.ru", "test/benchmark/request.rb", "test/benchmark/response.rb", "test/exec/README", "test/exec/test_exec.rb", "test/rails/app-1.2.3/.gitignore", "test/rails/app-1.2.3/Rakefile", "test/rails/app-1.2.3/app/controllers/application.rb", "test/rails/app-1.2.3/app/controllers/foo_controller.rb", "test/rails/app-1.2.3/app/helpers/application_helper.rb", "test/rails/app-1.2.3/config/boot.rb", "test/rails/app-1.2.3/config/database.yml", "test/rails/app-1.2.3/config/environment.rb", "test/rails/app-1.2.3/config/environments/development.rb", "test/rails/app-1.2.3/config/environments/production.rb", "test/rails/app-1.2.3/config/routes.rb", "test/rails/app-1.2.3/db/.gitignore", "test/rails/app-1.2.3/log/.gitignore", "test/rails/app-1.2.3/public/404.html", "test/rails/app-1.2.3/public/500.html", "test/rails/app-2.0.2/.gitignore", "test/rails/app-2.0.2/Rakefile", "test/rails/app-2.0.2/app/controllers/application.rb", "test/rails/app-2.0.2/app/controllers/foo_controller.rb", "test/rails/app-2.0.2/app/helpers/application_helper.rb", "test/rails/app-2.0.2/config/boot.rb", "test/rails/app-2.0.2/config/database.yml", "test/rails/app-2.0.2/config/environment.rb", "test/rails/app-2.0.2/config/environments/development.rb", "test/rails/app-2.0.2/config/environments/production.rb", "test/rails/app-2.0.2/config/routes.rb", "test/rails/app-2.0.2/db/.gitignore", "test/rails/app-2.0.2/log/.gitignore", "test/rails/app-2.0.2/public/404.html", "test/rails/app-2.0.2/public/500.html", "test/rails/app-2.1.2/.gitignore", "test/rails/app-2.1.2/Rakefile", "test/rails/app-2.1.2/app/controllers/application.rb", "test/rails/app-2.1.2/app/controllers/foo_controller.rb", "test/rails/app-2.1.2/app/helpers/application_helper.rb", "test/rails/app-2.1.2/config/boot.rb", "test/rails/app-2.1.2/config/database.yml", "test/rails/app-2.1.2/config/environment.rb", "test/rails/app-2.1.2/config/environments/development.rb", "test/rails/app-2.1.2/config/environments/production.rb", "test/rails/app-2.1.2/config/routes.rb", "test/rails/app-2.1.2/db/.gitignore", "test/rails/app-2.1.2/log/.gitignore", "test/rails/app-2.1.2/public/404.html", "test/rails/app-2.1.2/public/500.html", "test/rails/app-2.2.2/.gitignore", "test/rails/app-2.2.2/Rakefile", "test/rails/app-2.2.2/app/controllers/application.rb", "test/rails/app-2.2.2/app/controllers/foo_controller.rb", "test/rails/app-2.2.2/app/helpers/application_helper.rb", "test/rails/app-2.2.2/config/boot.rb", "test/rails/app-2.2.2/config/database.yml", "test/rails/app-2.2.2/config/environment.rb", "test/rails/app-2.2.2/config/environments/development.rb", "test/rails/app-2.2.2/config/environments/production.rb", "test/rails/app-2.2.2/config/routes.rb", "test/rails/app-2.2.2/db/.gitignore", "test/rails/app-2.2.2/log/.gitignore", "test/rails/app-2.2.2/public/404.html", "test/rails/app-2.2.2/public/500.html", "test/rails/app-2.3.2.1/.gitignore", "test/rails/app-2.3.2.1/Rakefile", "test/rails/app-2.3.2.1/app/controllers/application_controller.rb", "test/rails/app-2.3.2.1/app/controllers/foo_controller.rb", "test/rails/app-2.3.2.1/app/helpers/application_helper.rb", "test/rails/app-2.3.2.1/config/boot.rb", "test/rails/app-2.3.2.1/config/database.yml", "test/rails/app-2.3.2.1/config/environment.rb", "test/rails/app-2.3.2.1/config/environments/development.rb", "test/rails/app-2.3.2.1/config/environments/production.rb", "test/rails/app-2.3.2.1/config/routes.rb", "test/rails/app-2.3.2.1/db/.gitignore", "test/rails/app-2.3.2.1/log/.gitignore", "test/rails/app-2.3.2.1/public/404.html", "test/rails/app-2.3.2.1/public/500.html", "test/rails/test_rails.rb", "test/test_helper.rb", "test/unit/test_chunked_reader.rb", "test/unit/test_configurator.rb", "test/unit/test_http_parser.rb", "test/unit/test_request.rb", "test/unit/test_response.rb", "test/unit/test_server.rb", "test/unit/test_signals.rb", "test/unit/test_socket_helper.rb", "test/unit/test_trailer_parser.rb", "test/unit/test_upload.rb", "test/unit/test_util.rb", "unicorn.gemspec"]
16
- s.has_rdoc = true
17
16
  s.homepage = %q{http://unicorn.bogomips.org/}
18
17
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Unicorn", "--main", "README"]
19
18
  s.require_paths = ["lib", "ext"]
20
19
  s.rubyforge_project = %q{unicorn}
21
- s.rubygems_version = %q{1.3.2}
20
+ s.rubygems_version = %q{1.3.4}
22
21
  s.summary = %q{Rack HTTP server for Unix, fast clients and nothing else}
23
22
  s.test_files = ["test/unit/test_trailer_parser.rb", "test/unit/test_configurator.rb", "test/unit/test_chunked_reader.rb", "test/unit/test_response.rb", "test/unit/test_request.rb", "test/unit/test_signals.rb", "test/unit/test_upload.rb", "test/unit/test_http_parser.rb", "test/unit/test_socket_helper.rb", "test/unit/test_util.rb", "test/unit/test_server.rb"]
24
23
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unicorn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Wong
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-01 00:00:00 -07:00
12
+ date: 2009-07-09 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -231,7 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
231
231
  requirements: []
232
232
 
233
233
  rubyforge_project: unicorn
234
- rubygems_version: 1.3.2
234
+ rubygems_version: 1.3.4
235
235
  signing_key:
236
236
  specification_version: 3
237
237
  summary: Rack HTTP server for Unix, fast clients and nothing else