puma 2.2.0 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +3334 -0
  3. data/LICENSE +23 -20
  4. data/README.md +387 -100
  5. data/bin/puma-wild +25 -0
  6. data/bin/pumactl +1 -1
  7. data/docs/5.0-Upgrade.md +98 -0
  8. data/docs/6.0-Upgrade.md +56 -0
  9. data/docs/7.0-Upgrade.md +52 -0
  10. data/docs/8.0-Upgrade.md +100 -0
  11. data/docs/architecture.md +74 -0
  12. data/docs/compile_options.md +55 -0
  13. data/docs/deployment.md +137 -0
  14. data/docs/fork_worker.md +41 -0
  15. data/docs/grpc.md +62 -0
  16. data/docs/images/favicon.svg +1 -0
  17. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  18. data/docs/images/puma-connection-flow.png +0 -0
  19. data/docs/images/puma-general-arch.png +0 -0
  20. data/docs/images/running-puma.svg +1 -0
  21. data/docs/images/standard-logo.svg +1 -0
  22. data/docs/java_options.md +54 -0
  23. data/docs/jungle/README.md +9 -0
  24. data/docs/jungle/rc.d/README.md +74 -0
  25. data/docs/jungle/rc.d/puma +61 -0
  26. data/docs/jungle/rc.d/puma.conf +10 -0
  27. data/docs/kubernetes.md +73 -0
  28. data/docs/nginx.md +5 -5
  29. data/docs/plugins.md +42 -0
  30. data/docs/rails_dev_mode.md +28 -0
  31. data/docs/restart.md +65 -0
  32. data/docs/signals.md +98 -0
  33. data/docs/stats.md +148 -0
  34. data/docs/systemd.md +253 -0
  35. data/docs/testing_benchmarks_local_files.md +150 -0
  36. data/docs/testing_test_rackup_ci_files.md +36 -0
  37. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  38. data/ext/puma_http11/extconf.rb +59 -2
  39. data/ext/puma_http11/http11_parser.c +319 -487
  40. data/ext/puma_http11/http11_parser.h +15 -13
  41. data/ext/puma_http11/http11_parser.java.rl +64 -94
  42. data/ext/puma_http11/http11_parser.rl +27 -24
  43. data/ext/puma_http11/http11_parser_common.rl +8 -8
  44. data/ext/puma_http11/mini_ssl.c +696 -38
  45. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  46. data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  47. data/ext/puma_http11/org/jruby/puma/Http11.java +241 -145
  48. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +174 -221
  49. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
  50. data/ext/puma_http11/puma_http11.c +183 -175
  51. data/lib/puma/app/status.rb +77 -29
  52. data/lib/puma/binder.rb +349 -119
  53. data/lib/puma/cli.rb +163 -801
  54. data/lib/puma/client.rb +627 -144
  55. data/lib/puma/client_env.rb +171 -0
  56. data/lib/puma/cluster/worker.rb +183 -0
  57. data/lib/puma/cluster/worker_handle.rb +127 -0
  58. data/lib/puma/cluster.rb +634 -0
  59. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  60. data/lib/puma/commonlogger.rb +115 -0
  61. data/lib/puma/configuration.rb +420 -198
  62. data/lib/puma/const.rb +266 -115
  63. data/lib/puma/control_cli.rb +219 -120
  64. data/lib/puma/detect.rb +56 -2
  65. data/lib/puma/dsl.rb +1562 -0
  66. data/lib/puma/error_logger.rb +115 -0
  67. data/lib/puma/events.rb +44 -60
  68. data/lib/puma/io_buffer.rb +48 -5
  69. data/lib/puma/jruby_restart.rb +2 -51
  70. data/lib/puma/json_serialization.rb +96 -0
  71. data/lib/puma/launcher/bundle_pruner.rb +102 -0
  72. data/lib/puma/launcher.rb +501 -0
  73. data/lib/puma/log_writer.rb +153 -0
  74. data/lib/puma/minissl/context_builder.rb +96 -0
  75. data/lib/puma/minissl.rb +355 -37
  76. data/lib/puma/null_io.rb +79 -12
  77. data/lib/puma/plugin/systemd.rb +90 -0
  78. data/lib/puma/plugin/tmp_restart.rb +36 -0
  79. data/lib/puma/plugin.rb +111 -0
  80. data/lib/puma/rack/builder.rb +297 -0
  81. data/lib/puma/rack/urlmap.rb +93 -0
  82. data/lib/puma/rack_default.rb +21 -4
  83. data/lib/puma/reactor.rb +102 -133
  84. data/lib/puma/response.rb +532 -0
  85. data/lib/puma/runner.rb +211 -0
  86. data/lib/puma/sd_notify.rb +146 -0
  87. data/lib/puma/server.rb +582 -445
  88. data/lib/puma/server_plugin_control.rb +32 -0
  89. data/lib/puma/single.rb +72 -0
  90. data/lib/puma/state_file.rb +69 -0
  91. data/lib/puma/thread_pool.rb +389 -57
  92. data/lib/puma/util.rb +125 -0
  93. data/lib/puma.rb +80 -6
  94. data/lib/rack/handler/puma.rb +125 -47
  95. data/tools/Dockerfile +26 -0
  96. data/tools/trickletest.rb +44 -0
  97. metadata +88 -148
  98. data/COPYING +0 -55
  99. data/Gemfile +0 -10
  100. data/History.txt +0 -302
  101. data/Manifest.txt +0 -60
  102. data/Rakefile +0 -165
  103. data/TODO +0 -5
  104. data/docs/config.md +0 -0
  105. data/ext/puma_http11/ext_help.h +0 -15
  106. data/ext/puma_http11/io_buffer.c +0 -154
  107. data/lib/puma/accept_nonblock.rb +0 -23
  108. data/lib/puma/capistrano.rb +0 -34
  109. data/lib/puma/compat.rb +0 -11
  110. data/lib/puma/daemon_ext.rb +0 -20
  111. data/lib/puma/delegation.rb +0 -11
  112. data/lib/puma/java_io_buffer.rb +0 -45
  113. data/lib/puma/rack_patch.rb +0 -25
  114. data/puma.gemspec +0 -45
  115. data/test/test_app_status.rb +0 -88
  116. data/test/test_cli.rb +0 -171
  117. data/test/test_config.rb +0 -16
  118. data/test/test_http10.rb +0 -27
  119. data/test/test_http11.rb +0 -126
  120. data/test/test_integration.rb +0 -154
  121. data/test/test_iobuffer.rb +0 -38
  122. data/test/test_minissl.rb +0 -25
  123. data/test/test_null_io.rb +0 -31
  124. data/test/test_persistent.rb +0 -238
  125. data/test/test_puma_server.rb +0 -224
  126. data/test/test_rack_handler.rb +0 -10
  127. data/test/test_rack_server.rb +0 -141
  128. data/test/test_thread_pool.rb +0 -146
  129. data/test/test_unix_socket.rb +0 -39
  130. data/test/test_ws.rb +0 -89
  131. data/tools/jungle/init.d/README.md +0 -54
  132. data/tools/jungle/init.d/puma +0 -332
  133. data/tools/jungle/init.d/run-puma +0 -3
  134. data/tools/jungle/upstart/README.md +0 -61
  135. data/tools/jungle/upstart/puma-manager.conf +0 -31
  136. data/tools/jungle/upstart/puma.conf +0 -52
