piesync-puma 3.12.6

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