puma 3.0.0.rc1 → 5.0.0.beta1

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.

Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/{History.txt → History.md} +703 -70
  3. data/LICENSE +23 -20
  4. data/README.md +173 -163
  5. data/docs/architecture.md +37 -0
  6. data/{DEPLOYMENT.md → docs/deployment.md} +28 -6
  7. data/docs/fork_worker.md +31 -0
  8. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  9. data/docs/images/puma-connection-flow.png +0 -0
  10. data/docs/images/puma-general-arch.png +0 -0
  11. data/docs/jungle/README.md +13 -0
  12. data/docs/jungle/rc.d/README.md +74 -0
  13. data/docs/jungle/rc.d/puma +61 -0
  14. data/docs/jungle/rc.d/puma.conf +10 -0
  15. data/{tools → docs}/jungle/upstart/README.md +0 -0
  16. data/{tools → docs}/jungle/upstart/puma-manager.conf +0 -0
  17. data/{tools → docs}/jungle/upstart/puma.conf +1 -1
  18. data/docs/nginx.md +2 -2
  19. data/docs/plugins.md +38 -0
  20. data/docs/restart.md +41 -0
  21. data/docs/signals.md +57 -3
  22. data/docs/systemd.md +228 -0
  23. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  24. data/ext/puma_http11/extconf.rb +16 -0
  25. data/ext/puma_http11/http11_parser.c +287 -468
  26. data/ext/puma_http11/http11_parser.h +1 -0
  27. data/ext/puma_http11/http11_parser.java.rl +21 -37
  28. data/ext/puma_http11/http11_parser.rl +10 -9
  29. data/ext/puma_http11/http11_parser_common.rl +4 -4
  30. data/ext/puma_http11/mini_ssl.c +159 -10
  31. data/ext/puma_http11/org/jruby/puma/Http11.java +108 -116
  32. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +99 -132
  33. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +30 -6
  34. data/ext/puma_http11/puma_http11.c +6 -38
  35. data/lib/puma.rb +25 -5
  36. data/lib/puma/accept_nonblock.rb +7 -1
  37. data/lib/puma/app/status.rb +53 -26
  38. data/lib/puma/binder.rb +150 -119
  39. data/lib/puma/cli.rb +56 -38
  40. data/lib/puma/client.rb +277 -80
  41. data/lib/puma/cluster.rb +326 -130
  42. data/lib/puma/commonlogger.rb +21 -20
  43. data/lib/puma/configuration.rb +160 -161
  44. data/lib/puma/const.rb +50 -47
  45. data/lib/puma/control_cli.rb +104 -63
  46. data/lib/puma/detect.rb +13 -1
  47. data/lib/puma/dsl.rb +463 -114
  48. data/lib/puma/events.rb +22 -13
  49. data/lib/puma/io_buffer.rb +9 -5
  50. data/lib/puma/jruby_restart.rb +2 -59
  51. data/lib/puma/launcher.rb +195 -105
  52. data/lib/puma/minissl.rb +110 -4
  53. data/lib/puma/minissl/context_builder.rb +76 -0
  54. data/lib/puma/null_io.rb +9 -14
  55. data/lib/puma/plugin.rb +32 -12
  56. data/lib/puma/plugin/tmp_restart.rb +19 -6
  57. data/lib/puma/rack/builder.rb +7 -5
  58. data/lib/puma/rack/urlmap.rb +11 -8
  59. data/lib/puma/rack_default.rb +2 -0
  60. data/lib/puma/reactor.rb +242 -32
  61. data/lib/puma/runner.rb +41 -30
  62. data/lib/puma/server.rb +265 -183
  63. data/lib/puma/single.rb +22 -63
  64. data/lib/puma/state_file.rb +9 -2
  65. data/lib/puma/thread_pool.rb +179 -68
  66. data/lib/puma/util.rb +3 -11
  67. data/lib/rack/handler/puma.rb +60 -11
  68. data/tools/Dockerfile +16 -0
  69. data/tools/trickletest.rb +1 -2
  70. metadata +35 -99
  71. data/COPYING +0 -55
  72. data/Gemfile +0 -13
  73. data/Manifest.txt +0 -79
  74. data/Rakefile +0 -158
  75. data/docs/config.md +0 -0
  76. data/ext/puma_http11/io_buffer.c +0 -155
  77. data/lib/puma/capistrano.rb +0 -94
  78. data/lib/puma/compat.rb +0 -18
  79. data/lib/puma/convenient.rb +0 -23
  80. data/lib/puma/daemon_ext.rb +0 -31
  81. data/lib/puma/delegation.rb +0 -11
  82. data/lib/puma/java_io_buffer.rb +0 -45
  83. data/lib/puma/rack/backports/uri/common_18.rb +0 -56
  84. data/lib/puma/rack/backports/uri/common_192.rb +0 -52
  85. data/lib/puma/rack/backports/uri/common_193.rb +0 -29
  86. data/lib/puma/tcp_logger.rb +0 -32
  87. data/puma.gemspec +0 -52
  88. data/tools/jungle/README.md +0 -9
  89. data/tools/jungle/init.d/README.md +0 -54
  90. data/tools/jungle/init.d/puma +0 -394
  91. data/tools/jungle/init.d/run-puma +0 -3