data/lib/puma/cli.rb CHANGED
@@ -1,883 +1,245 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
  require 'uri'
3
5
 
4
- require 'puma/server'
5
- require 'puma/const'
6
- require 'puma/configuration'
7
- require 'puma/binder'
8
- require 'puma/detect'
9
- require 'puma/daemon_ext'
10
- require 'puma/util'
11
-
12
- require 'rack/commonlogger'
13
- require 'rack/utils'
6
+ require_relative '../puma'
7
+ require_relative 'configuration'
8
+ require_relative 'launcher'
9
+ require_relative 'const'
10
+ require_relative 'log_writer'
14
11
 
15
12
  module Puma
13
+ class << self
14
+ # The CLI exports a Puma::Configuration instance here to allow
15
+ # apps to pick it up. An app must load this object conditionally
16
+ # because it is not set if the app is launched via any mechanism
17
+ # other than the CLI class.
18
+ attr_accessor :cli_config
19
+ end
20
+
16
21
  # Handles invoke a Puma::Server in a command line style.
17
22
  #
18
23
  class CLI
19
24
  # Create a new CLI object using +argv+ as the command line
20
25
  # arguments.
21
26
  #
22
- # +stdout+ and +stderr+ can be set to IO-like objects which
23
- # this object will report status on.
24
- #
25
- def initialize(argv, stdout=STDOUT, stderr=STDERR)
27
+ def initialize(argv, log_writer = LogWriter.stdio, events = Events.new, env: ENV)
26
28
  @debug = false
