puma 2.7.0 → 3.1.1

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 (79) hide show
  1. checksums.yaml +5 -13
  2. data/DEPLOYMENT.md +91 -0
  3. data/Gemfile +3 -2
  4. data/History.txt +624 -1
  5. data/Manifest.txt +15 -3
  6. data/README.md +129 -14
  7. data/Rakefile +3 -3
  8. data/bin/puma-wild +31 -0
  9. data/bin/pumactl +1 -1
  10. data/docs/nginx.md +1 -1
  11. data/docs/signals.md +43 -0
  12. data/ext/puma_http11/extconf.rb +7 -2
  13. data/ext/puma_http11/http11_parser.java.rl +5 -5
  14. data/ext/puma_http11/io_buffer.c +1 -1
  15. data/ext/puma_http11/mini_ssl.c +233 -18
  16. data/ext/puma_http11/org/jruby/puma/Http11.java +12 -3
  17. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +39 -39
  18. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +245 -195
  19. data/ext/puma_http11/puma_http11.c +12 -4
  20. data/lib/puma.rb +1 -0
  21. data/lib/puma/app/status.rb +7 -0
  22. data/lib/puma/binder.rb +108 -39
  23. data/lib/puma/capistrano.rb +23 -6
  24. data/lib/puma/cli.rb +141 -446
  25. data/lib/puma/client.rb +48 -1
  26. data/lib/puma/cluster.rb +207 -58
  27. data/lib/puma/commonlogger.rb +107 -0
  28. data/lib/puma/configuration.rb +262 -235
  29. data/lib/puma/const.rb +97 -14
  30. data/lib/puma/control_cli.rb +85 -77
  31. data/lib/puma/convenient.rb +23 -0
  32. data/lib/puma/daemon_ext.rb +11 -4
  33. data/lib/puma/detect.rb +8 -1
  34. data/lib/puma/dsl.rb +456 -0
  35. data/lib/puma/events.rb +35 -18
  36. data/lib/puma/jruby_restart.rb +1 -1
  37. data/lib/puma/launcher.rb +399 -0
  38. data/lib/puma/minissl.rb +49 -20
  39. data/lib/puma/null_io.rb +15 -0
  40. data/lib/puma/plugin.rb +104 -0
  41. data/lib/puma/plugin/tmp_restart.rb +35 -0
  42. data/lib/puma/rack/backports/uri/common_18.rb +56 -0
  43. data/lib/puma/rack/backports/uri/common_192.rb +52 -0
  44. data/lib/puma/rack/backports/uri/common_193.rb +29 -0
  45. data/lib/puma/rack/builder.rb +295 -0
  46. data/lib/puma/rack/urlmap.rb +90 -0
  47. data/lib/puma/reactor.rb +14 -1
  48. data/lib/puma/runner.rb +35 -17
  49. data/lib/puma/server.rb +161 -58
  50. data/lib/puma/single.rb +15 -10
  51. data/lib/puma/state_file.rb +29 -0
  52. data/lib/puma/thread_pool.rb +88 -13
  53. data/lib/puma/util.rb +123 -0
  54. data/lib/rack/handler/puma.rb +35 -29
  55. data/puma.gemspec +2 -4
  56. data/tools/jungle/init.d/README.md +2 -2
  57. data/tools/jungle/init.d/puma +69 -7
  58. data/tools/jungle/upstart/puma.conf +8 -2
  59. metadata +51 -71
  60. data/COPYING +0 -55
  61. data/TODO +0 -5
  62. data/lib/puma/rack_patch.rb +0 -45
  63. data/test/test_app_status.rb +0 -92
  64. data/test/test_cli.rb +0 -173
  65. data/test/test_config.rb +0 -16
  66. data/test/test_http10.rb +0 -27
  67. data/test/test_http11.rb +0 -145
  68. data/test/test_integration.rb +0 -165
  69. data/test/test_iobuffer.rb +0 -38
  70. data/test/test_minissl.rb +0 -25
  71. data/test/test_null_io.rb +0 -31
  72. data/test/test_persistent.rb +0 -238
  73. data/test/test_puma_server.rb +0 -292
  74. data/test/test_rack_handler.rb +0 -10
  75. data/test/test_rack_server.rb +0 -141
  76. data/test/test_tcp_rack.rb +0 -42
  77. data/test/test_thread_pool.rb +0 -156
  78. data/test/test_unix_socket.rb +0 -39
  79. data/test/test_ws.rb +0 -89