@@ -1,3 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'io/wait'
5
+ rescue LoadError
6
+ end
7
+
1
8
  module Puma
2
9
  module MiniSSL
3
10
  class Socket
@@ -11,6 +18,10 @@ module Puma
11
18
  @socket
12
19
  end
13
20
 
21
+ def closed?
22
+ @socket.closed?
23
+ end
24
+
14
25
  def readpartial(size)
15
26
  while true
16
27
  output = @engine.read
@@ -36,12 +47,28 @@ module Puma
36
47
  output
37
48
  end
38
49
 
39
- def read_nonblock(size)
50
+ def read_nonblock(size, *_)
51
+ # *_ is to deal with keyword args that were added
52
+ # at some point (and being used in the wild)
40
53
  while true
41
54
  output = engine_read_all
42
55
  return output if output
43
56
 
44
- data = @socket.read_nonblock(size)
57
+ data = @socket.read_nonblock(size, exception: false)
58
+ if data == :wait_readable || data == :wait_writable
59
+ # It would make more sense to let @socket.read_nonblock raise
60
+ # EAGAIN if necessary but it seems like it'll misbehave on Windows.
61
+ # I don't have a Windows machine to debug this so I can't explain
62
+ # exactly whats happening in that OS. Please let me know if you
63
+ # find out!
64
+ #
65
+ # In the meantime, we can emulate the correct behavior by
66
+ # capturing :wait_readable & :wait_writable and raising EAGAIN
67
+ # ourselves.
68
+ raise IO::EAGAINWaitReadable
69
+ elsif data.nil?
70
+ return nil
71
+ end
45
72
 
46
73
  @engine.inject(data)
47
74
  output = engine_read_all
@@ -55,6 +82,8 @@ module Puma
55
82
  end
56
83
 
57
84
  def write(data)
85
+ return 0 if data.empty?
86
+
58
87
  need = data.bytesize
59
88
 
60
89
  while true
@@ -75,13 +104,53 @@ module Puma
75
104
  end
76
105
 
77
106
  alias_method :syswrite, :write
107
+ alias_method :<<, :write
108
+
109
+ # This is a temporary fix to deal with websockets code using
110
+ # write_nonblock. The problem with implementing it properly
111
+ # is that it means we'd have to have the ability to rewind
112
+ # an engine because after we write+extract, the socket
113
+ # write_nonblock call might raise an exception and later
114
+ # code would pass the same data in, but the engine would think
115
+ # it had already written the data in. So for the time being
116
+ # (and since write blocking is quite rare), go ahead and actually
117
+ # block in write_nonblock.
118
+ def write_nonblock(data, *_)
119
+ write data
120
+ end
78
121
 
79
122
  def flush
80
123
  @socket.flush
81
124
  end
82
125
 
126
+ def read_and_drop(timeout = 1)
127
+ return :timeout unless IO.select([@socket], nil, nil, timeout)
128
+ case @socket.read_nonblock(1024, exception: false)
129
+ when nil
130
+ :eof
131
+ when :wait_readable
132
+ :eagain
133
+ else
134
+ :drop
135
+ end
136
+ end
137
+
138
+ def should_drop_bytes?
139
+ @engine.init? || !@engine.shutdown
140
+ end
141
+
83
142
  def close
84
- @socket.close
143
+ begin
144
+ # Read any drop any partially initialized sockets and any received bytes during shutdown.
145
+ # Don't let this socket hold this loop forever.
146
+ # If it can't send more packets within 1s, then give up.
147
+ return if [:timeout, :eof].include?(read_and_drop(1)) while should_drop_bytes?
148
+ rescue IOError, SystemCallError
149
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
150
+ # nothing
151
+ ensure
152
+ @socket.close
153
+ end
85
154
  end
86
155
 
87
156
  def peeraddr
@@ -108,21 +177,34 @@ module Puma
108
177
 
109
178
  class Context
110
179
  attr_accessor :verify_mode
180
+ attr_reader :no_tlsv1, :no_tlsv1_1
181
+
182
+ def initialize
183
+ @no_tlsv1 = false
184
+ @no_tlsv1_1 = false
185
+ end
111
186
 
112
187
  if defined?(JRUBY_VERSION)
113
188
  # jruby-specific Context properties: java uses a keystore and password pair rather than a cert/key pair