27
- @argv = argv
28
- @stdout = stdout
29
- @stderr = stderr
30
-
31
- @phase = 0
32
- @workers = []
33
-
34
- @events = Events.new @stdout, @stderr
35
-
36
- @server = nil
37
- @status = nil
38
-
39
- @restart = false
40
- @phased_state = :idle
41
-
42
- @app = nil
43
-
44
- ENV['NEWRELIC_DISPATCHER'] ||= "puma"
45
-
46
- setup_options
47
-
48
- generate_restart_data
49
-
50
- @binder = Binder.new(@events)
51
- @binder.import_from_env
52
- end
53
-
54
- def restart_on_stop!
55
- @restart = true
56
- end
57
-
58
- def generate_restart_data
59
- # Use the same trick as unicorn, namely favor PWD because
60
- # it will contain an unresolved symlink, useful for when
61
- # the pwd is /data/releases/current.
62
- if dir = ENV['PWD']
63
- s_env = File.stat(dir)
64
- s_pwd = File.stat(Dir.pwd)
65
-
66
- if s_env.ino == s_pwd.ino and s_env.dev == s_pwd.dev
67
- @restart_dir = dir
68
- @options[:worker_directory] = dir
69
- end
70
- end
71
-
72
- @restart_dir ||= Dir.pwd
73
-
74
- @original_argv = ARGV.dup
75
-
76
- if defined? Rubinius::OS_ARGV
77
- @restart_argv = Rubinius::OS_ARGV
78
- else
79
- require 'rubygems'
80
-
81
- # if $0 is a file in the current directory, then restart
82
- # it the same, otherwise add -S on there because it was
83
- # picked up in PATH.
84
- #
85
- if File.exists?($0)
86
- arg0 = [Gem.ruby, $0]
87
- else
88
- arg0 = [Gem.ruby, "-S", $0]
89
- end
90
-
91
- # Detect and reinject -Ilib from the command line
92
- lib = File.expand_path "lib"
93
- arg0[1,0] = ["-I", lib] if $:[0] == lib
94
-
95
- @restart_argv = arg0 + ARGV
96
- end
97
- end
98
-
99
- def restart!
100
- @options[:on_restart].each do |blk|
101
- blk.call self
102
- end
103
-
104
- if jruby?
105
- @binder.listeners.each_with_index do |(str,io),i|
106
- io.close
107
-
108
- # We have to unlink a unix socket path that's not being used
109
- uri = URI.parse str
110
- if uri.scheme == "unix"
111
- path = "#{uri.host}#{uri.path}"
112
- File.unlink path
113
- end
114
- end
115
-
116
- require 'puma/jruby_restart'
117
- JRubyRestart.chdir_exec(@restart_dir, @restart_argv)
118
- else
119
- redirects = {:close_others => true}
120
- @binder.listeners.each_with_index do |(l,io),i|
121
- ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
122
- redirects[io.to_i] = io.to_i
123
- end
124
-
125
- if cmd = @options[:restart_cmd]
126
- argv = cmd.split(' ') + @original_argv
127
- else
128
- argv = @restart_argv
129
- end
130
-
131
- Dir.chdir @restart_dir
132
-
133
- argv += [redirects] unless RUBY_VERSION < '1.9'
134
- Kernel.exec(*argv)
135
- end
136
- end
137
-
138
- # Delegate +log+ to +@events+
139
- #
140
- def log(str)
141
- @events.log str
142
- end
143
-
144
- # Delegate +error+ to +@events+
145
- #
146
- def error(str)
147
- @events.error str
148
- end
149
-
150
- def debug(str)
151
- if @options[:debug]
152
- @events.log "- #{str}"
153
- end
154
- end
155
-
156
- def jruby?
157
- IS_JRUBY
158
- end
159
-
160
- def windows?
161
- RUBY_PLATFORM =~ /mswin32|ming32/
162
- end
163
-
164
- def unsupported(str, cond=true)
165
- return unless cond
166
- @events.error str
167
- raise UnsupportedOption
168
- end
169
-
170
- # Build the OptionParser object to handle the available options.
171
- #
172
- def setup_options
173
- @options = {
174
- :min_threads => 0,
175
- :max_threads => 16,
176
- :quiet => false,
177
- :debug => false,
178
- :binds => [],
179
- :workers => 0,
180
- :daemon => false,
181
- :worker_boot => []
182
- }
183
-
184
- @parser = OptionParser.new do |o|
185
- o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
186
- @options[:binds] << arg
187
- end
188
-
189
- o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
190
- @options[:config_file] = arg
191
- end
192
-
193
- o.on "--control URL", "The bind url to use for the control server",
194
- "Use 'auto' to use temp unix server" do |arg|
195
- if arg
196
- @options[:control_url] = arg
197
- elsif jruby?
198
- unsupported "No default url available on JRuby"
199
- end
200
- end
201
-
202
- o.on "--control-token TOKEN",
203
- "The token to use as authentication for the control server" do |arg|
204
- @options[:control_auth_token] = arg
205
- end
206
-
207
- o.on "-d", "--daemon", "Daemonize the server into the background" do
208
- @options[:daemon] = true
209
- @options[:quiet] = true
210
- end
211
-
212
- o.on "--debug", "Log lowlevel debugging information" do
213
- @options[:debug] = true
214
- end
215
-
216
- o.on "--dir DIR", "Change to DIR before starting" do |d|
217
- @options[:directory] = d.to_s
218
- @options[:worker_directory] = d.to_s
219
- end
29
+ @argv = argv.dup
30
+ @log_writer = log_writer
31
+ @events = events
220
32
 
