event_machine: disconnect idle clients at on SIGQUIT
[rainbows.git] / lib / rainbows / http_server.rb
blobcbeb38f458ec57a70ce2ae64ab1e832076dca2a6
1 # -*- encoding: binary -*-
2 # :enddoc:
4 class Rainbows::HttpServer < Unicorn::HttpServer
5   attr_accessor :copy_stream
6   attr_accessor :worker_connections
7   attr_accessor :keepalive_timeout
8   attr_accessor :client_header_buffer_size
9   attr_accessor :client_max_body_size
10   attr_reader :use
12   def self.setup(block)
13     Rainbows.server.instance_eval(&block)
14   end
16   def initialize(app, options)
17     Rainbows.server = self
18     @logger = Unicorn::Configurator::DEFAULTS[:logger]
19     super(app, options)
20     defined?(@use) or self.use = Rainbows::Base
21     @worker_connections ||= @use == :Base ? 1 : 50
22   end
24   def reopen_worker_logs(worker_nr)
25     logger.info "worker=#{worker_nr} reopening logs..."
26     Unicorn::Util.reopen_logs
27     logger.info "worker=#{worker_nr} done reopening logs"
28     rescue
29       Rainbows.quit! # let the master reopen and refork us
30   end
32   # Add one second to the timeout since our fchmod heartbeat is less
33   # precise (and must be more conservative) than Unicorn does.  We
34   # handle many clients per process and can't chmod on every
35   # connection we accept without wasting cycles.  That added to the
36   # fact that we let clients keep idle connections open for long
37   # periods of time means we have to chmod at a fixed interval.
38   def timeout=(nr)
39     @timeout = nr + 1
40   end
42   def load_config!
43     super
44     @worker_connections = 1 if @use == :Base
45   end
47   def worker_loop(worker)
48     Rainbows.forked = true
49     orig = method(:worker_loop)
50     extend(Rainbows.const_get(@use))
51     m = method(:worker_loop)
52     orig == m ? super(worker) : worker_loop(worker)
53   end
55   def spawn_missing_workers
56     # 5: std{in,out,err} + heartbeat FD + per-process listener
57     nofile = 5 + @worker_connections + LISTENERS.size
58     trysetrlimit(:RLIMIT_NOFILE, nofile)
60     case @use
61     when :ThreadSpawn, :ThreadPool, :ActorSpawn,
62          :CoolioThreadSpawn, :RevThreadSpawn,
63          :XEpollThreadSpawn, :WriterThreadPool, :WriterThreadSpawn
64       trysetrlimit(:RLIMIT_NPROC, @worker_connections + LISTENERS.size + 1)
65     when :XEpollThreadPool, :CoolioThreadPool
66       trysetrlimit(:RLIMIT_NPROC, Rainbows::O[:pool_size] + LISTENERS.size + 1)
67     end
68     super
69   end
71   def trysetrlimit(resource, want)
72     var = Process.const_get(resource)
73     cur, max = Process.getrlimit(var)
74     cur <= want and Process.setrlimit(var, cur = max > want ? max : want)
75     if cur == want
76       @logger.warn "#{resource} rlim_cur=#{cur} is barely enough"
77       @logger.warn "#{svc} may monopolize resources dictated by #{resource}" \
78                    " and leave none for your app"
79     end
80     rescue => e
81       @logger.error e.message
82       @logger.error "#{resource} needs to be increased to >=#{want} before" \
83                     " starting #{svc}"
84   end
86   def svc
87     File.basename($0)
88   end
90   def use=(mod)
91     @use = mod.to_s.split(/::/)[-1].to_sym
92     new_defaults = {
93       'rainbows.model' => @use,
94       'rack.multithread' => !!(mod.to_s =~ /Thread/),
95       'rainbows.autochunk' => [:Coolio,:Rev,:Epoll,:XEpoll,
96                                :EventMachine,:NeverBlock].include?(@use),
97     }
98     Rainbows::Const::RACK_DEFAULTS.update(new_defaults)
99   end
101   def keepalive_requests=(nr)
102     Unicorn::HttpRequest.keepalive_requests = nr
103   end
105   def keepalive_requests
106     Unicorn::HttpRequest.keepalive_requests
107   end