puma 1.6.3 → 2.0.0.b1

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.

@@ -4,6 +4,7 @@ require 'uri'
4
4
  require 'puma/server'
5
5
  require 'puma/const'
6
6
  require 'puma/configuration'
7
+ require 'puma/binder'
7
8
  require 'puma/detect'
8
9
 
9
10
  require 'rack/commonlogger'
@@ -20,10 +21,13 @@ module Puma
20
21
  # this object will report status on.
21
22
  #
22
23
  def initialize(argv, stdout=STDOUT, stderr=STDERR)
24
+ @debug = false
23
25
  @argv = argv
24
26
  @stdout = stdout
25
27
  @stderr = stderr
26
28
 
29
+ @workers = []
30
+
27
31
  @events = Events.new @stdout, @stderr
28
32
 
29
33
  @server = nil
@@ -31,26 +35,12 @@ module Puma
31
35
 
32
36
  @restart = false
33
37
 
34
- @listeners = []
35
-
36
38
  setup_options
37
39
 
38
40
  generate_restart_data
39
41
 
40
- @inherited_fds = {}
41
- remove = []
42
-
43
- ENV.each do |k,v|
44
- if k =~ /PUMA_INHERIT_\d+/
45
- fd, url = v.split(":", 2)
46
- @inherited_fds[url] = fd.to_i
47
- remove << k
48
- end
49
- end
50
-
51
- remove.each do |k|
52
- ENV.delete k
53
- end
42
+ @binder = Binder.new(@events)
43
+ @binder.import_from_env
54
44
  end
55
45
 
56
46
  def restart_on_stop!
@@ -98,8 +88,8 @@ module Puma
98
88
  blk.call self
99
89
  end
100
90
 
101
- if IS_JRUBY
102
- @listeners.each_with_index do |(str,io),i|
91
+ if jruby?
92
+ @binder.listeners.each_with_index do |(str,io),i|
103
93
  io.close
104
94
 
105
95
  # We have to unlink a unix socket path that's not being used
@@ -113,7 +103,7 @@ module Puma
113
103
  require 'puma/jruby_restart'
114
104
  JRubyRestart.chdir_exec(@restart_dir, Gem.ruby, *@restart_argv)
115
105
  else
116
- @listeners.each_with_index do |(l,io),i|
106
+ @binder.listeners.each_with_index do |(l,io),i|
117
107
  ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
118
108
  end
119
109
 
@@ -140,6 +130,26 @@ module Puma
140
130
  @events.error str
141
131
  end
142
132
 
133
+ def debug(str)
134
+ if @options[:debug]
135
+ @events.log "- #{str}"
136
+ end
137
+ end
138
+
139
+ def jruby?
140
+ IS_JRUBY
141
+ end
142
+
143
+ def windows?
144
+ RUBY_PLATFORM =~ /mswin32|ming32/
145
+ end
146
+
147
+ def unsupported(str, cond=true)
148
+ return unless cond
149
+ @events.error str
150
+ raise UnsupportedOption
151
+ end
152
+
143
153
  # Build the OptionParser object to handle the available options.
144
154
  #
145
155
  def setup_options
@@ -147,11 +157,13 @@ module Puma
147
157
  :min_threads => 0,
148
158
  :max_threads => 16,
149
159
  :quiet => false,
150
- :binds => []
160
+ :debug => false,
161
+ :binds => [],
162
+ :workers => 0
151
163
  }
152
164
 
153
165
  @parser = OptionParser.new do |o|
154
- o.on "-b", "--bind URI", "URI to bind to (tcp:// and unix:// only)" do |arg|
166
+ o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
155
167
  @options[:binds] << arg
156
168
  end
157
169
 
@@ -176,6 +188,10 @@ module Puma
176
188
  @options[:quiet] = true
177
189
  end
178
190
 
191
+ o.on "--debug", "Log lowlevel debugging information" do
192
+ @options[:debug] = true
193
+ end
194
+
179
195
  o.on "-S", "--state PATH", "Where to store the state details" do |arg|
180
196
  @options[:state] = arg
181
197
  end
@@ -184,8 +200,8 @@ module Puma
184
200
  "Use 'auto' to use temp unix server" do |arg|
185
201
  if arg
186
202
  @options[:control_url] = arg
187
- elsif IS_JRUBY
188
- raise NotImplementedError, "No default url available on JRuby"
203
+ elsif jruby?
204
+ unsupported "No default url available on JRuby"
189
205
  end
190
206
  end
191
207
 
@@ -205,6 +221,14 @@ module Puma
205
221
  end
206
222
  end
207
223
 
224
+ o.on "-w", "--workers COUNT",
225
+ "Activate cluster mode: How many worker processes to create" do |arg|
226
+ unsupported "-w not supported on JRuby and Windows",
227
+ jruby? || windows?
228
+
229
+ @options[:workers] = arg.to_i
230
+ end
231
+
208
232
  o.on "--restart-cmd CMD",
209
233
  "The puma command to run during a hot restart",
210
234
  "Default: inferred" do |cmd|
@@ -291,19 +315,39 @@ module Puma
291
315
  # for it to finish.