221
- o.on "-e", "--environment ENVIRONMENT",
222
- "The environment to run the Rack app on (default development)" do |arg|
223
- @options[:environment] = arg
224
- end
225
-
226
- o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
227
- $LOAD_PATH.unshift(*arg.split(':'))
228
- end
229
-
230
- o.on "-p", "--port PORT", "Define what port TCP port to bind to",
231
- "Use -b for more advanced options" do |arg|
232
- @options[:binds] << "tcp://#{Configuration::DefaultTCPHost}:#{arg}"
233
- end
33
+ @conf = nil
234
34
 
235
- o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
236
- @options[:pidfile] = arg
237
- end
35
+ @stdout = nil
36
+ @stderr = nil
37
+ @append = false
238
38
 
239
- o.on "-q", "--quiet", "Quiet down the output" do
240
- @options[:quiet] = true
241
- end
39
+ @control_url = nil
40
+ @control_options = {}
242
41
 
243
- o.on "-R", "--restart-cmd CMD",
244
- "The puma command to run during a hot restart",
245
- "Default: inferred" do |cmd|
246
- @options[:restart_cmd] = cmd
247
- end
248
-
249
- o.on "-S", "--state PATH", "Where to store the state details" do |arg|
250
- @options[:state] = arg
251
- end
42
+ begin
43
+ setup_options env
252
44
 
253
- o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
254
- min, max = arg.split(":")
255
- if max
256
- @options[:min_threads] = min.to_i
257
- @options[:max_threads] = max.to_i
258
- else
259
- @options[:min_threads] = 0
260
- @options[:max_threads] = arg.to_i
45
+ if file = @argv.shift
46
+ @conf.configure do |user_config, file_config|
47
+ file_config.rackup file
261
48
  end
262
49
  end
263
-
264
- o.on "-V", "--version", "Print the version information" do
265
- puts "puma version #{Puma::Const::VERSION}"
266
- exit 1
267
- end
268
-
269
- o.on "-w", "--workers COUNT",
270
- "Activate cluster mode: How many worker processes to create" do |arg|
271
- @options[:workers] = arg.to_i
272
- end
273
-
274
- end
275
-
276
- @parser.banner = "puma <options> <rackup file>"
277
-
278
- @parser.on_tail "-h", "--help", "Show help" do
279
- log @parser
50
+ rescue UnsupportedOption
280
51
  exit 1
281
52
  end
282
- end
283
53
 
284
- # If configured, write the pid of the current process out
285
- # to a file.
286
- #
287
- def write_pid
288
- if path = @options[:pidfile]
289
- File.open(path, "w") do |f|
290
- f.puts Process.pid
54
+ @conf.configure do |user_config, file_config|
55
+ if @stdout || @stderr
56
+ user_config.stdout_redirect @stdout, @stderr, @append
291
57
  end
292
- end
293
- end
294
-
295
- def set_rack_environment
296
- # Try the user option first, then the environment variable,
297
- # finally default to development
298
-
299
- env = @options[:environment] ||
300
- ENV['RACK_ENV'] ||
301
- 'development'
302
-
303
- @options[:environment] = env
304
- ENV['RACK_ENV'] = env
305
- end
306
-
307
- def development?
308
- @options[:environment] == "development"
309
- end
310
-
311
- def delete_pidfile
312
- if path = @options[:pidfile]
313
- File.unlink path
314
- end
315
- end
316
-
317
- def write_state
318
- write_pid
319
-
320
- require 'yaml'
321
-
322
- if path = @options[:state]
323
- state = { "pid" => Process.pid }
324
-
325
- cfg = @config.dup
326
-
327
- [ :logger, :worker_boot, :on_restart ].each { |o| cfg.options.delete o }
328
-
329
- state["config"] = cfg
330
58
 
331
- File.open(path, "w") do |f|
332
- f.write state.to_yaml
59
+ if @control_url
60
+ user_config.activate_control_app @control_url, @control_options
333
61
  end
334
62
  end
335
- end
336
-
337
- # :nodoc:
338
- def parse_options
339
- @parser.parse! @argv
340
-
341
- if @argv.last
342
- @options[:rackup] = @argv.shift
343
- end
344
-
345
- @config = Puma::Configuration.new @options
346
-
347
- # Advertise the Configuration
348
- Puma.cli_config = @config
349
-
350
- @config.load
351
-
352
- if @options[:workers] > 0
353
- unsupported "worker mode not supported on JRuby and Windows",
354
- jruby? || windows?
355
- end
356
- end
357
63
 
358
- def graceful_stop(server)
359
- log " - Gracefully stopping, waiting for requests to finish"
360
- @status.stop(true) if @status
361
- server.stop(true)
362
- delete_pidfile
363
- log " - Goodbye!"
64
+ @launcher = Puma::Launcher.new(@conf, env: ENV, log_writer: @log_writer, events: @events, argv: argv)
364
65
  end
365
66
 