data/lib/puma/events.rb CHANGED
@@ -8,12 +8,24 @@ module Puma
8
8
  # The methods available are the events that the Server fires.
9
9
  #
10
10
  class Events
11
+ class DefaultFormatter
12
+ def call(str)
13
+ str
14
+ end
15
+ end
16
+
17
+ class PidFormatter
18
+ def call(str)
19
+ "[#{$$}] #{str}"
20
+ end
21
+ end
11
22
 
12
23
  include Const
13
24
 
14
25
  # Create an Events object that prints to +stdout+ and +stderr+.
15
26
  #
16
27
  def initialize(stdout, stderr)
28
+ @formatter = DefaultFormatter.new
17
29
  @stdout = stdout
18
30
  @stderr = stderr
19
31
 
@@ -28,6 +40,7 @@ module Puma
28
40
  end
29
41
 
30
42
  attr_reader :stdout, :stderr
43
+ attr_accessor :formatter
31
44
 
32
45
  # Fire callbacks for the named hook
33
46
  #
@@ -52,11 +65,11 @@ module Puma
52
65
  # Write +str+ to +@stdout+
53
66
  #
54
67
  def log(str)
55
- @stdout.puts str
68
+ @stdout.puts format(str)
56
69
  end
57
70
 
58
71
  def write(str)
59
- @stdout.write str
72
+ @stdout.write format(str)
60
73
  end
61
74
 
62
75
  def debug(str)
@@ -66,11 +79,15 @@ module Puma
66
79
  # Write +str+ to +@stderr+
67
80
  #
68
81
  def error(str)
69
- @stderr.puts "ERROR: #{str}"
82
+ @stderr.puts format("ERROR: #{str}")
70
83
  exit 1
71
84
  end
72
85
 
73
- # An HTTP parse error has occured.
86
+ def format(str)
87
+ formatter.call(str)
88
+ end
89
+
90
+ # An HTTP parse error has occurred.
74
91
  # +server+ is the Server object, +env+ the request, and +error+ a
75
92
  # parsing exception.
76
93
  #
@@ -79,7 +96,16 @@ module Puma
79
96
  @stderr.puts "#{Time.now}: ENV: #{env.inspect}\n---\n"
80
97
  end
81
98
 
82
- # An unknown error has occured.
99
+ # An SSL error has occurred.
100
+ # +server+ is the Server object, +peeraddr+ peer address, +peercert+
101
+ # any peer certificate (if present), and +error+ an exception object.
102
+ #
103
+ def ssl_error(server, peeraddr, peercert, error)
104
+ subject = peercert ? peercert.subject : nil
105
+ @stderr.puts "#{Time.now}: SSL error, peer: #{peeraddr}, peer cert: #{subject}, #{error.inspect}"
106
+ end
107
+
108
+ # An unknown error has occurred.
83
109
  # +server+ is the Server object, +env+ the request, +error+ an exception
84
110
  # object, and +kind+ some additional info.
85
111
  #
@@ -102,7 +128,7 @@ module Puma
102
128
 
103
129
  DEFAULT = new(STDOUT, STDERR)
104
130
 
105
- # Returns an Events object which writes it's status to 2 StringIO
131
+ # Returns an Events object which writes its status to 2 StringIO
106
132
  # objects.
107
133
  #
108
134
  def self.strings
@@ -112,19 +138,10 @@ module Puma
112
138
  def self.stdio
113
139
  Events.new $stdout, $stderr
114
140
  end
115
- end
116
-
117
- class PidEvents < Events
118
- def log(str)
119
- super "[#{$$}] #{str}"
120
- end
121
141
 
122
- def write(str)
123
- super "[#{$$}] #{str}"
124
- end
125
-
126
- def error(str)
127
- super "[#{$$}] #{str}"
142
+ def self.null
143
+ n = NullIO.new
144
+ Events.new n, n
128
145
  end
129
146
  end
130
147
  end
@@ -61,7 +61,7 @@ module Puma
61
61
  end
62
62
 
63
63
  def self.daemon_start(dir, argv)
64
- ENV['PUMA_DAEMON_RESTART'] = Process.pid.to_s
64
+ ENV[RestartKey] = Process.pid.to_s
65
65
 
