puma 4.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1513 -0
  3. data/LICENSE +26 -0
  4. data/README.md +309 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +37 -0
  9. data/docs/deployment.md +111 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/nginx.md +80 -0
  14. data/docs/plugins.md +28 -0
  15. data/docs/restart.md +41 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +290 -0
  18. data/ext/puma_http11/PumaHttp11Service.java +19 -0
  19. data/ext/puma_http11/ext_help.h +15 -0
  20. data/ext/puma_http11/extconf.rb +23 -0
  21. data/ext/puma_http11/http11_parser.c +1044 -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 +147 -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 +553 -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/IOBuffer.java +72 -0
  31. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +363 -0
  32. data/ext/puma_http11/puma_http11.c +500 -0
  33. data/lib/puma.rb +31 -0
  34. data/lib/puma/accept_nonblock.rb +29 -0
  35. data/lib/puma/app/status.rb +80 -0
  36. data/lib/puma/binder.rb +439 -0
  37. data/lib/puma/cli.rb +239 -0
  38. data/lib/puma/client.rb +494 -0
  39. data/lib/puma/cluster.rb +555 -0
  40. data/lib/puma/commonlogger.rb +108 -0
  41. data/lib/puma/configuration.rb +362 -0
  42. data/lib/puma/const.rb +235 -0
  43. data/lib/puma/control_cli.rb +281 -0
  44. data/lib/puma/convenient.rb +25 -0
  45. data/lib/puma/delegation.rb +13 -0
  46. data/lib/puma/detect.rb +15 -0
  47. data/lib/puma/dsl.rb +738 -0
  48. data/lib/puma/events.rb +156 -0
  49. data/lib/puma/io_buffer.rb +4 -0
  50. data/lib/puma/jruby_restart.rb +84 -0
  51. data/lib/puma/launcher.rb +478 -0
  52. data/lib/puma/minissl.rb +278 -0
  53. data/lib/puma/null_io.rb +44 -0
  54. data/lib/puma/plugin.rb +120 -0
  55. data/lib/puma/plugin/tmp_restart.rb +36 -0
  56. data/lib/puma/rack/builder.rb +301 -0
  57. data/lib/puma/rack/urlmap.rb +93 -0
  58. data/lib/puma/rack_default.rb +9 -0
  59. data/lib/puma/reactor.rb +399 -0
  60. data/lib/puma/runner.rb +185 -0
  61. data/lib/puma/server.rb +1033 -0
  62. data/lib/puma/single.rb +124 -0
  63. data/lib/puma/state_file.rb +31 -0
  64. data/lib/puma/tcp_logger.rb +41 -0
  65. data/lib/puma/thread_pool.rb +328 -0
  66. data/lib/puma/util.rb +124 -0
  67. data/lib/rack/handler/puma.rb +115 -0
  68. data/tools/docker/Dockerfile +16 -0
  69. data/tools/jungle/README.md +19 -0
  70. data/tools/jungle/init.d/README.md +61 -0
  71. data/tools/jungle/init.d/puma +421 -0
  72. data/tools/jungle/init.d/run-puma +18 -0
  73. data/tools/jungle/rc.d/README.md +74 -0
  74. data/tools/jungle/rc.d/puma +61 -0
  75. data/tools/jungle/rc.d/puma.conf +10 -0
  76. data/tools/jungle/upstart/README.md +61 -0
  77. data/tools/jungle/upstart/puma-manager.conf +31 -0
  78. data/tools/jungle/upstart/puma.conf +69 -0
  79. data/tools/trickletest.rb +44 -0
  80. metadata +144 -0
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/const'
4
+ require "puma/null_io"
5
+ require 'stringio'
6
+
7
+ module Puma
8
+ # The default implement of an event sink object used by Server
9
+ # for when certain kinds of events occur in the life of the server.
10
+ #
11
+ # The methods available are the events that the Server fires.
12
+ #
13
+ class Events
14
+ class DefaultFormatter
15
+ def call(str)
16
+ str
17
+ end
18
+ end
19
+
20
+ class PidFormatter
21
+ def call(str)
22
+ "[#{$$}] #{str}"
23
+ end
24
+ end
25
+
26
+ include Const
27
+
28
+ # Create an Events object that prints to +stdout+ and +stderr+.
29
+ #
30
+ def initialize(stdout, stderr)
31
+ @formatter = DefaultFormatter.new
32
+ @stdout = stdout
33
+ @stderr = stderr
34
+
35
+ @stdout.sync = true
36
+ @stderr.sync = true
37
+
38
+ @debug = ENV.key? 'PUMA_DEBUG'
39
+
40
+ @hooks = Hash.new { |h,k| h[k] = [] }
41
+ end
42
+
43
+ attr_reader :stdout, :stderr
44
+ attr_accessor :formatter
45
+
46
+ # Fire callbacks for the named hook
47
+ #
48
+ def fire(hook, *args)
49
+ @hooks[hook].each { |t| t.call(*args) }
50
+ end
51
+
52
+ # Register a callback for a given hook
53
+ #
54
+ def register(hook, obj=nil, &blk)
55
+ if obj and blk
56
+ raise "Specify either an object or a block, not both"
57
+ end
58
+
59
+ h = obj || blk
60
+
61
+ @hooks[hook] << h
62
+
63
+ h
64
+ end
65
+
66
+ # Write +str+ to +@stdout+
67
+ #
68
+ def log(str)
69
+ @stdout.puts format(str)
70
+ end
71
+
72
+ def write(str)
73
+ @stdout.write format(str)
74
+ end
75
+
76
+ def debug(str)
77
+ log("% #{str}") if @debug
78
+ end
79
+
80
+ # Write +str+ to +@stderr+
81
+ #
82
+ def error(str)
83
+ @stderr.puts format("ERROR: #{str}")
84
+ exit 1
85
+ end
86
+
87
+ def format(str)
88
+ formatter.call(str)
89
+ end
90
+
91
+ # An HTTP parse error has occurred.
92
+ # +server+ is the Server object, +env+ the request, and +error+ a
93
+ # parsing exception.
94
+ #
95
+ def parse_error(server, env, error)
96
+ @stderr.puts "#{Time.now}: HTTP parse error, malformed request " \
97
+ "(#{env[HTTP_X_FORWARDED_FOR] || env[REMOTE_ADDR]}#{env[REQUEST_PATH]}): " \
98
+ "#{error.inspect}" \
99
+ "\n---\n"
100
+ end
101
+
102
+ # An SSL error has occurred.
103
+ # +server+ is the Server object, +peeraddr+ peer address, +peercert+
104
+ # any peer certificate (if present), and +error+ an exception object.
105
+ #
106
+ def ssl_error(server, peeraddr, peercert, error)
107
+ subject = peercert ? peercert.subject : nil
108
+ @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
109
+ end
110
+
111
+ # An unknown error has occurred.
112
+ # +server+ is the Server object, +error+ an exception object,
113
+ # +kind+ some additional info, and +env+ the request.
114
+ #
115
+ def unknown_error(server, error, kind="Unknown", env=nil)
116
+ if error.respond_to? :render
117
+ error.render "#{Time.now}: #{kind} error", @stderr
118
+ else
119
+ if env
120
+ string_block = [ "#{Time.now}: #{kind} error handling request { #{env['REQUEST_METHOD']} #{env['PATH_INFO']} }" ]
121
+ string_block << error.inspect
122
+ else
123
+ string_block = [ "#{Time.now}: #{kind} error: #{error.inspect}" ]
124
+ end
125
+ string_block << error.backtrace
126
+ @stderr.puts string_block.join("\n")
127
+ end
128
+ end
129
+
130
+ def on_booted(&block)
131
+ register(:on_booted, &block)
132
+ end
133
+
134
+ def fire_on_booted!
135
+ fire(:on_booted)
136
+ end
137
+
138
+ DEFAULT = new(STDOUT, STDERR)
139
+
140
+ # Returns an Events object which writes its status to 2 StringIO
141
+ # objects.
142
+ #
143
+ def self.strings
144
+ Events.new StringIO.new, StringIO.new
145
+ end
146
+
147
+ def self.stdio
148
+ Events.new $stdout, $stderr
149
+ end
150
+
151
+ def self.null
152
+ n = NullIO.new
153
+ Events.new n, n
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/detect'
4
+ require 'puma/puma_http11'
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ffi'
4
+
5
+ module Puma
6
+ module JRubyRestart
7
+ extend FFI::Library
8
+ ffi_lib 'c'
9
+
10
+ attach_function :execlp, [:string, :varargs], :int
11
+ attach_function :chdir, [:string], :int
12
+ attach_function :fork, [], :int
13
+ attach_function :exit, [:int], :void
14
+ attach_function :setsid, [], :int
15
+
16
+ def self.chdir_exec(dir, argv)
17
+ chdir(dir)
18
+ cmd = argv.first
19
+ argv = ([:string] * argv.size).zip(argv).flatten
20
+ argv << :string
21
+ argv << nil
22
+ execlp(cmd, *argv)
23
+ raise SystemCallError.new(FFI.errno)
24
+ end
25
+
26
+ PermKey = 'PUMA_DAEMON_PERM'
27
+ RestartKey = 'PUMA_DAEMON_RESTART'
28
+
29
+ # Called to tell things "Your now always in daemon mode,
30
+ # don't try to reenter it."
31
+ #
32
+ def self.perm_daemonize
33
+ ENV[PermKey] = "1"
34
+ end
35
+
36
+ def self.daemon?
37
+ ENV.key?(PermKey) || ENV.key?(RestartKey)
38
+ end
39
+
40
+ def self.daemon_init
41
+ return true if ENV.key?(PermKey)
42
+
43
+ return false unless ENV.key? RestartKey
44
+
45
+ master = ENV[RestartKey]
46
+
47
+ # In case the master disappears early
48
+ begin
49
+ Process.kill "SIGUSR2", master.to_i
50
+ rescue SystemCallError => e
51
+ end
52
+
53
+ ENV[RestartKey] = ""
54
+
55
+ setsid
56
+
57
+ null = File.open "/dev/null", "w+"
58
+ STDIN.reopen null
59
+ STDOUT.reopen null
60
+ STDERR.reopen null
61
+
62
+ true
63
+ end
64
+
65
+ def self.daemon_start(dir, argv)
66
+ ENV[RestartKey] = Process.pid.to_s
67
+
68
+ if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
69
+ ENV['JRUBY_OPTS'] = k
70
+ end
71
+
72
+ cmd = argv.first
73
+ argv = ([:string] * argv.size).zip(argv).flatten
74
+ argv << :string
75
+ argv << nil
76
+
77
+ chdir(dir)
78
+ ret = fork
79
+ return ret if ret != 0
80
+ execlp(cmd, *argv)
81
+ raise SystemCallError.new(FFI.errno)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,478 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'puma/events'
4
+ require 'puma/detect'
5
+ require 'puma/cluster'
6
+ require 'puma/single'
7
+ require 'puma/const'
8
+ require 'puma/binder'
9
+
10
+ module Puma
11
+ # Puma::Launcher is the single entry point for starting a Puma server based on user
12
+ # configuration. It is responsible for taking user supplied arguments and resolving them
13
+ # with configuration in `config/puma.rb` or `config/puma/<env>.rb`.
14
+ #
15
+ # It is responsible for either launching a cluster of Puma workers or a single
16
+ # puma server.
17
+ class Launcher
18
+ KEYS_NOT_TO_PERSIST_IN_STATE = [
19
+ :logger, :lowlevel_error_handler,
20
+ :before_worker_shutdown, :before_worker_boot, :before_worker_fork,
21
+ :after_worker_boot, :before_fork, :on_restart
22
+ ]
23
+ # Returns an instance of Launcher
24
+ #
25
+ # +conf+ A Puma::Configuration object indicating how to run the server.
26
+ #
27
+ # +launcher_args+ A Hash that currently has one required key `:events`,
28
+ # this is expected to hold an object similar to an `Puma::Events.stdio`,
29
+ # this object will be responsible for broadcasting Puma's internal state
30
+ # to a logging destination. An optional key `:argv` can be supplied,
31
+ # this should be an array of strings, these arguments are re-used when
32
+ # restarting the puma server.
33
+ #
34
+ # Examples:
35
+ #
36
+ # conf = Puma::Configuration.new do |user_config|
37
+ # user_config.threads 1, 10
38
+ # user_config.app do |env|
39
+ # [200, {}, ["hello world"]]
40
+ # end
41
+ # end
42
+ # Puma::Launcher.new(conf, events: Puma::Events.stdio).run
43
+ def initialize(conf, launcher_args={})
44
+ @runner = nil
45
+ @events = launcher_args[:events] || Events::DEFAULT
46
+ @argv = launcher_args[:argv] || []
47
+ @original_argv = @argv.dup
48
+ @config = conf
49
+
50
+ @binder = Binder.new(@events)
51
+ @binder.import_from_env
52
+
53
+ @environment = conf.environment
54
+
55
+ # Advertise the Configuration
56
+ Puma.cli_config = @config if defined?(Puma.cli_config)
57
+
58
+ @config.load
59
+
60
+ @options = @config.options
61
+ @config.clamp
62
+
63
+ @events.formatter = Events::PidFormatter.new if clustered?
64
+ @events.formatter = options[:log_formatter] if @options[:log_formatter]
65
+
66
+ generate_restart_data
67
+
68
+ if clustered? && !Process.respond_to?(:fork)
69
+ unsupported "worker mode not supported on #{RUBY_ENGINE} on this platform"
70
+ end
71
+
72
+ if @options[:daemon] && Puma.windows?
73
+ unsupported 'daemon mode not supported on Windows'
74
+ end
75
+
76
+ Dir.chdir(@restart_dir)
77
+
78
+ prune_bundler if prune_bundler?
79
+
80
+ @environment = @options[:environment] if @options[:environment]
81
+ set_rack_environment
82
+
83
+ if clustered?
84
+ @options[:logger] = @events
85
+
86
+ @runner = Cluster.new(self, @events)
87
+ else
88
+ @runner = Single.new(self, @events)
89
+ end
90
+ Puma.stats_object = @runner
91
+
92
+ @status = :run
93
+ end
94
+
95
+ attr_reader :binder, :events, :config, :options, :restart_dir
96
+
97
+ # Return stats about the server
98
+ def stats
99
+ @runner.stats
100
+ end
101
+
102
+ # Write a state file that can be used by pumactl to control
103
+ # the server
104
+ def write_state
105
+ write_pid
106
+
107
+ path = @options[:state]
108
+ return unless path
109
+
110
+ require 'puma/state_file'
111
+
112
+ sf = StateFile.new
113
+ sf.pid = Process.pid
114
+ sf.control_url = @options[:control_url]
115
+ sf.control_auth_token = @options[:control_auth_token]
116
+
117
+ sf.save path
118
+ end
119
+
120
+ # Delete the configured pidfile
121
+ def delete_pidfile
122
+ path = @options[:pidfile]
123
+ File.unlink(path) if path && File.exist?(path)
124
+ end
125
+
126
+ # Begin async shutdown of the server
127
+ def halt
128
+ @status = :halt
129
+ @runner.halt
130
+ end
131
+
132
+ # Begin async shutdown of the server gracefully
133
+ def stop
134
+ @status = :stop
135
+ @runner.stop
136
+ end
137
+
138
+ # Begin async restart of the server
139
+ def restart
140
+ @status = :restart
141
+ @runner.restart
142
+ end
143
+
144
+ # Begin a phased restart if supported
145
+ def phased_restart
146
+ unless @runner.respond_to?(:phased_restart) and @runner.phased_restart
147
+ log "* phased-restart called but not available, restarting normally."
148
+ return restart
149
+ end
150
+ true
151
+ end
152
+
153
+ # Run the server. This blocks until the server is stopped
154
+ def run
155
+ previous_env =
156
+ if defined?(Bundler)
157
+ env = Bundler::ORIGINAL_ENV.dup
158
+ # add -rbundler/setup so we load from Gemfile when restarting
159
+ bundle = "-rbundler/setup"
160
+ env["RUBYOPT"] = [env["RUBYOPT"], bundle].join(" ").lstrip unless env["RUBYOPT"].to_s.include?(bundle)
161
+ env
162
+ else
163
+ ENV.to_h
164
+ end
165
+
166
+ @config.clamp
167
+
168
+ @config.plugins.fire_starts self
169
+
170
+ setup_signals
171
+ set_process_title
172
+ @runner.run
173
+
174
+ case @status
175
+ when :halt
176
+ log "* Stopping immediately!"
177
+ when :run, :stop
178
+ graceful_stop
179
+ when :restart
180
+ log "* Restarting..."
181
+ ENV.replace(previous_env)
182
+ @runner.before_restart
183
+ restart!
184
+ when :exit
185
+ # nothing
186
+ end
187
+ end
188
+
189
+ # Return which tcp port the launcher is using, if it's using TCP
190
+ def connected_port
191
+ @binder.connected_port
192
+ end
193
+
194
+ def restart_args
195
+ cmd = @options[:restart_cmd]
196
+ if cmd
197
+ cmd.split(' ') + @original_argv
198
+ else
199
+ @restart_argv
200
+ end
201
+ end
202
+
203
+ def close_binder_listeners
204
+ @binder.close_listeners
205
+ end
206
+
207
+ def close_binder_unix_paths
208
+ @binder.close_unix_paths
209
+ end
210
+
211
+ private
212
+
213
+ # If configured, write the pid of the current process out
214
+ # to a file.
215
+ def write_pid
216
+ path = @options[:pidfile]
217
+ return unless path
218
+
219
+ File.open(path, 'w') { |f| f.puts Process.pid }
220
+ cur = Process.pid
221
+ at_exit do
222
+ delete_pidfile if cur == Process.pid
223
+ end
224
+ end
225
+
226
+ def reload_worker_directory
227
+ @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
228
+ end
229
+
230
+ def restart!
231
+ @config.run_hooks :on_restart, self
232
+
233
+ if Puma.jruby?
234
+ close_binder_listeners
235
+
236
+ require 'puma/jruby_restart'
237
+ JRubyRestart.chdir_exec(@restart_dir, restart_args)
238
+ elsif Puma.windows?
239
+ close_binder_listeners
240
+
241
+ argv = restart_args
242
+ Dir.chdir(@restart_dir)
243
+ Kernel.exec(*argv)
244
+ else
245
+ argv = restart_args
246
+ Dir.chdir(@restart_dir)
247
+ argv += [@binder.redirects_for_restart]
248
+ Kernel.exec(*argv)
249
+ end
250
+ end
251
+
252
+ def dependencies_and_files_to_require_after_prune
253
+ puma = spec_for_gem("puma")
254
+
255
+ deps = puma.runtime_dependencies.map do |d|
256
+ "#{d.name}:#{spec_for_gem(d.name).version}"
257
+ end
258
+
259
+ [deps, require_paths_for_gem(puma) + extra_runtime_deps_directories]
260
+ end
261
+
262
+ def extra_runtime_deps_directories
263
+ Array(@options[:extra_runtime_dependencies]).map do |d_name|
264
+ if (spec = spec_for_gem(d_name))
265
+ require_paths_for_gem(spec)
266
+ else
267
+ log "* Could not load extra dependency: #{d_name}"
268
+ nil
269
+ end
270
+ end.flatten.compact
271
+ end
272
+
273
+ def puma_wild_location
274
+ puma = spec_for_gem("puma")
275
+ dirs = require_paths_for_gem(puma)
276
+ puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
277
+ File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
278
+ end
279
+
280
+ def prune_bundler
281
+ return unless defined?(Bundler)
282
+ require_rubygems_min_version!(Gem::Version.new("2.2"), "prune_bundler")
283
+ unless puma_wild_location
284
+ log "! Unable to prune Bundler environment, continuing"
285
+ return
286
+ end
287
+
288
+ deps, dirs = dependencies_and_files_to_require_after_prune
289
+
290
+ log '* Pruning Bundler environment'
291
+ home = ENV['GEM_HOME']
292
+ Bundler.with_clean_env do
293
+ ENV['GEM_HOME'] = home
294
+ ENV['PUMA_BUNDLER_PRUNED'] = '1'
295
+ args = [Gem.ruby, puma_wild_location, '-I', dirs.join(':'), deps.join(',')] + @original_argv
296
+ # Ruby 2.0+ defaults to true which breaks socket activation
297
+ args += [{:close_others => false}]
298
+ Kernel.exec(*args)
299
+ end
300
+ end
301
+
302
+ def spec_for_gem(gem_name)
303
+ Bundler.rubygems.loaded_specs(gem_name)
304
+ end
305
+
306
+ def require_paths_for_gem(gem_spec)
307
+ gem_spec.full_require_paths
308
+ end
309
+
310
+ def log(str)
311
+ @events.log str
312
+ end
313
+
314
+ def clustered?
315
+ (@options[:workers] || 0) > 0
316
+ end
317
+
318
+ def unsupported(str)
319
+ @events.error(str)
320
+ raise UnsupportedOption
321
+ end
322
+
323
+ def graceful_stop
324
+ @runner.stop_blocked
325
+ log "=== puma shutdown: #{Time.now} ==="
326
+ log "- Goodbye!"
327
+ end
328
+
329
+ def log_thread_status
330
+ Thread.list.each do |thread|
331
+ log "Thread TID-#{thread.object_id.to_s(36)} #{thread['label']}"
332
+ logstr = "Thread: TID-#{thread.object_id.to_s(36)}"
333
+ logstr += " #{thread.name}" if thread.respond_to?(:name)
334
+ log logstr
335
+
336
+ if thread.backtrace
337
+ log thread.backtrace.join("\n")
338
+ else
339
+ log "<no backtrace available>"
340
+ end
341
+ end
342
+ end
343
+
344
+ def set_process_title
345
+ Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
346
+ end
347
+
348
+ def title
349
+ buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
350
+ buffer += " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
351
+ buffer
352
+ end
353
+
354
+ def set_rack_environment
355
+ @options[:environment] = environment
356
+ ENV['RACK_ENV'] = environment
357
+ end
358
+
359
+ def environment
360
+ @environment
361
+ end
362
+
363
+ def prune_bundler?
364
+ @options[:prune_bundler] && clustered? && !@options[:preload_app]
365
+ end
366
+
367
+ def generate_restart_data
368
+ if dir = @options[:directory]
369
+ @restart_dir = dir
370
+
371
+ elsif Puma.windows?
372
+ # I guess the value of PWD is garbage on windows so don't bother
373
+ # using it.
374
+ @restart_dir = Dir.pwd
375
+
376
+ # Use the same trick as unicorn, namely favor PWD because
377
+ # it will contain an unresolved symlink, useful for when
378
+ # the pwd is /data/releases/current.
379
+ elsif dir = ENV['PWD']
380
+ s_env = File.stat(dir)
381
+ s_pwd = File.stat(Dir.pwd)
382
+
383
+ if s_env.ino == s_pwd.ino and (Puma.jruby? or s_env.dev == s_pwd.dev)
384
+ @restart_dir = dir
385
+ end
386
+ end
387
+
388
+ @restart_dir ||= Dir.pwd
389
+
390
+ # if $0 is a file in the current directory, then restart
391
+ # it the same, otherwise add -S on there because it was
392
+ # picked up in PATH.
393
+ #
394
+ if File.exist?($0)
395
+ arg0 = [Gem.ruby, $0]
396
+ else
397
+ arg0 = [Gem.ruby, "-S", $0]
398
+ end
399
+
400
+ # Detect and reinject -Ilib from the command line, used for testing without bundler
401
+ # cruby has an expanded path, jruby has just "lib"
402
+ lib = File.expand_path "lib"
403
+ arg0[1,0] = ["-I", lib] if [lib, "lib"].include?($LOAD_PATH[0])
404
+
405
+ if defined? Puma::WILD_ARGS
406
+ @restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
407
+ else
408
+ @restart_argv = arg0 + @original_argv
409
+ end
410
+ end
411
+
412
+ def setup_signals
413
+ begin
414
+ Signal.trap "SIGUSR2" do
415
+ restart
416
+ end
417
+ rescue Exception
418
+ log "*** SIGUSR2 not implemented, signal based restart unavailable!"
419
+ end
420
+
421
+ unless Puma.jruby?
422
+ begin
423
+ Signal.trap "SIGUSR1" do
424
+ phased_restart
425
+ end
426
+ rescue Exception
427
+ log "*** SIGUSR1 not implemented, signal based restart unavailable!"
428
+ end
429
+ end
430
+
431
+ begin
432
+ Signal.trap "SIGTERM" do
433
+ graceful_stop
434
+
435
+ raise(SignalException, "SIGTERM") if @options[:raise_exception_on_sigterm]
436
+ end
437
+ rescue Exception
438
+ log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
439
+ end
440
+
441
+ begin
442
+ Signal.trap "SIGINT" do
443
+ stop
444
+ end
445
+ rescue Exception
446
+ log "*** SIGINT not implemented, signal based gracefully stopping unavailable!"
447
+ end
448
+
449
+ begin
450
+ Signal.trap "SIGHUP" do
451
+ if @runner.redirected_io?
452
+ @runner.redirect_io
453
+ else
454
+ stop
455
+ end
456
+ end
457
+ rescue Exception
458
+ log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
459
+ end
460
+
461
+ begin
462
+ Signal.trap "SIGINFO" do
463
+ log_thread_status
464
+ end
465
+ rescue Exception
466
+ # Not going to log this one, as SIGINFO is *BSD only and would be pretty annoying
467
+ # to see this constantly on Linux.
468
+ end
469
+ end
470
+
471
+ def require_rubygems_min_version!(min_version, feature)
472
+ return if min_version <= Gem::Version.new(Gem::VERSION)
473
+
474
+ raise "#{feature} is not supported on your version of RubyGems. " \
475
+ "You must have RubyGems #{min_version}+ to use this feature."
476
+ end
477
+ end
478
+ end