366
- def redirect_io
367
- stdout = @options[:redirect_stdout]
368
- stderr = @options[:redirect_stderr]
369
- append = @options[:redirect_append]
370
-
371
- if stdout
372
- STDOUT.reopen stdout, (append ? "a" : "w")
373
- STDOUT.sync = true
374
- STDOUT.puts "=== puma startup: #{Time.now} ==="
375
- end
376
-
377
- if stderr
378
- STDERR.reopen stderr, (append ? "a" : "w")
379
- STDERR.sync = true
380
- STDERR.puts "=== puma startup: #{Time.now} ==="
381
- end
382
- end
67
+ attr_reader :launcher
383
68
 
384
69
  # Parse the options, load the rackup, start the server and wait
385
70
  # for it to finish.
386
71
  #
387
72
  def run
388
- begin
389
- parse_options
390
- rescue UnsupportedOption
391
- exit 1
392
- end
393
-
394
- if dir = @options[:directory]
395
- Dir.chdir dir
396
- end
397
-
398
- clustered = @options[:workers] > 0
399
-
400
- if clustered
401
- @events = PidEvents.new STDOUT, STDERR
402
- @options[:logger] = @events
403
- end
404
-
405
- set_rack_environment
406
-
407
- if clustered
408
- run_cluster
409
- else
410
- run_single
411
- end
412
- end
413
-
414
- def daemon?
415
- @options[:daemon]
73
+ @launcher.run
416
74
  end
417
75
 
418
- def jruby_daemon?
419
- daemon? and jruby?
420
- end
421
-
422
- def output_header
423
- min_t = @options[:min_threads]
424
- max_t = @options[:max_threads]
425
-
426
- log "Puma #{Puma::Const::PUMA_VERSION} starting..."
427
- log "* Min threads: #{min_t}, max threads: #{max_t}"
428
- log "* Environment: #{ENV['RACK_ENV']}"
429
- end
430
-
431
- def start_control(server, str)
432
- require 'puma/app/status'
433
-
434
- uri = URI.parse str
435
-
436
- app = Puma::App::Status.new server, self
437
-
438
- if token = @options[:control_auth_token]
439
- app.auth_token = token unless token.empty? or token == :none
440
- end
441
-
442
- status = Puma::Server.new app, @events
443
- status.min_threads = 0
444
- status.max_threads = 1
445
-
446
- case uri.scheme
447
- when "tcp"
448
- log "* Starting status server on #{str}"
449
- status.add_tcp_listener uri.host, uri.port
450
- when "unix"
451
- log "* Starting status server on #{str}"
452
- path = "#{uri.host}#{uri.path}"
453
-
454
- status.add_unix_listener path
455
- else
456
- error "Invalid status URI: #{str}"
457
- end
458
-
459
- status.run
460
- @status = status
76
+ private
77
+ def unsupported(str)
78
+ @log_writer.error(str)
79
+ raise UnsupportedOption
461
80
  end
462
81
 
463
- def load_and_bind
464
- unless @config.app_configured?
465
- error "No application configured, nothing to run"
466
- exit 1
467
- end
468
-
469
- # Load the app before we daemonize.
470
- begin
471
- @app = @config.app
472
- rescue Exception => e
473
- log "! Unable to load application"
474
- raise e
82
+ def configure_control_url(command_line_arg)
83
+ if command_line_arg
84
+ @control_url = command_line_arg
85
+ elsif Puma.jruby?
86
+ unsupported "No default url available on JRuby"
475
87
  end
476
-
477
- @binder.parse @options[:binds], self
478
88
  end
479
89
 
480
- def run_single
481
- already_daemon = false
482
-
483
- if jruby_daemon?
484
- require 'puma/jruby_restart'
485
-
486
- if JRubyRestart.daemon?
487
- # load and bind before redirecting IO so errors show up on stdout/stderr
488
- load_and_bind
489
- end
490
-
491
- already_daemon = JRubyRestart.daemon_init
492
- end
493
-
494
- output_header
495
-
496
- if jruby_daemon?
497
- unless already_daemon
498
- require 'puma/jruby_restart'
499
-
500
- pid = nil
90
+ # Build the OptionParser object to handle the available options.
91
+ #
501
92
 
502
- Signal.trap "SIGUSR2" do
503
- log "* Started new process #{pid} as daemon..."
504
- exit
93
+ def setup_options(env = ENV)
94
+ @conf = Configuration.new({}, { events: @events }, env) do |user_config, file_config|
95
+ @parser = OptionParser.new do |o|
96
+ o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
97
+ user_config.bind arg
505
98
  end
506
99
 