114
189
  attr_reader :keystore
115
190
  attr_accessor :keystore_pass
191
+ attr_accessor :ssl_cipher_list
116
192
 
117
193
  def keystore=(keystore)
118
194
  raise ArgumentError, "No such keystore file '#{keystore}'" unless File.exist? keystore
119
195
  @keystore = keystore
120
196
  end
197
+
198
+ def check
199
+ raise "Keystore not configured" unless @keystore
200
+ end
201
+
121
202
  else
122
203
  # non-jruby Context properties
123
204
  attr_reader :key
124
205
  attr_reader :cert
125
206
  attr_reader :ca
207
+ attr_accessor :ssl_cipher_filter
126
208
 
127
209
  def key=(key)
128
210
  raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
@@ -138,7 +220,25 @@ module Puma
138
220
  raise ArgumentError, "No such ca file '#{ca}'" unless File.exist? ca
139
221
  @ca = ca
140
222
  end
223
+
224
+ def check
225
+ raise "Key not configured" unless @key
226
+ raise "Cert not configured" unless @cert
227
+ end
141
228
  end
229
+
230
+ # disables TLSv1
231
+ def no_tlsv1=(tlsv1)
232
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1)
233
+ @no_tlsv1 = tlsv1
234
+ end
235
+
236
+ # disables TLSv1 and TLSv1.1. Overrides `#no_tlsv1=`
237
+ def no_tlsv1_1=(tlsv1_1)
238
+ raise ArgumentError, "Invalid value of no_tlsv1" unless ['true', 'false', true, false].include?(tlsv1_1)
239
+ @no_tlsv1_1 = tlsv1_1
240
+ end
241
+
142
242
  end
143
243
 
144
244
  VERIFY_NONE = 0
@@ -156,6 +256,7 @@ module Puma
156
256
  end
157
257
 
158
258
  def accept
259
+ @ctx.check
159
260
  io = @socket.accept
160
261
  engine = Engine.server @ctx
161
262
 
@@ -163,14 +264,19 @@ module Puma
163
264
  end
164
265
 
165
266
  def accept_nonblock
267
+ @ctx.check
166
268
  io = @socket.accept_nonblock
167
269
  engine = Engine.server @ctx
168
270
 
169
271
  Socket.new io, engine
170
272
  end
171
273
 
274
+ def addr
275
+ @socket.addr
276
+ end
277
+
172
278
  def close
173
- @socket.close
279
+ @socket.close unless @socket.closed? # closed? call is for Windows
174
280
  end
175
281
  end
176
282
  end
@@ -0,0 +1,76 @@
1
+ module Puma
2
+ module MiniSSL
3
+ class ContextBuilder
4
+ def initialize(params, events)
5
+ require 'puma/minissl'
6
+ MiniSSL.check
7
+
8
+ @params = params
9
+ @events = events
10
+ end
11
+
12
+ def context
13
+ ctx = MiniSSL::Context.new
14
+
15
+ if defined?(JRUBY_VERSION)
16
+ unless params['keystore']
17
+ events.error "Please specify the Java keystore via 'keystore='"
18
+ end
19
+
20
+ ctx.keystore = params['keystore']
21
+
22
+ unless params['keystore-pass']
23
+ events.error "Please specify the Java keystore password via 'keystore-pass='"
24
+ end
25
+
26
+ ctx.keystore_pass = params['keystore-pass']
27
+ ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
28
+ else
29
+ unless params['key']
30
+ events.error "Please specify the SSL key via 'key='"
31
+ end
32
+
33
+ ctx.key = params['key']
34
+
35
+ unless params['cert']
36
+ events.error "Please specify the SSL cert via 'cert='"
37
+ end
38
+
39
+ ctx.cert = params['cert']
40
+
41
+ if ['peer', 'force_peer'].include?(params['verify_mode'])
42
+ unless params['ca']
43
+ events.error "Please specify the SSL ca via 'ca='"
44
+ end
45
+ end
46
+
47
+ ctx.ca = params['ca'] if params['ca']
48
+ ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
49
+ end
50
+
51
+ ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
52
+ ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
53
+
54
+ if params['verify_mode']
55
+ ctx.verify_mode = case params['verify_mode']
56
+ when "peer"
57
+ MiniSSL::VERIFY_PEER
58
+ when "force_peer"
59
+ MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
60
+ when "none"
61
+ MiniSSL::VERIFY_NONE
62
+ else
63
+ events.error "Please specify a valid verify_mode="
64
+ MiniSSL::VERIFY_NONE
65
+ end
66
+ end
67
+
68
+ ctx
69
+ end
70
+
71
+ private
72
+
73
+ attr_reader :params, :events
74
+ end
75
+ end
76
+ end
@@ -1,42 +1,37 @@
1
- module Puma
1
+ # frozen_string_literal: true
2
2
 