66
66
  if k = ENV['PUMA_JRUBY_DAEMON_OPTS']
67
67
  ENV['JRUBY_OPTS'] = k
@@ -0,0 +1,399 @@
1
+ require 'puma/server'
2
+ require 'puma/const'
3
+ require 'puma/configuration'
4
+ require 'puma/binder'
5
+ require 'puma/detect'
6
+ require 'puma/daemon_ext'
7
+ require 'puma/util'
8
+ require 'puma/single'
9
+ require 'puma/cluster'
10
+ require 'puma/state_file'
11
+
12
+ require 'puma/commonlogger'
13
+
14
+ module Puma
15
+ # Puam::Launcher is the single entry point for starting a Puma server based on user
16
+ # configuration. It is responsible for taking user supplied arguments and resolving them
17
+ # with configuration in `config/puma.rb` or `config/puma/<env>.rb`.
18
+ #
19
+ # It is responsible for either launching a cluster of Puma workers or a single
20
+ # puma server.
21
+ class Launcher
22
+ KEYS_NOT_TO_PERSIST_IN_STATE = [
23
+ :logger, :lowlevel_error_handler,
24
+ :before_worker_shutdown, :before_worker_boot, :before_worker_fork,
25
+ :after_worker_boot, :before_fork, :on_restart
26
+ ]
27
+ # Returns an instance of Launcher
28
+ #
29
+ # +conf+ A Puma::Configuration object indicating how to run the server.
30
+ #
31
+ # +launcher_args+ A Hash that currently has one required key `:events`,
32
+ # this is expected to hold an object similar to an `Puma::Events.stdio`,
33
+ # this object will be responsible for broadcasting Puma's internal state
34
+ # to a logging destination. An optional key `:argv` can be supplied,
35
+ # this should be an array of strings, these arguments are re-used when
36
+ # restarting the puma server.
37
+ #
38
+ # Examples:
39
+ #
40
+ # conf = Puma::Configuration.new do |c|
41
+ # c.threads 1, 10
42
+ # c.app do |env|
43
+ # [200, {}, ["hello world"]]
44
+ # end
45
+ # end
46
+ # Puma::Launcher.new(conf, argv: Puma::Events.stdio).run
47
+ def initialize(conf, launcher_args={})
48
+ @runner = nil
49
+ @events = launcher_args[:events] || Events::DEFAULT
50
+ @argv = launcher_args[:argv] || []
51
+ @original_argv = @argv.dup
52
+ @config = conf
53
+
54
+ @binder = Binder.new(@events)
55
+ @binder.import_from_env
56
+
57
+ generate_restart_data
58
+
59
+ @environment = conf.environment
60
+
61
+ # Advertise the Configuration
62
+ Puma.cli_config = @config if defined?(Puma.cli_config)
63
+
64
+ @config.load
65
+
66
+ @options = @config.options
67
+
68
+ if clustered? && (Puma.jruby? || Puma.windows?)
69
+ unsupported 'worker mode not supported on JRuby or Windows'
70
+ end
71
+
72
+ if @options[:daemon] && Puma.windows?
73
+ unsupported 'daemon mode not supported on Windows'
74
+ end
75
+
76
+ dir = @options[:directory]
77
+ Dir.chdir(dir) if dir
78
+
79
+ prune_bundler if prune_bundler?
80
+
81
+ @environment = @options[:environment] if @options[:environment]
82
+ set_rack_environment
83
+
84
+ if clustered?
85
+ @events.formatter = Events::PidFormatter.new
86
+ @options[:logger] = @events
87
+
88
+ @runner = Cluster.new(self, @events)
89
+ else
90
+ @runner = Single.new(self, @events)
91
+ end
92
+
93
+ @status = :run
94
+ end
95
+
96
+ attr_reader :binder, :events, :config, :options
97
+
98
+ # Return stats about the server
99
+ def stats
100
+ @runner.stats
101
+ end
102
+
103
+ # Write a state file that can be used by pumactl to control
104
+ # the server
105
+ def write_state
106
+ write_pid
107
+
108
+ path = @options[:state]
109
+ return unless path
110
+
111
+ sf = StateFile.new
112
+ sf.pid = Process.pid
113
+ sf.control_url = @options[:control_url]
114
+ sf.control_auth_token = @options[:control_auth_token]
115
+
116
+ sf.save path
117
+ end
118
+
119
+ # Delete the configured pidfile
120
+ def delete_pidfile
121
+ path = @options[:pidfile]
122
+ File.unlink(path) if path && File.exist?(path)
123
+ end
124
+
125
+ # If configured, write the pid of the current process out
126
+ # to a file.
127
+ def write_pid
128
+ path = @options[:pidfile]
129
+ return unless path
130
+
131
+ File.open(path, 'w') { |f| f.puts Process.pid }
132
+ cur = Process.pid
133
+ at_exit do
134
+ delete_pidfile if cur == Process.pid
135
+ end
136
+ end
137
+
138
+ # Begin async shutdown of the server
139
+ def halt
140
+ @status = :halt
141
+ @runner.halt
142
+ end
143
+
144
+ # Begin async shutdown of the server gracefully
145
+ def stop
146
+ @status = :stop
147
+ @runner.stop
148
+ end
149
+
150
+ # Begin async restart of the server
151
+ def restart
152
+ @status = :restart
153
+ @runner.restart
154
+ end
155
+
156
+ # Begin a phased restart if supported
157
+ def phased_restart
158
+ unless @runner.respond_to?(:phased_restart) and @runner.phased_restart
159
+ log "* phased-restart called but not available, restarting normally."
160
+ return restart
161
+ end
162
+ true
163
+ end
164
+
165
+ # Run the server. This blocks until the server is stopped
166
+ def run
167
+ @config.clamp
168
+
169
+ @config.plugins.fire_starts self
170
+
171
+ setup_signals
172
+ set_process_title
173
+ @runner.run
174
+
175
+ case @status
176
+ when :halt
177
+ log "* Stopping immediately!"
178
+ when :run, :stop
179
+ graceful_stop
180
+ when :restart
181
+ log "* Restarting..."
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
+ private
204
+
205
+ def reload_worker_directory
206
+ @runner.reload_worker_directory if @runner.respond_to?(:reload_worker_directory)
207
+ end
208
+
209
+ def restart!
210
+ @config.run_hooks :on_restart, self
211
+
212
+ if Puma.jruby?
213
+ close_binder_listeners
214
+
215
+ require 'puma/jruby_restart'
216
+ JRubyRestart.chdir_exec(@restart_dir, restart_args)
217
+ elsif Puma.windows?
218
+ close_binder_listeners
219
+
220
+ argv = restart_args
221
+ Dir.chdir(@restart_dir)
222
+ Kernel.exec(*argv)
223
+ else
224
+ redirects = {:close_others => true}
225
+ @binder.listeners.each_with_index do |(l, io), i|
226
+ ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
227
+ redirects[io.to_i] = io.to_i
228
+ end
229
+
230
+ argv = restart_args
231
+ Dir.chdir(@restart_dir)
232
+ argv += [redirects] if RUBY_VERSION >= '1.9'
233
+ Kernel.exec(*argv)
234
+ end
235
+ end
236
+
237
+ def prune_bundler
238
+ return unless defined?(Bundler)
239
+ puma = Bundler.rubygems.loaded_specs("puma")
240
+ dirs = puma.require_paths.map { |x| File.join(puma.full_gem_path, x) }
241
+ puma_lib_dir = dirs.detect { |x| File.exist? File.join(x, '../bin/puma-wild') }
242
+
243
+ unless puma_lib_dir
244
+ log "! Unable to prune Bundler environment, continuing"
245
+ return
246
+ end
247
+
248
+ deps = puma.runtime_dependencies.map do |d|
249
+ spec = Bundler.rubygems.loaded_specs(d.name)
250
+ "#{d.name}:#{spec.version.to_s}"
251
+ end
252
+
253
+ log '* Pruning Bundler environment'
254
+ home = ENV['GEM_HOME']
255
+ Bundler.with_clean_env do
256
+ ENV['GEM_HOME'] = home
257
+ ENV['PUMA_BUNDLER_PRUNED'] = '1'
258
+ wild = File.expand_path(File.join(puma_lib_dir, "../bin/puma-wild"))
259
+ args = [Gem.ruby, wild, '-I', dirs.join(':'), deps.join(',')] + @original_argv
260
+ Kernel.exec(*args)
261
+ end
262
+ end
263
+
264
+ def log(str)
265
+ @events.log str
266
+ end
267
+
268
+ def clustered?
269
+ (@options[:workers] || 0) > 0
270
+ end
271
+
272
+ def unsupported(str)
273
+ @events.error(str)
274
+ raise UnsupportedOption
275
+ end
276
+
277
+ def graceful_stop
278
+ @runner.stop_blocked
279
+ log "=== puma shutdown: #{Time.now} ==="
280
+ log "- Goodbye!"
281
+ end
282
+
283
+ def set_process_title
284
+ Process.respond_to?(:setproctitle) ? Process.setproctitle(title) : $0 = title
285
+ end
286
+
287
+ def title
288
+ buffer = "puma #{Puma::Const::VERSION} (#{@options[:binds].join(',')})"
289
+ buffer << " [#{@options[:tag]}]" if @options[:tag] && !@options[:tag].empty?
290
+ buffer
291
+ end
292
+
293
+ def set_rack_environment
294
+ @options[:environment] = environment
295
+ ENV['RACK_ENV'] = environment
296
+ end
297
+
298
+ def environment
299
+ @environment
300
+ end
301
+
302
+ def prune_bundler?
303
+ @options[:prune_bundler] && clustered? && !@options[:preload_app]
304
+ end
305
+
306
+ def close_binder_listeners
307
+ @binder.listeners.each do |l, io|
308
+ io.close
309
+ uri = URI.parse(l)
310
+ next unless uri.scheme == 'unix'
311
+ File.unlink("#{uri.host}#{uri.path}")
312
+ end
313
+ end
314
+
315
+
316
+ def generate_restart_data
317
+ # Use the same trick as unicorn, namely favor PWD because
318
+ # it will contain an unresolved symlink, useful for when
319
+ # the pwd is /data/releases/current.
320
+ if dir = ENV['PWD']
321
+ s_env = File.stat(dir)
322
+ s_pwd = File.stat(Dir.pwd)
323
+
324
+ if s_env.ino == s_pwd.ino and (Puma.jruby? or s_env.dev == s_pwd.dev)
325
+ @restart_dir = dir
326
+ @config.configure { |c| c.worker_directory dir }
327
+ end
328
+ end
329
+
330
+ @restart_dir ||= Dir.pwd
331
+
332
+ require 'rubygems'
333
+
334
+ # if $0 is a file in the current directory, then restart
335
+ # it the same, otherwise add -S on there because it was
336
+ # picked up in PATH.
337
+ #
338
+ if File.exist?($0)
339
+ arg0 = [Gem.ruby, $0]
340
+ else
341
+ arg0 = [Gem.ruby, "-S", $0]
342
+ end
343
+
344
+ # Detect and reinject -Ilib from the command line
345
+ lib = File.expand_path "lib"
346
+ arg0[1,0] = ["-I", lib] if $:[0] == lib
347
+
348
+ if defined? Puma::WILD_ARGS
349
+ @restart_argv = arg0 + Puma::WILD_ARGS + @original_argv
350
+ else
351
+ @restart_argv = arg0 + @original_argv
352
+ end
353
+ end
354
+
355
+ def setup_signals
356
+ begin
357
+ Signal.trap "SIGUSR2" do
358
+ restart
359
+ end
360
+ rescue Exception
361
+ log "*** SIGUSR2 not implemented, signal based restart unavailable!"
362
+ end
363
+
364
+ unless Puma.jruby?
365
+ begin
366
+ Signal.trap "SIGUSR1" do
367
+ phased_restart
368
+ end
369
+ rescue Exception
370
+ log "*** SIGUSR1 not implemented, signal based restart unavailable!"
371
+ end
372
+ end
373
+
374
+ begin
375
+ Signal.trap "SIGTERM" do
376
+ stop
377
+ end
378
+ rescue Exception
379
+ log "*** SIGTERM not implemented, signal based gracefully stopping unavailable!"
380
+ end
381
+
382
+ begin
383
+ Signal.trap "SIGHUP" do
384
+ @runner.redirect_io
385
+ end
386
+ rescue Exception
387
+ log "*** SIGHUP not implemented, signal based logs reopening unavailable!"
388
+ end
389
+
390
+ if Puma.jruby?
391
+ Signal.trap("INT") do
392
+ @status = :exit
393
+ graceful_stop
394
+ exit
395
+ end
396
+ end
397
+ end
398
+ end
399
+ end