507
- pid = JRubyRestart.daemon_start(@restart_dir, @restart_argv)
508
- sleep
509
- end
510
- else
511
- load_and_bind
512
- Process.daemon(true) if daemon?
513
- end
514
-
515
- write_state
516
-
517
- server = Puma::Server.new @app, @events
518
- server.binder = @binder
519
- server.min_threads = @options[:min_threads]
520
- server.max_threads = @options[:max_threads]
521
-
522
- unless development?
523
- server.leak_stack_on_error = false
524
- end
525
-
526
- @server = server
527
-
528
- if str = @options[:control_url]
529
- start_control server, str
530
- end
531
-
532
- begin
533
- Signal.trap "SIGUSR2" do
534
- @restart = true
535
- server.begin_restart
536
- end
537
- rescue Exception
538
- log "*** Sorry signal SIGUSR2 not implemented, restart feature disabled!"
539
- end
540
-
541
- begin
542
- Signal.trap "SIGTERM" do
543
- log " - Gracefully stopping, waiting for requests to finish"
544
- server.stop false
545
- end
546
- rescue Exception
547
- log "*** Sorry signal SIGTERM not implemented, gracefully stopping feature disabled!"
548
- end
549
-
550
- unless @options[:daemon]
551
- log "Use Ctrl-C to stop"
552
- end
553
-
554
- redirect_io
555
-
556
- if jruby?
557
- Signal.trap("INT") do
558
- graceful_stop server
559
- exit
560
- end
561
- end
562
-
563
- begin
564
- server.run.join
565
- rescue Interrupt
566
- graceful_stop server
567
- end
568
-
569
- if @restart
570
- log "* Restarting..."
571
- @status.stop true if @status
572
- restart!
573
- end
574
- end
575
-
576
- def worker(upgrade)
577
- $0 = "puma: cluster worker: #{@master_pid}"
578
- Signal.trap "SIGINT", "IGNORE"
579
-
580
- @master_read.close
581
- @suicide_pipe.close
582
-
583
- Thread.new do
584
- IO.select [@check_pipe]
585
- log "! Detected parent died, dying"
586
- exit! 1
587
- end
588
-
589
- # Be sure to change the directory again before loading
590
- # the app. This way we can pick up new code.
591
- if upgrade
592
- if dir = @options[:worker_directory]
593
- log "+ Changing to #{dir}"
594
- Dir.chdir dir
595
- end
596
- end
597
-
598
- # Invoke any worker boot hooks so they can get
599
- # things in shape before booting the app.
600
- hooks = @options[:worker_boot]
601
- hooks.each { |h| h.call }
602
-
603
- min_t = @options[:min_threads]
604
- max_t = @options[:max_threads]
605
-
606
- # If preload is used, then @app is set to the preloaded
607
- # application. Otherwise load it now via the config.
608
- app = @app || @config.app
609
-
610
- server = Puma::Server.new app, @events
611
- server.min_threads = min_t
612
- server.max_threads = max_t
613
- server.inherit_binder @binder
614
-
615
- unless development?
616
- server.leak_stack_on_error = false
617
- end
618
-
619
- Signal.trap "SIGTERM" do
620
- server.stop
621
- end
622
-
623
- begin
624
- @worker_write << "b#{Process.pid}\n"
625
- rescue SystemCallError, IOError
626
- STDERR.puts "Master seems to have exitted, exitting."
627
- return
628
- end
629
-
630
- server.run.join
631
-
632
- ensure
633
- @worker_write.close
634
- end
635
-
636
- def stop_workers
637
- log "- Gracefully shutting down workers..."
638
- @workers.each { |x| x.term }
639
-
640
- begin
641
- Process.waitall
642
- rescue Interrupt
643
- log "! Cancelled waiting for workers"
644
- else
645
- log "- Goodbye!"
646
- end
647
- end
648
-
649
- def start_phased_restart
650
- @phase += 1
651
- log "- Starting phased worker restart, phase: #{@phase}"
652
- end
653
-
654
- class Worker
655
- def initialize(pid, phase)
656
- @pid = pid
657
- @phase = phase
658
- @stage = :started
659
- end
660
-
661
- attr_reader :pid, :phase
662
-
663
- def booted?
664
- @stage == :booted
665
- end
666
-
667
- def boot!
668
- @stage = :booted
669
- end
670
-
671
- def term
672
- begin
673
- Process.kill "TERM", @pid
674
- rescue Errno::ESRCH
675
- end
676
- end
677
- end
678
-
679
- def spawn_workers
680
- diff = @options[:workers] - @workers.size
681
-
682
- upgrade = (@phased_state == :waiting)
683
-
684
- diff.times do
685
- pid = fork { worker(upgrade) }
686
- debug "Spawned worker: #{pid}"
687
- @workers << Worker.new(pid, @phase)
688
- end
100
+ o.on "--bind-to-activated-sockets [only]", "Bind to all activated sockets" do |arg|
101
+ user_config.bind_to_activated_sockets(arg || true)
102
+ end
689
103
 
690
- if diff > 0
691
- @phased_state = :idle
692
- end
693
- end
104
+ o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
105
+ file_config.load arg
106
+ end
694
107
 
695
- def all_workers_booted?
696
- @workers.count { |w| !w.booted? } == 0
697
- end
108
+ # Identical to supplying --config "-", but more semantic
109
+ o.on "--no-config", "Prevent Puma from searching for a config file" do |arg|
110
+ file_config.load "-"
111
+ end
698
112
 