3
+ module Puma
3
4
  # Provides an IO-like object that always appears to contain no data.
4
5
  # Used as the value for rack.input when the request has no body.
5
6
  #
6
7
  class NullIO
7
- # Always returns nil
8
- #
9
8
  def gets
10
9
  nil
11
10
  end
12
11
 
13
- # Never yields
14
- #
15
12
  def each
16
13
  end
17
14
 
18
- # Mimics IO#read with no data
15
+ # Mimics IO#read with no data.
19
16
  #
20
- def read(count=nil,buffer=nil)
21
- (count && count > 0) ? nil : ""
17
+ def read(count = nil, _buffer = nil)
18
+ count && count > 0 ? nil : ""
22
19
  end
23
20
 
24
- # Does nothing
25
- #
26
21
  def rewind
27
22
  end
28
23
 
29
- # Does nothing
30
- #
31
24
  def close
32
25
  end
33
26
 
34
- # Always zero
35
- #
36
27
  def size
37
28
  0
38
29
  end
39
30
 
31
+ def eof?
32
+ true
33
+ end
34
+
40
35
  def sync=(v)
41
36
  end
42
37
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puma
2
4
  class UnknownPlugin < RuntimeError; end
3
5
 
@@ -8,7 +10,7 @@ module Puma
8
10
 
9
11
  def create(name)
10
12
  if cls = Plugins.find(name)
11
- plugin = cls.new(Plugin)
13
+ plugin = cls.new
12
14
  @instances << plugin
13
15
  return plugin
14
16
  end
@@ -28,6 +30,7 @@ module Puma
28
30
  class PluginRegistry
29
31
  def initialize
30
32
  @plugins = {}
33
+ @background = []
31
34
  end
32
35
 
33
36
  def register(name, cls)
@@ -53,13 +56,39 @@ module Puma
53
56
 
54
57
  raise UnknownPlugin, "file failed to register a plugin"
55
58
  end
59
+
60
+ def add_background(blk)
61
+ @background << blk
62
+ end
63
+
64
+ def fire_background
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plugin background #{i}"
68
+ b.call
69
+ end
70
+ end
71
+ end
56
72
  end
57
73
 
58
74
  Plugins = PluginRegistry.new
59
75
 
60
76
  class Plugin
77
+ # Matches
78
+ # "C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb:3:in `<top (required)>'"
79
+ # AS
80
+ # C:/Ruby22/lib/ruby/gems/2.2.0/gems/puma-3.0.1/lib/puma/plugin/tmp_restart.rb
81
+ CALLER_FILE = /
82
+ \A # start of string
83
+ .+ # file path (one or more characters)
84
+ (?= # stop previous match when
85
+ :\d+ # a colon is followed by one or more digits
86
+ :in # followed by a colon followed by in
87
+ )
88
+ /x
89
+
61
90
  def self.extract_name(ary)
62
- path = ary.first.split(":").first
91
+ path = ary.first[CALLER_FILE]
63
92
 
64
93
  m = %r!puma/plugin/([^/]*)\.rb$!.match(path)
65
94
  return m[1]
@@ -75,17 +104,8 @@ module Puma
75
104
  Plugins.register name, cls
76
105
  end
77
106
 
78
- def initialize(loader)
79
- @loader = loader
80
- end
81
-
82
107
  def in_background(&blk)
83
- Thread.new(&blk)
84
- end
85
-
86
- def workers_supported?
87
- return false if Puma.jruby? || Puma.windows?
88
- true
108
+ Plugins.add_background blk
89
109
  end
90
110
  end
91
111
  end
@@ -1,23 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puma/plugin'
2
4
 
3
5
  Puma::Plugin.create do
4
6
  def start(launcher)
5
7
  path = File.join("tmp", "restart.txt")
6
8
 
7
- File.write path, ""
9
+ orig = nil
8
10
 
9
- orig = File.stat(path).mtime
11
+ # If we can't write to the path, then just don't bother with this plugin
12
+ begin
13
+ File.write(path, "") unless File.exist?(path)
14
+ orig = File.stat(path).mtime
15
+ rescue SystemCallError
16
+ return
17
+ end
10
18
 
11
19
  in_background do
12
20
  while true
13
21
  sleep 2
14
22
 
15
- if File.stat(path).mtime > orig
16
- launcher.restart
17
- break
23
+ begin
24
+ mtime = File.stat(path).mtime
25
+ rescue SystemCallError
26
+ # If the file has disappeared, assume that means don't restart
27
+ else
28
+ if mtime > orig
29
+ launcher.restart
30
+ break
31
+ end
18
32
  end
19
33
  end
20
34
  end
21
35
  end
22
36
  end
23
-