292
316
  #
293
317
  def run
294
- parse_options
318
+ begin
319
+ parse_options
320
+ rescue UnsupportedOption
321
+ exit 1
322
+ end
295
323
 
296
- set_rack_environment
324
+ clustered = @options[:workers] > 0
325
+
326
+ if clustered
327
+ @events = PidEvents.new STDOUT, STDERR
328
+ @options[:logger] = @events
329
+ end
297
330
 
298
- app = @config.app
331
+ set_rack_environment
299
332
 
300
333
  write_pid
301
334
  write_state
302
335
 
336
+ @binder.parse @options[:binds], self
337
+
338
+ if clustered
339
+ run_cluster
340
+ else
341
+ run_single
342
+ end
343
+ end
344
+
345
+ def run_single
303
346
  min_t = @options[:min_threads]
304
347
  max_t = @options[:max_threads]
305
348
 
306
- server = Puma::Server.new app, @events
349
+ server = Puma::Server.new @config.app, @events
350
+ server.binder = @binder
307
351
  server.min_threads = min_t
308
352
  server.max_threads = max_t
309
353
 
@@ -311,93 +355,6 @@ module Puma
311
355
  log "* Min threads: #{min_t}, max threads: #{max_t}"
312
356
  log "* Environment: #{ENV['RACK_ENV']}"
313
357
 
314
- @options[:binds].each do |str|
315
- uri = URI.parse str
316
- case uri.scheme
317
- when "tcp"
318
- if fd = @inherited_fds.delete(str)
319
- log "* Inherited #{str}"
320
- io = server.inherit_tcp_listener uri.host, uri.port, fd
321
- else
322
- log "* Listening on #{str}"
323
- io = server.add_tcp_listener uri.host, uri.port
324
- end
325
-
326
- @listeners << [str, io]
327
- when "unix"
328
- path = "#{uri.host}#{uri.path}"
329
-
330
- if fd = @inherited_fds.delete(str)
331
- log "* Inherited #{str}"
332
- io = server.inherit_unix_listener path, fd
333
- else
334
- log "* Listening on #{str}"
335
-
336
- umask = nil
337
-
338
- if uri.query
339
- params = Rack::Utils.parse_query uri.query
340
- if u = params['umask']
341
- # Use Integer() to respect the 0 prefix as octal
342
- umask = Integer(u)
343
- end
344
- end
345
-
346
- io = server.add_unix_listener path, umask
347
- end
348
-
349
- @listeners << [str, io]
350
- when "ssl"
351
- params = Rack::Utils.parse_query uri.query
352
- require 'openssl'
353
-
354
- ctx = OpenSSL::SSL::SSLContext.new
355
- unless params['key']
356
- error "Please specify the SSL key via 'key='"
357
- end
358
-
359
- ctx.key = OpenSSL::PKey::RSA.new File.read(params['key'])
360
-
361
- unless params['cert']
362
- error "Please specify the SSL cert via 'cert='"
363
- end
364
-
365
- ctx.cert = OpenSSL::X509::Certificate.new File.read(params['cert'])
366
-
367
- ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
368
-
369
- if fd = @inherited_fds.delete(str)
370
- log "* Inherited #{str}"
371
- io = server.inherited_ssl_listener fd, ctx
372
- else
373
- log "* Listening on #{str}"
374
- io = server.add_ssl_listener uri.host, uri.port, ctx
375
- end
376
-
377
- @listeners << [str, io]
378
- else
379
- error "Invalid URI: #{str}"
380
- end
381
- end
382
-
383
- # If we inherited fds but didn't use them (because of a
384
- # configuration change), then be sure to close them.
385
- @inherited_fds.each do |str, fd|
386
- log "* Closing unused inherited connection: #{str}"
387
-
388
- begin
389
- IO.for_fd(fd).close
390
- rescue SystemCallError
391
- end
392
-
393
- # We have to unlink a unix socket path that's not being used
394
- uri = URI.parse str
395
- if uri.scheme == "unix"
396
- path = "#{uri.host}#{uri.path}"
397
- File.unlink path
398
- end
399
- end
400
-
401
358
  @server = server
402
359
 
403
360
  if str = @options[:control_url]
@@ -465,6 +422,147 @@ module Puma
465
422
  end
466
423
  end
467
424
 
