http_server: XEpollThreadSpawn sets RLIMIT_NPROC
[rainbows.git] / lib / rainbows / http_server.rb
blob8732e68968196395445c8b3cd23c7f222166aed4
1 # -*- encoding: binary -*-
2 # :enddoc:
4 class Rainbows::HttpServer < Unicorn::HttpServer
5   def self.setup(block)
6     Rainbows.server.instance_eval(&block)
7   end
9   def initialize(app, options)
10     Rainbows.server = self
11     @logger = Unicorn::Configurator::DEFAULTS[:logger]
12     super(app, options)
13     defined?(@use) or use(:Base)
14     @worker_connections ||= @use == :Base ? 1 : 50
15   end
17   def reopen_worker_logs(worker_nr)
18     logger.info "worker=#{worker_nr} reopening logs..."
19     Unicorn::Util.reopen_logs
20     logger.info "worker=#{worker_nr} done reopening logs"
21     rescue
22       Rainbows.quit! # let the master reopen and refork us
23   end
25   # Add one second to the timeout since our fchmod heartbeat is less
26   # precise (and must be more conservative) than Unicorn does.  We
27   # handle many clients per process and can't chmod on every
28   # connection we accept without wasting cycles.  That added to the
29   # fact that we let clients keep idle connections open for long
30   # periods of time means we have to chmod at a fixed interval.
31   def timeout=(nr)
32     @timeout = nr + 1
33   end
35   def load_config!
36     use :Base
37     Rainbows.defaults!
38     @worker_connections = nil
39     super
40     @worker_connections ||= @use == :Base ? 1 : 50
41   end
43   def worker_loop(worker)
44     orig = method(:worker_loop)
45     extend(Rainbows.const_get(@use))
46     m = method(:worker_loop)
47     orig == m ? super(worker) : worker_loop(worker)
48   end
50   def spawn_missing_workers
51     # 5: std{in,out,err} + heartbeat FD + per-process listener
52     nofile = 5 + @worker_connections + LISTENERS.size
53     trysetrlimit(:RLIMIT_NOFILE, nofile)
55     case @use
56     when :ThreadSpawn, :ThreadPool, :ActorSpawn,
57          :CoolioThreadSpawn, :RevThreadSpawn,
58          :XEpollThreadSpawn, :WriterThreadPool, :WriterThreadSpawn
59       trysetrlimit(:RLIMIT_NPROC, @worker_connections + LISTENERS.size + 1)
60     end
61     super
62   end
64   def trysetrlimit(resource, want)
65     var = Process.const_get(resource)
66     cur, max = Process.getrlimit(var)
67     cur <= want and Process.setrlimit(var, cur = max > want ? max : want)
68     if cur == want
69       @logger.warn "#{resource} rlim_cur=#{cur} is barely enough"
70       @logger.warn "#{svc} may monopolize resources dictated by #{resource}" \
71                    " and leave none for your app"
72     end
73     rescue => e
74       @logger.error e.message
75       @logger.error "#{resource} needs to be increased to >=#{want} before" \
76                     " starting #{svc}"
77   end
79   def svc
80     File.basename($0)
81   end
83   def use(*args)
84     model = args.shift or return @use
85     mod = begin
86       Rainbows.const_get(model)
87     rescue NameError => e
88       logger.error "error loading #{model.inspect}: #{e}"
89       e.backtrace.each { |l| logger.error l }
90       raise ArgumentError, "concurrency model #{model.inspect} not supported"
91     end
93     Module === mod or
94       raise ArgumentError, "concurrency model #{model.inspect} not supported"
95     args.each do |opt|
96       case opt
97       when Hash; Rainbows::O.update(opt)
98       when Symbol; Rainbows::O[opt] = true
99       else; raise ArgumentError, "can't handle option: #{opt.inspect}"
100       end
101     end
102     mod.setup if mod.respond_to?(:setup)
103     new_defaults = {
104       'rainbows.model' => (@use = model.to_sym),
105       'rack.multithread' => !!(model.to_s =~ /Thread/),
106       'rainbows.autochunk' => [:Coolio,:Rev,:Epoll,:XEpoll,
107                                :EventMachine,:NeverBlock].include?(@use),
108     }
109     Rainbows::Const::RACK_DEFAULTS.update(new_defaults)
110   end
112   def worker_connections(*args)
113     return @worker_connections if args.empty?
114     nr = args[0]
115     (Integer === nr && nr > 0) or
116       raise ArgumentError, "worker_connections must be a positive Integer"
117     @worker_connections = nr
118   end
120   def keepalive_timeout(nr)
121     (Integer === nr && nr >= 0) or
122       raise ArgumentError, "keepalive_timeout must be a non-negative Integer"
123     Rainbows.keepalive_timeout = nr
124   end
126   def keepalive_requests(nr)
127     Integer === nr or
128       raise ArgumentError, "keepalive_requests must be a non-negative Integer"
129     Unicorn::HttpRequest.keepalive_requests = nr
130   end
132   def client_max_body_size(nr)
133     err = "client_max_body_size must be nil or a non-negative Integer"
134     case nr
135     when nil
136     when Integer
137       nr >= 0 or raise ArgumentError, err
138     else
139       raise ArgumentError, err
140     end
141     Rainbows.client_max_body_size = nr
142   end
144   def client_header_buffer_size(bytes)
145     Integer === bytes && bytes > 0 or raise ArgumentError,
146             "client_header_buffer_size must be a positive Integer"
147     Rainbows.client_header_buffer_size = bytes
148   end