unicorn 0.9.0 → 0.9.1

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