425
+ def worker
426
+ $0 = "puma: cluster worker: #{@master_pid}"
427
+ Signal.trap "SIGINT", "IGNORE"
428
+
429
+ @suicide_pipe.close
430
+
431
+ Thread.new do
432
+ IO.select [@check_pipe]
433
+ log "! Detected parent died, dieing"
434
+ exit! 1
435
+ end
436
+
437
+ min_t = @options[:min_threads]
438
+ max_t = @options[:max_threads]
439
+
440
+ server = Puma::Server.new @config.app, @events
441
+ server.min_threads = min_t
442
+ server.max_threads = max_t
443
+ server.binder = @binder
444
+
445
+ Signal.trap "SIGTERM" do
446
+ server.stop
447
+ end
448
+
449
+ server.run.join
450
+ end
451
+
452
+ def stop_workers
453
+ log "- Gracefully shutting down workers..."
454
+ @workers.each { |x| x.term }
455
+
456
+ begin
457
+ Process.waitall
458
+ rescue Interrupt
459
+ log "! Cancelled waiting for workers"
460
+ else
461
+ log "- Goodbye!"
462
+ end
463
+ end
464
+
465
+ class Worker
466
+ def initialize(pid)
467
+ @pid = pid
468
+ end
469
+
470
+ attr_reader :pid
471
+
472
+ def term
473
+ begin
474
+ Process.kill "TERM", @pid
475
+ rescue Errno::ESRCH
476
+ end
477
+ end
478
+ end
479
+
480
+ def spawn_workers
481
+ diff = @options[:workers] - @workers.size
482
+
483
+ diff.times do
484
+ pid = fork { worker }
485
+ debug "Spawned worker: #{pid}"
486
+ @workers << Worker.new(pid)
487
+ end
488
+ end
489
+
490
+ def check_workers
491
+ while true
492
+ pid = Process.waitpid(-1, Process::WNOHANG)
493
+ break unless pid
494
+
495
+ @workers.delete_if { |w| w.pid == pid }
496
+ end
497
+
498
+ spawn_workers
499
+ end
500
+
501
+ def run_cluster
502
+ log "Puma #{Puma::Const::PUMA_VERSION} starting in cluster mode..."
503
+ log "* Process workers: #{@options[:workers]}"
504
+ log "* Min threads: #{@options[:min_threads]}, max threads: #{@options[:max_threads]}"
505
+ log "* Environment: #{ENV['RACK_ENV']}"
506
+
507
+ @master_pid = Process.pid
508
+
509
+ read, write = IO.pipe
510
+
511
+ Signal.trap "SIGCHLD" do
512
+ write.write "!"
513
+ end
514
+
515
+ stop = false
516
+
517
+ begin
518
+ Signal.trap "SIGUSR2" do
519
+ @restart = true
520
+ stop = true
521
+ write.write "!"
522
+ end
523
+ rescue Exception
524
+ end
525
+
526
+ begin
527
+ Signal.trap "SIGTERM" do
528
+ stop = true
529
+ write.write "!"
530
+ end
531
+ rescue Exception
532
+ end
533
+
534
+ # Used by the workers to detect if the master process dies.
535
+ # If select says that @check_pipe is ready, it's because the
536
+ # master has exited and @suicide_pipe has been automatically
537
+ # closed.
538
+ #
539
+ @check_pipe, @suicide_pipe = IO.pipe
540
+
541
+ spawn_workers
542
+
543
+ log "* Use Ctrl-C to stop"
544
+
545
+ begin
546
+ while !stop
547
+ begin
548
+ IO.select([read], nil, nil, 5)
549
+ check_workers
550
+ rescue Interrupt
551
+ stop = true
552
+ end
553
+ end
554
+
555
+ stop_workers
556
+ ensure
557
+ delete_pidfile
558
+ end
559
+
560
+ if @restart
561
+ log "* Restarting..."
562
+ restart!
563
+ end
564
+ end
565
+
468
566
  def stop
469
567
  @server.stop(true) if @server
470
568
  delete_pidfile
@@ -48,7 +48,7 @@ module Puma
48
48
  @timeout_at = Time.now + val
49
49
  end
50
50
 
51
- def reset
51
+ def reset(fast_check=true)
52
52
  @parser.reset
53
53
  @read_header = true
54
54
  @env = @proto_env.dup
@@ -67,6 +67,9 @@ module Puma
67
67
  end
68
68
 
69
69
  return false
70
+ elsif fast_check &&
71
+ IO.select([@to_io], nil, nil, FAST_TRACK_KA_TIMEOUT)
72
+ return try_to_finish
70
73
  end
71
74
  end
72
75
 
@@ -124,7 +127,11 @@ module Puma
124
127
  def try_to_finish
125
128
  return read_body unless @read_header
126
129
 
127
- data = @io.readpartial(CHUNK_SIZE)
130
+ begin
131
+ data = @io.read_nonblock(CHUNK_SIZE)
132
+ rescue Errno::EAGAIN
133
+ return false
134
+ end
128
135
 
129
136
  if @buffer
130
137
  @buffer << data
@@ -204,7 +211,11 @@ module Puma
204
211
  want = remain
205
212
  end
206
213
 
207
- chunk = @io.readpartial(want)
214
+ begin
215
+ chunk = @io.read_nonblock(want)
216
+ rescue Errno::EAGAIN
217
+ return false
218
+ end
208
219
 
209
220
  # No chunk means a closed socket
210
221
  unless chunk
@@ -229,5 +240,19 @@ module Puma
229
240
 
230
241
  false
231
242
  end
243
+
244
+ def write_400
245
+ begin
246
+ @io << ERROR_400_RESPONSE
247
+ rescue StandardError
248
+ end
249
+ end
250
+
251
+ def write_500
252
+ begin
253
+ @io << ERROR_500_RESPONSE
254
+ rescue StandardError
255
+ end
256
+ end
232
257
  end
233
258
  end