puma 2.1.1 → 7.2.1

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