gitlab-puma 4.3.1.gitlab.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1537 -0
  3. data/LICENSE +26 -0
  4. data/README.md +291 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +37 -0
  9. data/docs/deployment.md +111 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/nginx.md +80 -0
  14. data/docs/plugins.md +38 -0
  15. data/docs/restart.md +41 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +290 -0
  18. data/docs/tcp_mode.md +96 -0
  19. data/ext/puma_http11/PumaHttp11Service.java +19 -0
  20. data/ext/puma_http11/ext_help.h +15 -0
  21. data/ext/puma_http11/extconf.rb +28 -0
  22. data/ext/puma_http11/http11_parser.c +1044 -0
  23. data/ext/puma_http11/http11_parser.h +65 -0
  24. data/ext/puma_http11/http11_parser.java.rl +145 -0
  25. data/ext/puma_http11/http11_parser.rl +147 -0
  26. data/ext/puma_http11/http11_parser_common.rl +54 -0
  27. data/ext/puma_http11/io_buffer.c +155 -0
  28. data/ext/puma_http11/mini_ssl.c +553 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11.java +226 -0
  30. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  31. data/ext/puma_http11/org/jruby/puma/IOBuffer.java +72 -0
  32. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
  33. data/ext/puma_http11/puma_http11.c +502 -0
  34. data/lib/puma.rb +31 -0
  35. data/lib/puma/accept_nonblock.rb +29 -0
  36. data/lib/puma/app/status.rb +80 -0
  37. data/lib/puma/binder.rb +385 -0
  38. data/lib/puma/cli.rb +239 -0
  39. data/lib/puma/client.rb +494 -0
  40. data/lib/puma/cluster.rb +554 -0
  41. data/lib/puma/commonlogger.rb +108 -0
  42. data/lib/puma/configuration.rb +362 -0
  43. data/lib/puma/const.rb +242 -0
  44. data/lib/puma/control_cli.rb +289 -0
  45. data/lib/puma/detect.rb +15 -0
  46. data/lib/puma/dsl.rb +740 -0
  47. data/lib/puma/events.rb +156 -0
  48. data/lib/puma/io_buffer.rb +4 -0
  49. data/lib/puma/jruby_restart.rb +84 -0
  50. data/lib/puma/launcher.rb +475 -0
  51. data/lib/puma/minissl.rb +278 -0
  52. data/lib/puma/minissl/context_builder.rb +76 -0
  53. data/lib/puma/null_io.rb +44 -0
  54. data/lib/puma/plugin.rb +120 -0
  55. data/lib/puma/plugin/tmp_restart.rb +36 -0
  56. data/lib/puma/rack/builder.rb +301 -0
  57. data/lib/puma/rack/urlmap.rb +93 -0
  58. data/lib/puma/rack_default.rb +9 -0
  59. data/lib/puma/reactor.rb +400 -0
  60. data/lib/puma/runner.rb +192 -0
  61. data/lib/puma/server.rb +1053 -0
  62. data/lib/puma/single.rb +123 -0
  63. data/lib/puma/state_file.rb +31 -0
  64. data/lib/puma/tcp_logger.rb +41 -0
  65. data/lib/puma/thread_pool.rb +348 -0
  66. data/lib/puma/util.rb +124 -0
  67. data/lib/rack/handler/puma.rb +115 -0
  68. data/tools/docker/Dockerfile +16 -0
  69. data/tools/jungle/README.md +19 -0
  70. data/tools/jungle/init.d/README.md +61 -0
  71. data/tools/jungle/init.d/puma +421 -0
  72. data/tools/jungle/init.d/run-puma +18 -0
  73. data/tools/jungle/rc.d/README.md +74 -0
  74. data/tools/jungle/rc.d/puma +61 -0
  75. data/tools/jungle/rc.d/puma.conf +10 -0
  76. data/tools/jungle/upstart/README.md +61 -0
  77. data/tools/jungle/upstart/puma-manager.conf +31 -0
  78. data/tools/jungle/upstart/puma.conf +69 -0
  79. data/tools/trickletest.rb +44 -0
  80. metadata +147 -0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/plugin'