699
- def check_workers
700
- while @workers.any?
701
- pid = Process.waitpid(-1, Process::WNOHANG)
702
- break unless pid
113
+ o.on "--control-url URL", "The bind url to use for the control server. Use 'auto' to use temp unix server" do |arg|
114
+ configure_control_url(arg)
115
+ end
703
116
 
704
- @workers.delete_if { |w| w.pid == pid }
705
- end
117
+ o.on "--control-token TOKEN",
118
+ "The token to use as authentication for the control server" do |arg|
119
+ @control_options[:auth_token] = arg
120
+ end
706
121
 
707
- spawn_workers
122
+ o.on "--debug", "Log lowlevel debugging information" do
123
+ user_config.debug
124
+ end
708
125
 
709
- if @phased_state == :idle && all_workers_booted?
710
- # If we're running at proper capacity, check to see if
711
- # we need to phase any workers out (which will restart
712
- # in the right phase).
713
- #
714
- w = @workers.find { |x| x.phase != @phase }
126
+ o.on "--dir DIR", "Change to DIR before starting" do |d|
127
+ user_config.directory d
128
+ end
715
129
 
716
- if w
717
- @phased_state = :waiting
718
- log "- Stopping #{w.pid} for phased upgrade..."
719
- w.term
720
- end
721
- end
722
- end
130
+ o.on "-e", "--environment ENVIRONMENT",
131
+ "The environment to run the Rack app on (default development)" do |arg|
132
+ user_config.environment arg
133
+ end
723
134
 
724
- def wakeup!
725
- begin
726
- @wakeup.write "!" unless @wakeup.closed?
727
- rescue SystemCallError, IOError
728
- end
729
- end
135
+ o.on "-f", "--fork-worker=[REQUESTS]", OptionParser::DecimalInteger,
136
+ "Fork new workers from existing worker. Cluster mode only",
137
+ "Auto-refork after REQUESTS (default 1000)" do |*args|
138
+ user_config.fork_worker(*args.compact)
139
+ end
730
140
 
731
- def run_cluster
732
- log "Puma #{Puma::Const::PUMA_VERSION} starting in cluster mode..."
733
- log "* Process workers: #{@options[:workers]}"
734
- log "* Min threads: #{@options[:min_threads]}, max threads: #{@options[:max_threads]}"
735
- log "* Environment: #{ENV['RACK_ENV']}"
736
-
737
- if @options[:preload_app]
738
- log "* Preloading application"
739
- load_and_bind
740
- else
741
- log "* Phased restart available"
742
-
743
- unless @config.app_configured?
744
- error "No application configured, nothing to run"
745
- exit 1
746
- end
141
+ o.on "-I", "--include PATH", "Specify $LOAD_PATH directories" do |arg|
142
+ $LOAD_PATH.unshift(*arg.split(':'))
143
+ end
747
144
 
748
- @binder.parse @options[:binds], self
749
- end
145
+ o.on "--idle-timeout SECONDS", "Number of seconds until the next request before automatic shutdown" do |arg|
146
+ user_config.idle_timeout arg
147
+ end
750
148
 
751
- read, @wakeup = Puma::Util.pipe
149
+ o.on "-p", "--port PORT", "Define the TCP port to bind to",
150
+ "Use -b for more advanced options" do |arg|
151
+ user_config.port arg, Configuration.default_tcp_host
152
+ end
752
153
 
753
- Signal.trap "SIGCHLD" do
754
- wakeup!
755
- end
154
+ o.on "--pidfile PATH", "Use PATH as a pidfile" do |arg|
155
+ user_config.pidfile arg
156
+ end
756
157
 
757
- stop = false
158
+ o.on "--plugin PLUGIN", "Load the given PLUGIN. Can be used multiple times to load multiple plugins." do |arg|
159
+ user_config.plugin arg
160
+ end
758
161
 
759
- begin
760
- Signal.trap "SIGUSR2" do
761
- @restart = true
762
- stop = true
763
- wakeup!
764
- end
765
- rescue Exception
766
- end
162
+ o.on "--preload", "Preload the app. Cluster mode only" do
163
+ user_config.preload_app!
164
+ end
767
165
 
768
- master_pid = Process.pid
166
+ o.on "--prune-bundler", "Prune out the bundler env if possible" do
167
+ user_config.prune_bundler
168
+ end
769
169
 
770
- begin
771
- Signal.trap "SIGTERM" do
772
- # The worker installs their own SIGTERM when booted.
773
- # Until then, this is run by the worker and the worker
774
- # should just exit if they get it.
775
- if Process.pid != master_pid
776
- log "Early termination of worker"
777
- exit! 0
778
- else
779
- stop = true
780
- wakeup!
170
+ o.on "--extra-runtime-dependencies GEM1,GEM2", "Defines any extra needed gems when using --prune-bundler" do |arg|
171
+ user_config.extra_runtime_dependencies arg.split(',')
781
172
  end
