puma 2.7.0 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +5 -13
- data/DEPLOYMENT.md +91 -0
- data/Gemfile +3 -2
- data/History.txt +624 -1
- data/Manifest.txt +15 -3
- data/README.md +129 -14
- data/Rakefile +3 -3
- data/bin/puma-wild +31 -0
- data/bin/pumactl +1 -1
- data/docs/nginx.md +1 -1
- data/docs/signals.md +43 -0
- data/ext/puma_http11/extconf.rb +7 -2
- data/ext/puma_http11/http11_parser.java.rl +5 -5
- data/ext/puma_http11/io_buffer.c +1 -1
- data/ext/puma_http11/mini_ssl.c +233 -18
- data/ext/puma_http11/org/jruby/puma/Http11.java +12 -3
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -39
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +245 -195
- data/ext/puma_http11/puma_http11.c +12 -4
- data/lib/puma.rb +1 -0
- data/lib/puma/app/status.rb +7 -0
- data/lib/puma/binder.rb +108 -39
- data/lib/puma/capistrano.rb +23 -6
- data/lib/puma/cli.rb +141 -446
- data/lib/puma/client.rb +48 -1
- data/lib/puma/cluster.rb +207 -58
- data/lib/puma/commonlogger.rb +107 -0
- data/lib/puma/configuration.rb +262 -235
- data/lib/puma/const.rb +97 -14
- data/lib/puma/control_cli.rb +85 -77
- data/lib/puma/convenient.rb +23 -0
- data/lib/puma/daemon_ext.rb +11 -4
- data/lib/puma/detect.rb +8 -1
- data/lib/puma/dsl.rb +456 -0
- data/lib/puma/events.rb +35 -18
- data/lib/puma/jruby_restart.rb +1 -1
- data/lib/puma/launcher.rb +399 -0
- data/lib/puma/minissl.rb +49 -20
- data/lib/puma/null_io.rb +15 -0
- data/lib/puma/plugin.rb +104 -0
- data/lib/puma/plugin/tmp_restart.rb +35 -0
- data/lib/puma/rack/backports/uri/common_18.rb +56 -0
- data/lib/puma/rack/backports/uri/common_192.rb +52 -0
- data/lib/puma/rack/backports/uri/common_193.rb +29 -0
- data/lib/puma/rack/builder.rb +295 -0
- data/lib/puma/rack/urlmap.rb +90 -0
- data/lib/puma/reactor.rb +14 -1
- data/lib/puma/runner.rb +35 -17
- data/lib/puma/server.rb +161 -58
- data/lib/puma/single.rb +15 -10
- data/lib/puma/state_file.rb +29 -0
- data/lib/puma/thread_pool.rb +88 -13
- data/lib/puma/util.rb +123 -0
- data/lib/rack/handler/puma.rb +35 -29
- data/puma.gemspec +2 -4
- data/tools/jungle/init.d/README.md +2 -2
- data/tools/jungle/init.d/puma +69 -7
- data/tools/jungle/upstart/puma.conf +8 -2
- metadata +51 -71
- data/COPYING +0 -55
- data/TODO +0 -5
- data/lib/puma/rack_patch.rb +0 -45
- data/test/test_app_status.rb +0 -92
- data/test/test_cli.rb +0 -173
- data/test/test_config.rb +0 -16
- data/test/test_http10.rb +0 -27
- data/test/test_http11.rb +0 -145
- data/test/test_integration.rb +0 -165
- data/test/test_iobuffer.rb +0 -38
- data/test/test_minissl.rb +0 -25
- data/test/test_null_io.rb +0 -31
- data/test/test_persistent.rb +0 -238
- data/test/test_puma_server.rb +0 -292
- data/test/test_rack_handler.rb +0 -10
- data/test/test_rack_server.rb +0 -141
- data/test/test_tcp_rack.rb +0 -42
- data/test/test_thread_pool.rb +0 -156
- data/test/test_unix_socket.rb +0 -39
- data/test/test_ws.rb +0 -89
data/lib/puma/single.rb
CHANGED
@@ -27,7 +27,12 @@ module Puma
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def jruby_daemon?
|
30
|
-
daemon? and
|
30
|
+
daemon? and Puma.jruby?
|
31
|
+
end
|
32
|
+
|
33
|
+
def jruby_daemon_start
|
34
|
+
require 'puma/jruby_restart'
|
35
|
+
JRubyRestart.daemon_start(@restart_dir, @launcher.restart_args)
|
31
36
|
end
|
32
37
|
|
33
38
|
def run
|
@@ -62,35 +67,35 @@ module Puma
|
|
62
67
|
end
|
63
68
|
|
64
69
|
Signal.trap "SIGCHLD" do
|
65
|
-
log "! Error starting new process as daemon,
|
70
|
+
log "! Error starting new process as daemon, exiting"
|
66
71
|
exit 1
|
67
72
|
end
|
68
73
|
|
69
|
-
pid =
|
74
|
+
pid = jruby_daemon_start
|
70
75
|
sleep
|
71
76
|
end
|
72
77
|
else
|
73
|
-
load_and_bind
|
74
|
-
|
75
78
|
if daemon?
|
76
79
|
log "* Daemonizing..."
|
77
80
|
Process.daemon(true)
|
81
|
+
redirect_io
|
78
82
|
end
|
83
|
+
|
84
|
+
load_and_bind
|
79
85
|
end
|
80
86
|
|
81
|
-
@
|
87
|
+
@launcher.write_state
|
82
88
|
|
83
89
|
start_control
|
84
90
|
|
85
91
|
@server = server = start_server
|
86
92
|
|
87
|
-
unless
|
93
|
+
unless daemon?
|
88
94
|
log "Use Ctrl-C to stop"
|
95
|
+
redirect_io
|
89
96
|
end
|
90
97
|
|
91
|
-
|
92
|
-
|
93
|
-
@cli.events.fire_on_booted!
|
98
|
+
@launcher.events.fire_on_booted!
|
94
99
|
|
95
100
|
begin
|
96
101
|
server.run.join
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Puma
|
4
|
+
class StateFile
|
5
|
+
def initialize
|
6
|
+
@options = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def save(path)
|
10
|
+
File.write path, YAML.dump(@options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(path)
|
14
|
+
@options = YAML.load File.read(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
FIELDS = %w!control_url control_auth_token pid!
|
18
|
+
|
19
|
+
FIELDS.each do |f|
|
20
|
+
define_method f do
|
21
|
+
@options[f]
|
22
|
+
end
|
23
|
+
|
24
|
+
define_method "#{f}=" do |v|
|
25
|
+
@options[f] = v
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/puma/thread_pool.rb
CHANGED
@@ -12,7 +12,8 @@ module Puma
|
|
12
12
|
# thread.
|
13
13
|
#
|
14
14
|
def initialize(min, max, *extra, &block)
|
15
|
-
@
|
15
|
+
@not_empty = ConditionVariable.new
|
16
|
+
@not_full = ConditionVariable.new
|
16
17
|
@mutex = Mutex.new
|
17
18
|
|
18
19
|
@todo = []
|
@@ -32,13 +33,23 @@ module Puma
|
|
32
33
|
@workers = []
|
33
34
|
|
34
35
|
@auto_trim = nil
|
36
|
+
@reaper = nil
|
35
37
|
|
36
38
|
@mutex.synchronize do
|
37
39
|
@min.times { spawn_thread }
|
38
40
|
end
|
41
|
+
|
42
|
+
@clean_thread_locals = false
|
39
43
|
end
|
40
44
|
|
41
45
|
attr_reader :spawned, :trim_requested
|
46
|
+
attr_accessor :clean_thread_locals
|
47
|
+
|
48
|
+
def self.clean_thread_locals
|
49
|
+
Thread.current.keys.each do |key|
|
50
|
+
Thread.current[key] = nil unless key == :__recursive_key__
|
51
|
+
end
|
52
|
+
end
|
42
53
|
|
43
54
|
# How many objects have yet to be processed by the pool?
|
44
55
|
#
|
@@ -57,7 +68,8 @@ module Puma
|
|
57
68
|
todo = @todo
|
58
69
|
block = @block
|
59
70
|
mutex = @mutex
|
60
|
-
|
71
|
+
not_empty = @not_empty
|
72
|
+
not_full = @not_full
|
61
73
|
|
62
74
|
extra = @extra.map { |i| i.new }
|
63
75
|
|
@@ -71,6 +83,7 @@ module Puma
|
|
71
83
|
if @trim_requested > 0
|
72
84
|
@trim_requested -= 1
|
73
85
|
continue = false
|
86
|
+
not_full.signal
|
74
87
|
break
|
75
88
|
end
|
76
89
|
|
@@ -80,16 +93,24 @@ module Puma
|
|
80
93
|
end
|
81
94
|
|
82
95
|
@waiting += 1
|
83
|
-
|
96
|
+
not_full.signal
|
97
|
+
not_empty.wait mutex
|
84
98
|
@waiting -= 1
|
85
99
|
end
|
86
100
|
|
87
|
-
work = todo.
|
101
|
+
work = todo.shift if continue
|
88
102
|
end
|
89
103
|
|
90
104
|
break unless continue
|
91
105
|
|
92
|
-
|
106
|
+
if @clean_thread_locals
|
107
|
+
ThreadPool.clean_thread_locals
|
108
|
+
end
|
109
|
+
|
110
|
+
begin
|
111
|
+
block.call(work, *extra)
|
112
|
+
rescue Exception
|
113
|
+
end
|
93
114
|
end
|
94
115
|
|
95
116
|
mutex.synchronize do
|
@@ -114,11 +135,19 @@ module Puma
|
|
114
135
|
|
115
136
|
@todo << work
|
116
137
|
|
117
|
-
if @waiting
|
138
|
+
if @waiting < @todo.size and @spawned < @max
|
118
139
|
spawn_thread
|
119
140
|
end
|
120
141
|
|
121
|
-
@
|
142
|
+
@not_empty.signal
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def wait_until_not_full
|
147
|
+
@mutex.synchronize do
|
148
|
+
until @todo.size - @waiting < @max - @spawned or @shutdown
|
149
|
+
@not_full.wait @mutex
|
150
|
+
end
|
122
151
|
end
|
123
152
|
end
|
124
153
|
|
@@ -130,11 +159,26 @@ module Puma
|
|
130
159
|
@mutex.synchronize do
|
131
160
|
if (force or @waiting > 0) and @spawned - @trim_requested > @min
|
132
161
|
@trim_requested += 1
|
133
|
-
@
|
162
|
+
@not_empty.signal
|
134
163
|
end
|
135
164
|
end
|
136
165
|
end
|
137
166
|
|
167
|
+
# If there are dead threads in the pool make them go away while decreasing
|
168
|
+
# spawned counter so that new healthy threads could be created again.
|
169
|
+
def reap
|
170
|
+
@mutex.synchronize do
|
171
|
+
dead_workers = @workers.reject(&:alive?)
|
172
|
+
|
173
|
+
dead_workers.each do |worker|
|
174
|
+
worker.kill
|
175
|
+
@spawned -= 1
|
176
|
+
end
|
177
|
+
|
178
|
+
@workers -= dead_workers
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
138
182
|
class AutoTrim
|
139
183
|
def initialize(pool, timeout)
|
140
184
|
@pool = pool
|
@@ -164,19 +208,50 @@ module Puma
|
|
164
208
|
@auto_trim.start!
|
165
209
|
end
|
166
210
|
|
211
|
+
class Reaper
|
212
|
+
def initialize(pool, timeout)
|
213
|
+
@pool = pool
|
214
|
+
@timeout = timeout
|
215
|
+
@running = false
|
216
|
+
end
|
217
|
+
|
218
|
+
def start!
|
219
|
+
@running = true
|
220
|
+
|
221
|
+
@thread = Thread.new do
|
222
|
+
while @running
|
223
|
+
@pool.reap
|
224
|
+
sleep @timeout
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def stop
|
230
|
+
@running = false
|
231
|
+
@thread.wakeup
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def auto_reap!(timeout=5)
|
236
|
+
@reaper = Reaper.new(self, timeout)
|
237
|
+
@reaper.start!
|
238
|
+
end
|
239
|
+
|
167
240
|
# Tell all threads in the pool to exit and wait for them to finish.
|
168
241
|
#
|
169
242
|
def shutdown
|
170
|
-
@mutex.synchronize do
|
243
|
+
threads = @mutex.synchronize do
|
171
244
|
@shutdown = true
|
172
|
-
@
|
245
|
+
@not_empty.broadcast
|
246
|
+
@not_full.broadcast
|
173
247
|
|
174
248
|
@auto_trim.stop if @auto_trim
|
249
|
+
@reaper.stop if @reaper
|
250
|
+
# dup workers so that we join them all safely
|
251
|
+
@workers.dup
|
175
252
|
end
|
176
253
|
|
177
|
-
|
178
|
-
# of each and see a mutated object mid #each
|
179
|
-
@workers.first.join until @workers.empty?
|
254
|
+
threads.each(&:join)
|
180
255
|
|
181
256
|
@spawned = 0
|
182
257
|
@workers = []
|
data/lib/puma/util.rb
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
major, minor, patch = RUBY_VERSION.split('.').map { |v| v.to_i }
|
2
|
+
|
3
|
+
if major == 1 && minor < 9
|
4
|
+
require 'puma/rack/backports/uri/common_18'
|
5
|
+
elsif major == 1 && minor == 9 && patch == 2 && RUBY_PATCHLEVEL <= 328 && RUBY_ENGINE != 'jruby'
|
6
|
+
require 'puma/rack/backports/uri/common_192'
|
7
|
+
elsif major == 1 && minor == 9 && patch == 3 && RUBY_PATCHLEVEL < 125
|
8
|
+
require 'puma/rack/backports/uri/common_193'
|
9
|
+
else
|
10
|
+
require 'uri/common'
|
11
|
+
end
|
12
|
+
|
1
13
|
module Puma
|
2
14
|
module Util
|
3
15
|
module_function
|
@@ -5,5 +17,116 @@ module Puma
|
|
5
17
|
def pipe
|
6
18
|
IO.pipe
|
7
19
|
end
|
20
|
+
|
21
|
+
# Unescapes a URI escaped string with +encoding+. +encoding+ will be the
|
22
|
+
# target encoding of the string returned, and it defaults to UTF-8
|
23
|
+
if defined?(::Encoding)
|
24
|
+
def unescape(s, encoding = Encoding::UTF_8)
|
25
|
+
URI.decode_www_form_component(s, encoding)
|
26
|
+
end
|
27
|
+
else
|
28
|
+
def unescape(s, encoding = nil)
|
29
|
+
URI.decode_www_form_component(s, encoding)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
module_function :unescape
|
33
|
+
|
34
|
+
DEFAULT_SEP = /[&;] */n
|
35
|
+
|
36
|
+
# Stolen from Mongrel, with some small modifications:
|
37
|
+
# Parses a query string by breaking it up at the '&'
|
38
|
+
# and ';' characters. You can also use this to parse
|
39
|
+
# cookies by changing the characters used in the second
|
40
|
+
# parameter (which defaults to '&;').
|
41
|
+
def parse_query(qs, d = nil, &unescaper)
|
42
|
+
unescaper ||= method(:unescape)
|
43
|
+
|
44
|
+
params = {}
|
45
|
+
|
46
|
+
(qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
|
47
|
+
next if p.empty?
|
48
|
+
k, v = p.split('=', 2).map(&unescaper)
|
49
|
+
|
50
|
+
if cur = params[k]
|
51
|
+
if cur.class == Array
|
52
|
+
params[k] << v
|
53
|
+
else
|
54
|
+
params[k] = [cur, v]
|
55
|
+
end
|
56
|
+
else
|
57
|
+
params[k] = v
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
return params
|
62
|
+
end
|
63
|
+
|
64
|
+
# A case-insensitive Hash that preserves the original case of a
|
65
|
+
# header when set.
|
66
|
+
class HeaderHash < Hash
|
67
|
+
def self.new(hash={})
|
68
|
+
HeaderHash === hash ? hash : super(hash)
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(hash={})
|
72
|
+
super()
|
73
|
+
@names = {}
|
74
|
+
hash.each { |k, v| self[k] = v }
|
75
|
+
end
|
76
|
+
|
77
|
+
def each
|
78
|
+
super do |k, v|
|
79
|
+
yield(k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_hash
|
84
|
+
hash = {}
|
85
|
+
each { |k,v| hash[k] = v }
|
86
|
+
hash
|
87
|
+
end
|
88
|
+
|
89
|
+
def [](k)
|
90
|
+
super(k) || super(@names[k.downcase])
|
91
|
+
end
|
92
|
+
|
93
|
+
def []=(k, v)
|
94
|
+
canonical = k.downcase
|
95
|
+
delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
|
96
|
+
@names[k] = @names[canonical] = k
|
97
|
+
super k, v
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete(k)
|
101
|
+
canonical = k.downcase
|
102
|
+
result = super @names.delete(canonical)
|
103
|
+
@names.delete_if { |name,| name.downcase == canonical }
|
104
|
+
result
|
105
|
+
end
|
106
|
+
|
107
|
+
def include?(k)
|
108
|
+
@names.include?(k) || @names.include?(k.downcase)
|
109
|
+
end
|
110
|
+
|
111
|
+
alias_method :has_key?, :include?
|
112
|
+
alias_method :member?, :include?
|
113
|
+
alias_method :key?, :include?
|
114
|
+
|
115
|
+
def merge!(other)
|
116
|
+
other.each { |k, v| self[k] = v }
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
def merge(other)
|
121
|
+
hash = dup
|
122
|
+
hash.merge! other
|
123
|
+
end
|
124
|
+
|
125
|
+
def replace(other)
|
126
|
+
clear
|
127
|
+
other.each { |k, v| self[k] = v }
|
128
|
+
self
|
129
|
+
end
|
130
|
+
end
|
8
131
|
end
|
9
132
|
end
|
data/lib/rack/handler/puma.rb
CHANGED
@@ -5,44 +5,55 @@ module Rack
|
|
5
5
|
module Handler
|
6
6
|
module Puma
|
7
7
|
DEFAULT_OPTIONS = {
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:Threads => '0:16',
|
11
|
-
:Verbose => false
|
8
|
+
:Verbose => false,
|
9
|
+
:Silent => false
|
12
10
|
}
|
13
11
|
|
14
12
|
def self.run(app, options = {})
|
15
13
|
options = DEFAULT_OPTIONS.merge(options)
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
15
|
+
conf = ::Puma::Configuration.new do |c|
|
16
|
+
c.quiet
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
if options.delete(:Verbose)
|
19
|
+
app = Rack::CommonLogger.new(app, STDOUT)
|
20
|
+
end
|
21
|
+
|
22
|
+
if options[:environment]
|
23
|
+
c.environment options[:environment]
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
26
|
+
if options[:Threads]
|
27
|
+
min, max = options.delete(:Threads).split(':', 2)
|
28
|
+
c.threads min, max
|
29
|
+
end
|
27
30
|
|
28
|
-
|
29
|
-
puts "* Min threads: #{min}, max threads: #{max}"
|
30
|
-
puts "* Environment: #{ENV['RACK_ENV']}"
|
31
|
-
puts "* Listening on tcp://#{options[:Host]}:#{options[:Port]}"
|
31
|
+
host = options[:Host]
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
if host && (host[0,1] == '.' || host[0,1] == '/')
|
34
|
+
c.bind "unix://#{host}"
|
35
|
+
else
|
36
|
+
host ||= ::Puma::Configuration::DefaultTCPHost
|
37
|
+
port = options[:Port] || ::Puma::Configuration::DefaultTCPPort
|
38
|
+
|
39
|
+
c.port port, host
|
40
|
+
end
|
41
|
+
|
42
|
+
c.app app
|
43
|
+
end
|
37
44
|
|
45
|
+
events = options.delete(:Silent) ? ::Puma::Events.strings : ::Puma::Events.stdio
|
46
|
+
|
47
|
+
launcher = ::Puma::Launcher.new(conf, :events => events)
|
48
|
+
|
49
|
+
yield launcher if block_given?
|
38
50
|
begin
|
39
|
-
|
51
|
+
launcher.run
|
40
52
|
rescue Interrupt
|
41
53
|
puts "* Gracefully stopping, waiting for requests to finish"
|
42
|
-
|
54
|
+
launcher.stop
|
43
55
|
puts "* Goodbye!"
|
44
56
|
end
|
45
|
-
|
46
57
|
end
|
47
58
|
|
48
59
|
def self.valid_options
|
@@ -50,7 +61,7 @@ module Rack
|
|
50
61
|
"Host=HOST" => "Hostname to listen on (default: localhost)",
|
51
62
|
"Port=PORT" => "Port to listen on (default: 8080)",
|
52
63
|
"Threads=MIN:MAX" => "min:max threads to use (default 0:16)",
|
53
|
-
"
|
64
|
+
"Verbose" => "Don't report each request (default: false)"
|
54
65
|
}
|
55
66
|
end
|
56
67
|
end
|
@@ -59,8 +70,3 @@ module Rack
|
|
59
70
|
end
|
60
71
|
end
|
61
72
|
|
62
|
-
# This is to trick newrelic into enabling the agent automatically.
|
63
|
-
module Mongrel
|
64
|
-
class HttpServer
|
65
|
-
end
|
66
|
-
end
|