4
+
5
+ Puma::Plugin.create do
6
+ def start(launcher)
7
+ path = File.join("tmp", "restart.txt")
8
+
9
+ orig = nil
10
+
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
18
+
19
+ in_background do
20
+ while true
21
+ sleep 2
22
+
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
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,301 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ end
5
+
6
+ module Puma::Rack
7
+ class Options
8
+ def parse!(args)
9
+ options = {}
10
+ opt_parser = OptionParser.new("", 24, ' ') do |opts|
11
+ opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
12
+
13
+ opts.separator ""
14
+ opts.separator "Ruby options:"
15
+
16
+ lineno = 1
17
+ opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
18
+ eval line, TOPLEVEL_BINDING, "-e", lineno
19
+ lineno += 1
20
+ }
21
+
22
+ opts.on("-b", "--builder BUILDER_LINE", "evaluate a BUILDER_LINE of code as a builder script") { |line|
23
+ options[:builder] = line
24
+ }
25
+
26
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
27
+ options[:debug] = true
28
+ }
29
+ opts.on("-w", "--warn", "turn warnings on for your script") {
30
+ options[:warn] = true
31
+ }
32
+ opts.on("-q", "--quiet", "turn off logging") {
33
+ options[:quiet] = true
34
+ }
35
+
36
+ opts.on("-I", "--include PATH",
37
+ "specify $LOAD_PATH (may be used more than once)") { |path|
38
+ (options[:include] ||= []).concat(path.split(":"))
39
+ }
40
+
41
+ opts.on("-r", "--require LIBRARY",
42
+ "require the library, before executing your script") { |library|
43
+ options[:require] = library
44
+ }
45
+
46
+ opts.separator ""
47
+ opts.separator "Rack options:"
48
+ opts.on("-s", "--server SERVER", "serve using SERVER (thin/puma/webrick/mongrel)") { |s|
49
+ options[:server] = s
50
+ }
51
+
52
+ opts.on("-o", "--host HOST", "listen on HOST (default: localhost)") { |host|
53
+ options[:Host] = host
54
+ }
55
+
56
+ opts.on("-p", "--port PORT", "use PORT (default: 9292)") { |port|
57
+ options[:Port] = port
58
+ }
59
+
60
+ opts.on("-O", "--option NAME[=VALUE]", "pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} -s SERVER -h' to get a list of options for SERVER") { |name|
61
+ name, value = name.split('=', 2)
62
+ value = true if value.nil?
63
+ options[name.to_sym] = value
64
+ }
65
+
66
+ opts.on("-E", "--env ENVIRONMENT", "use ENVIRONMENT for defaults (default: development)") { |e|
67
+ options[:environment] = e
68
+ }
69
+
70
+ opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
71
+ options[:daemonize] = d ? true : false
72
+ }
73
+
74
+ opts.on("-P", "--pid FILE", "file to store PID") { |f|
75
+ options[:pid] = ::File.expand_path(f)
76
+ }
77
+
78
+ opts.separator ""
79
+ opts.separator "Common options:"
80
+
81
+ opts.on_tail("-h", "-?", "--help", "Show this message") do
82
+ puts opts
83
+ puts handler_opts(options)
84
+
85
+ exit
86
+ end
87
+
88
+ opts.on_tail("--version", "Show version") do
89
+ puts "Rack #{Rack.version} (Release: #{Rack.release})"
90
+ exit
91
+ end
92
+ end
93
+
94
+ begin
95
+ opt_parser.parse! args
96
+ rescue OptionParser::InvalidOption => e
97
+ warn e.message
98
+ abort opt_parser.to_s
99
+ end
100
+
101
+ options[:config] = args.last if args.last
102
+ options
103
+ end
104
+
105
+ def handler_opts(options)
106
+ begin
107
+ info = []
108
+ server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
109
+ if server && server.respond_to?(:valid_options)
110
+ info << ""
111
+ info << "Server-specific options for #{server.name}:"
112
+
113
+ has_options = false
114
+ server.valid_options.each do |name, description|
115
+ next if name.to_s =~ /^(Host|Port)[^a-zA-Z]/ # ignore handler's host and port options, we do our own.
116
+
117
+ info << " -O %-21s %s" % [name, description]
118
+ has_options = true
119
+ end
120
+ return "" if !has_options
121
+ end
122
+ info.join("\n")
123
+ rescue NameError
124
+ return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
125
+ end
126
+ end
127
+ end
128
+
129
+ # Rack::Builder implements a small DSL to iteratively construct Rack
130
+ # applications.
131
+ #
132
+ # Example:
133
+ #
134
+ # require 'rack/lobster'
135
+ # app = Rack::Builder.new do
136
+ # use Rack::CommonLogger
137
+ # use Rack::ShowExceptions
138
+ # map "/lobster" do
139
+ # use Rack::Lint
140
+ # run Rack::Lobster.new
141
+ # end
142
+ # end
143
+ #
144
+ # run app
145
+ #
146
+ # Or
147
+ #
148
+ # app = Rack::Builder.app do
149
+ # use Rack::CommonLogger
150
+ # run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['OK']] }
151
+ # end
152
+ #
153
+ # run app
154
+ #
155
+ # +use+ adds middleware to the stack, +run+ dispatches to an application.
156
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
157
+
158
+ class Builder
159
+ def self.parse_file(config, opts = Options.new)
160
+ options = {}
161
+ if config =~ /\.ru$/
162
+ cfgfile = ::File.read(config)
163
+ if cfgfile[/^#\\(.*)/] && opts
164
+ options = opts.parse! $1.split(/\s+/)
165
+ end
166
+ cfgfile.sub!(/^__END__\n.*\Z/m, '')
167
+ app = new_from_string cfgfile, config
168
+ else
169
+ require config
170
+ app = Object.const_get(::File.basename(config, '.rb').capitalize)
171
+ end
172
+ return app, options
173
+ end
174
+
175
+ def self.new_from_string(builder_script, file="(rackup)")
176
+ eval "Puma::Rack::Builder.new {\n" + builder_script + "\n}.to_app",
177
+ TOPLEVEL_BINDING, file, 0
178
+ end
179
+
180
+ def initialize(default_app = nil,&block)
181
+ @use, @map, @run, @warmup = [], nil, default_app, nil
182
+
183
+ # Conditionally load rack now, so that any rack middlewares,
184
+ # etc are available.
185
+ begin
186
+ require 'rack'
187
+ rescue LoadError
188
+ end
189
+
190
+ instance_eval(&block) if block_given?
191
+ end
192
+
193
+ def self.app(default_app = nil, &block)
194
+ self.new(default_app, &block).to_app
195
+ end
196
+
197
+ # Specifies middleware to use in a stack.
198
+ #
199
+ # class Middleware
200
+ # def initialize(app)
201
+ # @app = app
202
+ # end
203
+ #
204
+ # def call(env)
205
+ # env["rack.some_header"] = "setting an example"
206
+ # @app.call(env)
207
+ # end
208
+ # end
209
+ #
210
+ # use Middleware
211
+ # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
212
+ #
213
+ # All requests through to this application will first be processed by the middleware class.
214
+ # The +call+ method in this example sets an additional environment key which then can be
215
+ # referenced in the application if required.
216
+ def use(middleware, *args, &block)
217
+ if @map
218
+ mapping, @map = @map, nil
219
+ @use << proc { |app| generate_map app, mapping }
220
+ end
221
+ @use << proc { |app| middleware.new(app, *args, &block) }
222
+ end
223
+
224
+ # Takes an argument that is an object that responds to #call and returns a Rack response.
225
+ # The simplest form of this is a lambda object:
226
+ #
227
+ # run lambda { |env| [200, { "Content-Type" => "text/plain" }, ["OK"]] }
228
+ #
229
+ # However this could also be a class:
230
+ #
231
+ # class Heartbeat
232
+ # def self.call(env)
233
+ # [200, { "Content-Type" => "text/plain" }, ["OK"]]
234
+ # end
235
+ # end
236
+ #
237
+ # run Heartbeat
238
+ def run(app)
239
+ @run = app
240
+ end
241
+
242
+ # Takes a lambda or block that is used to warm-up the application.
243
+ #
244
+ # warmup do |app|
245
+ # client = Rack::MockRequest.new(app)
246
+ # client.get('/')
247
+ # end
248
+ #
249
+ # use SomeMiddleware
250
+ # run MyApp
251
+ def warmup(prc=nil, &block)
252
+ @warmup = prc || block
253
+ end
254
+
255
+ # Creates a route within the application.
256
+ #
257
+ # Rack::Builder.app do
258
+ # map '/' do
259
+ # run Heartbeat
260
+ # end
261
+ # end
262
+ #
263
+ # The +use+ method can also be used here to specify middleware to run under a specific path:
264
+ #
265
+ # Rack::Builder.app do
266
+ # map '/' do
267
+ # use Middleware
268
+ # run Heartbeat
269
+ # end
270
+ # end
271
+ #
272
+ # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
273
+ #
274
+ def map(path, &block)
275
+ @map ||= {}
276
+ @map[path] = block
277
+ end
278
+
279
+ def to_app
280
+ app = @map ? generate_map(@run, @map) : @run
281
+ fail "missing run or map statement" unless app
282
+ app = @use.reverse.inject(app) { |a,e| e[a] }
283
+ @warmup.call(app) if @warmup
284
+ app
285
+ end
286
+
287
+ def call(env)
288
+ to_app.call(env)
289
+ end
290
+
291
+ private
292
+
293
+ def generate_map(default_app, mapping)
294
+ require 'puma/rack/urlmap'
295
+
296
+ mapped = default_app ? {'/' => default_app} : {}
297
+ mapping.each { |r,b| mapped[r] = self.class.new(default_app, &b).to_app }
298
+ URLMap.new(mapped)
299
+ end
300
+ end
301
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma::Rack
4
+ # Rack::URLMap takes a hash mapping urls or paths to apps, and
5
+ # dispatches accordingly. Support for HTTP/1.1 host names exists if
6
+ # the URLs start with <tt>http://</tt> or <tt>https://</tt>.
7
+ #
8
+ # URLMap modifies the SCRIPT_NAME and PATH_INFO such that the part
9
+ # relevant for dispatch is in the SCRIPT_NAME, and the rest in the
10
+ # PATH_INFO. This should be taken care of when you need to
11
+ # reconstruct the URL in order to create links.
12
+ #
13
+ # URLMap dispatches in such a way that the longest paths are tried
14
+ # first, since they are most specific.
15
+
16
+ class URLMap
17
+ NEGATIVE_INFINITY = -1.0 / 0.0
18
+ INFINITY = 1.0 / 0.0
19
+
20
+ def initialize(map = {})
21
+ remap(map)
22
+ end
23
+
24
+ def remap(map)
25
+ @mapping = map.map { |location, app|
26
+ if location =~ %r{\Ahttps?://(.*?)(/.*)}
27
+ host, location = $1, $2
28
+ else
29
+ host = nil
30
+ end
31
+
32
+ unless location[0] == ?/
33
+ raise ArgumentError, "paths need to start with /"
34
+ end
35
+
36
+ location = location.chomp('/')
37
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
38
+
39
+ [host, location, match, app]
40
+ }.sort_by do |(host, location, _, _)|
41
+ [host ? -host.size : INFINITY, -location.size]
42
+ end
43
+ end
44
+
45
+ def call(env)
46
+ path = env['PATH_INFO']
47
+ script_name = env['SCRIPT_NAME']
48
+ http_host = env['HTTP_HOST']
49
+ server_name = env['SERVER_NAME']
50
+ server_port = env['SERVER_PORT']
51
+
52
+ is_same_server = casecmp?(http_host, server_name) ||
53
+ casecmp?(http_host, "#{server_name}:#{server_port}")
54
+
55
+ @mapping.each do |host, location, match, app|
56
+ unless casecmp?(http_host, host) \
57
+ || casecmp?(server_name, host) \
58
+ || (!host && is_same_server)
59
+ next
60
+ end
61
+
62
+ next unless m = match.match(path.to_s)
63
+
64
+ rest = m[1]
65
+ next unless !rest || rest.empty? || rest[0] == ?/
66
+
67
+ env['SCRIPT_NAME'] = (script_name + location)
68
+ env['PATH_INFO'] = rest
69
+
70
+ return app.call(env)
71
+ end
72
+
73
+ [404, {'Content-Type' => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path}"]]
74
+
75
+ ensure
76
+ env['PATH_INFO'] = path
77
+ env['SCRIPT_NAME'] = script_name
78
+ end
79
+
80
+ private
81
+ def casecmp?(v1, v2)
82
+ # if both nil, or they're the same string
83
+ return true if v1 == v2
84
+
85
+ # if either are nil... (but they're not the same)
86
+ return false if v1.nil?
87
+ return false if v2.nil?
88
+
89
+ # otherwise check they're not case-insensitive the same
90
+ v1.casecmp(v2).zero?
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/handler/puma'
4
+
5
+ module Rack::Handler
6
+ def self.default(options = {})
7
+ Rack::Handler::Puma
8
+ end
9
+ end
@@ -0,0 +1,400 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/util'
4
+ require 'puma/minissl'
5
+
6
+ require 'nio'
7
+
8
+ module Puma
9
+ # Internal Docs, Not a public interface.
10
+ #
11
+ # The Reactor object is responsible for ensuring that a request has been
12
+ # completely received before it starts to be processed. This may be known as read buffering.
13
+ # If read buffering is not done, and no other read buffering is performed (such as by an application server
14
+ # such as nginx) then the application would be subject to a slow client attack.
15
+ #
16
+ # Each Puma "worker" process has its own Reactor. For example if you start puma with `$ puma -w 5` then
17
+ # it will have 5 workers and each worker will have it's own reactor.
18
+ #
19
+ # For a graphical representation of how the reactor works see [architecture.md](https://github.com/puma/puma/blob/master/docs/architecture.md#connection-pipeline).
20
+ #
21
+ # ## Reactor Flow
22
+ #
23
+ # A connection comes into a `Puma::Server` instance, it is then passed to a `Puma::Reactor` instance,
24
+ # which stores it in an array and waits for any of the connections to be ready for reading.
25
+ #
26
+ # The waiting/wake up is performed with nio4r, which will use the appropriate backend (libev, Java NIO or
27
+ # just plain IO#select). The call to `NIO::Selector#select` will "wake up" and
28
+ # return the references to any objects that caused it to "wake". The reactor
29
+ # then loops through each of these request objects, and sees if they're complete. If they
30
+ # have a full header and body then the reactor passes the request to a thread pool.
31
+ # Once in a thread pool, a "worker thread" can run the the application's Ruby code against the request.
32
+ #
33
+ # If the request is not complete, then it stays in the array, and the next time any
34
+ # data is written to that socket reference, then the loop is woken up and it is checked for completeness again.
35
+ #
36
+ # A detailed example is given in the docs for `run_internal` which is where the bulk
37
+ # of this logic lives.
38
+ class Reactor
39
+ DefaultSleepFor = 5
40
+
41
+ # Creates an instance of Puma::Reactor
42
+ #
43
+ # The `server` argument is an instance of `Puma::Server`
44
+ # that is used to write a response for "low level errors"
45
+ # when there is an exception inside of the reactor.
46
+ #
47
+ # The `app_pool` is an instance of `Puma::ThreadPool`.
48
+ # Once a request is fully formed (header and body are received)
49
+ # it will be passed to the `app_pool`.
50
+ def initialize(server, app_pool)
51
+ @server = server
52
+ @events = server.events
53
+ @app_pool = app_pool
54
+
55
+ @selector = NIO::Selector.new
56
+
57
+ @mutex = Mutex.new
58
+
59
+ # Read / Write pipes to wake up internal while loop
60
+ @ready, @trigger = Puma::Util.pipe
61
+ @input = []
62
+ @sleep_for = DefaultSleepFor
63
+ @timeouts = []
64
+
65
+ mon = @selector.register(@ready, :r)
66
+ mon.value = @ready
67
+
68
+ @monitors = [mon]
69
+ end
70
+
71
+ private
72
+
73
+ # Until a request is added via the `add` method this method will internally
74
+ # loop, waiting on the `sockets` array objects. The only object in this
75
+ # array at first is the `@ready` IO object, which is the read end of a pipe
76
+ # connected to `@trigger` object. When `@trigger` is written to, then the loop
77
+ # will break on `NIO::Selector#select` and return an array.
78
+ #
79
+ # ## When a request is added:
80
+ #
81
+ # When the `add` method is called, an instance of `Puma::Client` is added to the `@input` array.
82
+ # Next the `@ready` pipe is "woken" by writing a string of `"*"` to `@trigger`.
83
+ #
84
+ # When that happens, the internal loop stops blocking at `NIO::Selector#select` and returns a reference
85
+ # to whatever "woke" it up. On the very first loop, the only thing in `sockets` is `@ready`.
86
+ # When `@trigger` is written-to, the loop "wakes" and the `ready`
87
+ # variable returns an array of arrays that looks like `[[#<IO:fd 10>], [], []]` where the
88
+ # first IO object is the `@ready` object. This first array `[#<IO:fd 10>]`
89
+ # is saved as a `reads` variable.
90
+ #
91
+ # The `reads` variable is iterated through. In the case that the object
92
+ # is the same as the `@ready` input pipe, then we know that there was a `trigger` event.
93
+ #
94
+ # If there was a trigger event, then one byte of `@ready` is read into memory. In the case of the first request,
95
+ # the reactor sees that it's a `"*"` value and the reactor adds the contents of `@input` into the `sockets` array.
96
+ # The while then loop continues to iterate again, but now the `sockets` array contains a `Puma::Client` instance in addition
97
+ # to the `@ready` IO object. For example: `[#<IO:fd 10>, #<Puma::Client:0x3fdc1103bee8 @ready=false>]`.
98
+ #
99
+ # Since the `Puma::Client` in this example has data that has not been read yet,
100
+ # the `NIO::Selector#select` is immediately able to "wake" and read from the `Puma::Client`. At this point the
101
+ # `ready` output looks like this: `[[#<Puma::Client:0x3fdc1103bee8 @ready=false>], [], []]`.
102
+ #
103
+ # Each element in the first entry is iterated over. The `Puma::Client` object is not
104
+ # the `@ready` pipe, so the reactor checks to see if it has the full header and body with
105
+ # the `Puma::Client#try_to_finish` method. If the full request has been sent,
106
+ # then the request is passed off to the `@app_pool` thread pool so that a "worker thread"
107
+ # can pick up the request and begin to execute application logic. This is done
108
+ # via `@app_pool << c`. The `Puma::Client` is then removed from the `sockets` array.
109
+ #
110
+ # If the request body is not present then nothing will happen, and the loop will iterate
111
+ # again. When the client sends more data to the socket the `Puma::Client` object will
112
+ # wake up the `NIO::Selector#select` and it can again be checked to see if it's ready to be
113
+ # passed to the thread pool.
114
+ #
115
+ # ## Time Out Case
116
+ #
117
+ # In addition to being woken via a write to one of the sockets the `NIO::Selector#select` will
118
+ # periodically "time out" of the sleep. One of the functions of this is to check for
119
+ # any requests that have "timed out". At the end of the loop it's checked to see if
120
+ # the first element in the `@timeout` array has exceed its allowed time. If so,
121
+ # the client object is removed from the timeout array, a 408 response is written.
122
+ # Then its connection is closed, and the object is removed from the `sockets` array
123
+ # that watches for new data.
124
+ #
125
+ # This behavior loops until all the objects that have timed out have been removed.
126
+ #
127
+ # Once all the timeouts have been processed, the next duration of the `NIO::Selector#select` sleep
128
+ # will be set to be equal to the amount of time it will take for the next timeout to occur.
129
+ # This calculation happens in `calculate_sleep`.
130
+ def run_internal
131
+ monitors = @monitors
132
+ selector = @selector
133
+
134
+ while true
135
+ begin
136
+ ready = selector.select @sleep_for
137
+ rescue IOError => e
138
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
139
+ if monitors.any? { |mon| mon.value.closed? }
140
+ STDERR.puts "Error in select: #{e.message} (#{e.class})"
141
+ STDERR.puts e.backtrace
142
+
143
+ monitors.reject! do |mon|
144
+ if mon.value.closed?
145
+ selector.deregister mon.value
146
+ true
147
+ end
148
+ end
149
+
150
+ retry
151
+ else
152
+ raise
153
+ end
154
+ end
155
+
156
+ if ready
157
+ ready.each do |mon|
158
+ if mon.value == @ready
159
+ @mutex.synchronize do
160
+ case @ready.read(1)
161
+ when "*"
162
+ @input.each do |c|
163
+ mon = nil
164
+ begin
165
+ begin
166
+ mon = selector.register(c, :r)
167
+ rescue ArgumentError
168
+ # There is a bug where we seem to be registering an already registered
169
+ # client. This code deals with this situation but I wish we didn't have to.
170
+ monitors.delete_if { |submon| submon.value.to_io == c.to_io }
171
+ selector.deregister(c)
172
+ mon = selector.register(c, :r)
173
+ end
174
+ rescue IOError
175
+ # Means that the io is closed, so we should ignore this request
176
+ # entirely
177
+ else
178
+ mon.value = c
179
+ @timeouts << mon if c.timeout_at
180
+ monitors << mon
181
+ end
182
+ end
183
+ @input.clear
184
+
185
+ @timeouts.sort! { |a,b| a.value.timeout_at <=> b.value.timeout_at }
186
+ calculate_sleep
187
+ when "c"
188
+ monitors.reject! do |submon|
189
+ if submon.value == @ready
190
+ false
191
+ else
192
+ submon.value.close
193
+ begin
194
+ selector.deregister submon.value
195
+ rescue IOError
196
+ # nio4r on jruby seems to throw an IOError here if the IO is closed, so
197
+ # we need to swallow it.
198
+ end
199
+ true
200
+ end
201
+ end
202
+ when "!"
203
+ return
204
+ end
205
+ end
206
+ else
207
+ c = mon.value
208
+
209
+ # We have to be sure to remove it from the timeout
210
+ # list or we'll accidentally close the socket when
211
+ # it's in use!
212
+ if c.timeout_at
213
+ @mutex.synchronize do
214
+ @timeouts.delete mon
215
+ end
216
+ end
217
+
218
+ begin
219
+ if c.try_to_finish
220
+ @app_pool << c
221
+ clear_monitor mon
222
+ end
223
+
224
+ # Don't report these to the lowlevel_error handler, otherwise
225
+ # will be flooding them with errors when persistent connections
226
+ # are closed.
227
+ rescue ConnectionError
228
+ c.write_error(500)
229
+ c.close
230
+
231
+ clear_monitor mon
232
+
233
+ # SSL handshake failure
234
+ rescue MiniSSL::SSLError => e
235
+ @server.lowlevel_error(e, c.env)
236
+
237
+ ssl_socket = c.io
238
+ begin
239
+ addr = ssl_socket.peeraddr.last
240
+ # EINVAL can happen when browser closes socket w/security exception
241
+ rescue IOError, Errno::EINVAL
242
+ addr = "<unknown>"
243
+ end
244
+
245
+ cert = ssl_socket.peercert
246
+
247
+ c.close
248
+ clear_monitor mon
249
+
250
+ @events.ssl_error @server, addr, cert, e
251
+
252
+ # The client doesn't know HTTP well
253
+ rescue HttpParserError => e
254
+ @server.lowlevel_error(e, c.env)
255
+
256
+ c.write_error(400)
257
+ c.close
258
+
259
+ clear_monitor mon
260
+
261
+ @events.parse_error @server, c.env, e
262
+ rescue StandardError => e
263
+ @server.lowlevel_error(e, c.env)
264
+
265
+ c.write_error(500)
266
+ c.close
267
+
268
+ clear_monitor mon
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ unless @timeouts.empty?
275
+ @mutex.synchronize do
276
+ now = Time.now
277
+
278
+ while @timeouts.first.value.timeout_at < now
279
+ mon = @timeouts.shift
280
+ c = mon.value
281
+ c.write_error(408) if c.in_data_phase
282
+ c.close
283
+
284
+ clear_monitor mon
285
+
286
+ break if @timeouts.empty?
287
+ end
288
+
289
+ calculate_sleep
290
+ end
291
+ end
292
+ end
293
+ end
294
+
295
+ def clear_monitor(mon)
296
+ @selector.deregister mon.value
297
+ @monitors.delete mon
298
+ end
299
+
300
+ public
301
+
302
+ def run
303
+ run_internal
304
+ ensure
305
+ @trigger.close
306
+ @ready.close
307
+ end
308
+
309
+ def run_in_thread
310
+ @thread = Thread.new do
311
+ Puma.set_thread_name "reactor"
312
+ begin
313
+ run_internal
314
+ rescue StandardError => e
315
+ STDERR.puts "Error in reactor loop escaped: #{e.message} (#{e.class})"
316
+ STDERR.puts e.backtrace
317
+ retry
318
+ ensure
319
+ @trigger.close
320
+ @ready.close
321
+ end
322
+ end
323
+ end
324
+
325
+ # The `calculate_sleep` sets the value that the `NIO::Selector#select` will
326
+ # sleep for in the main reactor loop when no sockets are being written to.
327
+ #
328
+ # The values kept in `@timeouts` are sorted so that the first timeout
329
+ # comes first in the array. When there are no timeouts the default timeout is used.
330
+ #
331
+ # Otherwise a sleep value is set that is the same as the amount of time it
332
+ # would take for the first element to time out.
333
+ #
334
+ # If that value is in the past, then a sleep value of zero is used.
335
+ def calculate_sleep
336
+ if @timeouts.empty?
337
+ @sleep_for = DefaultSleepFor
338
+ else
339
+ diff = @timeouts.first.value.timeout_at.to_f - Time.now.to_f
340
+
341
+ if diff < 0.0
342
+ @sleep_for = 0
343
+ else
344
+ @sleep_for = diff
345
+ end
346
+ end
347
+ end
348
+
349
+ # This method adds a connection to the reactor
350
+ #
351
+ # Typically called by `Puma::Server` the value passed in
352
+ # is usually a `Puma::Client` object that responds like an IO
353
+ # object.
354
+ #
355
+ # The main body of the reactor loop is in `run_internal` and it
356
+ # will sleep on `NIO::Selector#select`. When a new connection is added to the
357
+ # reactor it cannot be added directly to the `sockets` array, because
358
+ # the `NIO::Selector#select` will not be watching for it yet.
359
+ #
360
+ # Instead what needs to happen is that `NIO::Selector#select` needs to be woken up,
361
+ # the contents of `@input` added to the `sockets` array, and then
362
+ # another call to `NIO::Selector#select` needs to happen. Since the `Puma::Client`
363
+ # object can be read immediately, it does not block, but instead returns
364
+ # right away.
365
+ #
366
+ # This behavior is accomplished by writing to `@trigger` which wakes up
367
+ # the `NIO::Selector#select` and then there is logic to detect the value of `*`,
368
+ # pull the contents from `@input` and add them to the sockets array.
369
+ #
370
+ # If the object passed in has a timeout value in `timeout_at` then
371
+ # it is added to a `@timeouts` array. This array is then re-arranged
372
+ # so that the first element to timeout will be at the front of the
373
+ # array. Then a value to sleep for is derived in the call to `calculate_sleep`
374
+ def add(c)
375
+ @mutex.synchronize do
376
+ @input << c
377
+ @trigger << "*"
378
+ end
379
+ end
380
+
381
+ # Close all watched sockets and clear them from being watched
382
+ def clear!
383
+ begin
384
+ @trigger << "c"
385
+ rescue IOError
386
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
387
+ end
388
+ end
389
+
390
+ def shutdown
391
+ begin
392
+ @trigger << "!"
393
+ rescue IOError
394
+ Thread.current.purge_interrupt_queue if Thread.current.respond_to? :purge_interrupt_queue
395
+ end
396
+
397
+ @thread.join
398
+ end
399
+ end
400
+ end