782
- end
783
- rescue Exception
784
- end
785
173
 
786
- phased_restart = false
174
+ o.on "-q", "--quiet", "Do not log requests internally (default true)" do
175
+ user_config.quiet
176
+ end
787
177
 
788
- begin
789
- if @options[:preload_app]
790
- Signal.trap "SIGUSR1" do
791
- log "App preloaded, phased restart unavailable"
178
+ o.on "-v", "--log-requests", "Log requests as they occur" do
179
+ user_config.log_requests
792
180
  end
793
- else
794
- Signal.trap "SIGUSR1" do
795
- phased_restart = true
796
- @wakeup << "!"
797
- wakeup!
181
+
182
+ o.on "-R", "--restart-cmd CMD",
183
+ "The puma command to run during a hot restart",
184
+ "Default: inferred" do |cmd|
185
+ user_config.restart_command cmd
798
186
  end
799
- end
800
- rescue Exception
801
- end
802
187
 
803
- # Used by the workers to detect if the master process dies.
804
- # If select says that @check_pipe is ready, it's because the
805
- # master has exited and @suicide_pipe has been automatically
806
- # closed.
807
- #
808
- @check_pipe, @suicide_pipe = Puma::Util.pipe
809
-
810
- if @options[:daemon]
811
- Process.daemon(true)
812
- else
813
- log "Use Ctrl-C to stop"
814
- end
188
+ o.on "-s", "--silent", "Do not log prompt messages other than errors" do
189
+ @log_writer = LogWriter.new(NullIO.new, $stderr)
190
+ end
815
191
 
816
- @master_pid = Process.pid
192
+ o.on "-S", "--state PATH", "Where to store the state details" do |arg|
193
+ user_config.state_path arg
194
+ end
817
195
 
818
- redirect_io
196
+ o.on '-t', '--threads INT', "min:max threads to use (default 0:16)" do |arg|
197
+ min, max = arg.split(":")
198
+ if max
199
+ user_config.threads min, max
200
+ else
201
+ user_config.threads min, min
202
+ end
203
+ end
819
204
 
820
- write_state
205
+ o.on "--early-hints", "Enable early hints support" do
206
+ user_config.early_hints
207
+ end
821
208
 
822
- @master_read, @worker_write = read, @wakeup
823
- spawn_workers
209
+ o.on "-V", "--version", "Print the version information" do
210
+ puts "puma version #{Puma::Const::VERSION}"
211
+ exit 0
212
+ end
824
213
 
825
- Signal.trap "SIGINT" do
826
- stop = true
827
- wakeup!
828
- end
214
+ o.on "-w", "--workers COUNT",
215
+ "Activate cluster mode: How many worker processes to create" do |arg|
216
+ user_config.workers arg
217
+ end
829
218
 
830
- begin
831
- while !stop
832
- begin
833
- res = IO.select([read], nil, nil, 5)
834
-
835
- if res
836
- req = read.read_nonblock(1)
837
-
838
- if req == "b"
839
- pid = read.gets.to_i
840
- w = @workers.find { |x| x.pid == pid }
841
- if w
842
- w.boot!
843
- log "- Worker #{pid} booted, phase: #{w.phase}"
844
- else
845
- log "! Out-of-sync worker list, no #{pid} worker"
846
- end
847
- end
848
- end
219
+ o.on "--tag NAME", "Additional text to display in process listing" do |arg|
220
+ user_config.tag arg
221
+ end
849
222
 
850
- check_workers
223
+ o.on "--redirect-stdout FILE", "Redirect STDOUT to a specific file" do |arg|
224
+ @stdout = arg.to_s
225
+ end
851
226
 
852
- if phased_restart
853
- start_phased_restart
854
- phased_restart = false
855
- end
227
+ o.on "--redirect-stderr FILE", "Redirect STDERR to a specific file" do |arg|
228
+ @stderr = arg.to_s
229
+ end
856
230
 
857
- rescue Interrupt
858
- stop = true
231
+ o.on "--[no-]redirect-append", "Append to redirected files" do |val|
232
+ @append = val
859
233
  end
860
- end
861
234
 
862
- stop_workers
863
- ensure
864
- delete_pidfile
865
- @check_pipe.close
866
- @suicide_pipe.close
867
- read.close
868
- @wakeup.close
869
- end
235
+ o.banner = "puma <options> <rackup file>"
870
236
 
871
- if @restart
872
- log "* Restarting..."
873
- restart!
237
+ o.on_tail "-h", "--help", "Show help" do
238
+ $stdout.puts o
239
+ exit 0
240
+ end
241
+ end.parse! @argv
874
242
  end
875
243
  end
876
-
877
- def stop
878
- @status.stop(true) if @status
879
- @server.stop(true) if @server
880
- delete_pidfile
881
- end
882
244
  end
883
245
  end