puma 2.0.0.b4-java → 2.0.0.b7-java

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.

@@ -1,3 +1,56 @@
1
+ === 2.0.0.b7 / 2013-03-18
2
+
3
+ * 5 minor enhancements:
4
+
5
+ * Add -q option for :start
6
+ * Add -V, --version
7
+ * Add default Rack handler helper
8
+ * Upstart support
9
+ * Set worker directory from configuration file
10
+
11
+ * 12 bug fixes:
12
+
13
+ * Close the binder in the right place. Fixes #192
14
+ * Handle early term in workers. Fixes #206
15
+ * Make sure that the default port is 80 when the request doesn't include HTTP_X_FORWARDED_PROTO.
16
+ * Prevent Errno::EBADF errors on restart when running ruby 2.0
17
+ * Record the proper @master_pid
18
+ * Respect the header HTTP_X_FORWARDED_PROTO when the host doesn't include a port number.
19
+ * Retry EAGAIN/EWOULDBLOCK during syswrite
20
+ * Run exec properly to restart. Fixes #154
21
+ * Set Rack run_once to false
22
+ * Syncronize all access to @timeouts. Fixes #208
23
+ * Write out the state post-daemonize. Fixes #189
24
+ * Prevent crash when all workers are gone
25
+
26
+ === 2.0.0.b6 / 2013-02-06
27
+
28
+ * 2 minor enhancements:
29
+
30
+ * Add hook for running when a worker boots
31
+ * Advertise the Configuration object for apps to use.
32
+
33
+ * 1 bug fix:
34
+
35
+ * Change directory in working during upgrade. Fixes #185
36
+
37
+ === 2.0.0.b5 / 2013-02-05
38
+
39
+ * 2 major features:
40
+ * Add phased worker upgrade
41
+ * Add support for the rack hijack protocol
42
+
43
+ * 2 minor features:
44
+ * Add -R to specify the restart command
45
+ * Add config file option to specify the restart command
46
+
47
+ * 5 bug fixes:
48
+ * Cleanup pipes properly. Fixes #182
49
+ * Daemonize earlier so that we don't lose app threads. Fixes #183
50
+ * Drain the notification pipe. Fixes #176, thanks @cryo28
51
+ * Move write_pid to after we daemonize. Fixes #180
52
+ * Redirect IO properly and emit message for checkpointing
53
+
1
54
  === 2.0.0.b4 / 2012-12-12
2
55
 
3
56
  * 4 bug fixes:
@@ -44,12 +44,17 @@ lib/puma/java_io_buffer.rb
44
44
  lib/puma/jruby_restart.rb
45
45
  lib/puma/minissl.rb
46
46
  lib/puma/null_io.rb
47
+ lib/puma/rack_default.rb
47
48
  lib/puma/rack_patch.rb
48
49
  lib/puma/reactor.rb
49
50
  lib/puma/server.rb
50
51
  lib/puma/thread_pool.rb
52
+ lib/puma/util.rb
51
53
  lib/rack/handler/puma.rb
52
54
  puma.gemspec
53
- tools/jungle/README.md
54
- tools/jungle/puma
55
- tools/jungle/run-puma
55
+ tools/jungle/init.d/README.md
56
+ tools/jungle/init.d/puma
57
+ tools/jungle/init.d/run-puma
58
+ tools/jungle/upstart/README.md
59
+ tools/jungle/upstart/puma-manager.conf
60
+ tools/jungle/upstart/puma.conf
data/README.md CHANGED
@@ -66,7 +66,7 @@ Puma provides numerous options for controlling the operation of the server. Cons
66
66
  Puma utilizes a dynamic thread pool which you can modify. You can set the minimum and maximum number of threads that are available in the pool with the `-t` (or `--threads`) flag:
67
67
 
68
68
  $ puma -t 8:32
69
-
69
+
70
70
  Puma will automatically scale the number of threads based on how much traffic is present. The current default is `0:16`. Feel free to experiment, but be careful not to set the number of maximum threads to a very large number, as you may exhaust resources on the system (or hit resource limits).
71
71
 
72
72
  ### Binding TCP / Sockets
@@ -95,9 +95,21 @@ Puma comes with a builtin status/control app that can be used query and control
95
95
 
96
96
  This directs puma to start the control server on localhost port 9293. Additionally, all requests to the control server will need to include `token=foo` as a query parameter. This allows for simple authentication. Check out https://github.com/puma/puma/blob/master/lib/puma/app/status.rb to see what the app has available.
97
97
 
98
+ ### Configuration file
99
+
100
+ You can also provide a configuration file which puma will use:
101
+
102
+ $ puma --config /path/to/config
103
+
104
+ or
105
+
106
+ $ puma -C /path/to/config
107
+
108
+ Take the following [sample configuration](https://github.com/puma/puma/blob/master/examples/config.rb) as inspiration or check out [configuration.rb](https://github.com/puma/puma/blob/master/lib/puma/configuration.rb#L138) to see all available options.
109
+
98
110
  ## Restart
99
111
 
100
- Puma includes the ability to restart itself, allowing for new versions to be easily upgraded to. When available (currently anywhere but JRuby), puma performs a "hot restart". This is the same functionality available in *unicorn* and *nginx* which keep the server sockets open between restarts. This makes sure that no pending requests are dropped while the restart is taking place.
112
+ Puma includes the ability to restart itself, allowing for new versions to be easily upgraded to. When available (MRI, Rubinius, JRuby), puma performs a "hot restart". This is the same functionality available in *unicorn* and *nginx* which keep the server sockets open between restarts. This makes sure that no pending requests are dropped while the restart is taking place.
101
113
 
102
114
  To perform a restart, there are 2 builtin mechanism:
103
115
 
@@ -120,7 +132,7 @@ If you start puma with `-S some/path` then you can pass that same path to the `p
120
132
 
121
133
  $ pumactl -S some/path command
122
134
 
123
- or
135
+ or
124
136
 
125
137
  $ pumactl -C url -T token command
126
138
 
@@ -128,9 +140,28 @@ will cause the server to perform a restart. `pumactl` is a simple CLI frontend t
128
140
 
129
141
  Allowed commands: status, restart, halt, stop
130
142
 
131
- ## Managing multiple Pumas / init.d script
143
+ ## Managing multiple Pumas / init.d / upstart scripts
144
+
145
+ If you want an easy way to manage multiple scripts at once check [tools/jungle](https://github.com/puma/puma/tree/master/tools/jungle) for init.d and upstart scripts.
146
+
147
+ ## Capistrano deployment
148
+
149
+ Puma has included Capistrano [deploy script](https://github.com/plentz/puma/blob/master/lib/puma/capistrano.rb), you just need require that:
150
+
151
+ config/deploy.rb
152
+
153
+ ```ruby
154
+ require 'puma/capistrano'
155
+ ```
156
+
157
+ and then
158
+
159
+ ```bash
160
+ $ bunde exec cap puma:start
161
+ $ bunde exec cap puma:restart
162
+ $ bunde exec cap puma:stop
163
+ ```
132
164
 
133
- If you want an easy way to manage multiple scripts at once check [tools/jungle](https://github.com/puma/puma/tree/master/tools/jungle) for an init.d script.
134
165
 
135
166
  ## License
136
167
 
data/Rakefile CHANGED
@@ -19,7 +19,7 @@ HOE = Hoe.spec "puma" do
19
19
 
20
20
  require_ruby_version ">= 1.8.7"
21
21
 
22
- dependency "rack", "~> 1.2"
22
+ dependency "rack", [">= 1.1", "< 2.0"]
23
23
 
24
24
  extra_dev_deps << ["rake-compiler", "~> 0.8.0"]
25
25
  end
@@ -15,7 +15,7 @@ module Puma
15
15
  "rack.errors".freeze => events.stderr,
16
16
  "rack.multithread".freeze => true,
17
17
  "rack.multiprocess".freeze => false,
18
- "rack.run_once".freeze => true,
18
+ "rack.run_once".freeze => false,
19
19
  "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
20
20
 
21
21
  # Rack blows up if this is an empty string, and Rack::Lint
@@ -1,26 +1,28 @@
1
1
  Capistrano::Configuration.instance.load do
2
- after "deploy:stop", "puma:stop"
3
- after "deploy:start", "puma:start"
4
- after "deploy:restart", "puma:restart"
2
+ after 'deploy:stop', 'puma:stop'
3
+ after 'deploy:start', 'puma:start'
4
+ after 'deploy:restart', 'puma:restart'
5
5
 
6
- _cset(:puma_role) { :app }
6
+ _cset(:puma_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec puma" }
7
+ _cset(:pumactl_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec pumactl" }
8
+ _cset(:puma_state) { "#{shared_path}/sockets/puma.state" }
9
+ _cset(:puma_role) { :app }
7
10
 
8
11
  namespace :puma do
9
- desc "Start puma"
12
+ desc 'Start puma'
10
13
  task :start, :roles => lambda { fetch(:puma_role) }, :on_no_matching_servers => :continue do
11
- puma_env = fetch(:rack_env, fetch(:rails_env, "production"))
12
- run "cd #{current_path} && #{fetch(:bundle_cmd, "bundle")} exec puma -d -e #{puma_env} -b 'unix://#{shared_path}/sockets/puma.sock' -S #{shared_path}/sockets/puma.state --control 'unix://#{shared_path}/sockets/pumactl.sock'", :pty => false
14
+ puma_env = fetch(:rack_env, fetch(:rails_env, 'production'))
15
+ run "cd #{current_path} && #{fetch(:puma_cmd)} -q -d -e #{puma_env} -b 'unix://#{shared_path}/sockets/puma.sock' -S #{fetch(:puma_state)} --control 'unix://#{shared_path}/sockets/pumactl.sock'", :pty => false
13
16
  end
14
17
 
15
- desc "Stop puma"
18
+ desc 'Stop puma'
16
19
  task :stop, :roles => lambda { fetch(:puma_role) }, :on_no_matching_servers => :continue do
17
- run "cd #{current_path} && #{fetch(:bundle_cmd, "bundle")} exec pumactl -S #{shared_path}/sockets/puma.state stop"
20
+ run "cd #{current_path} && #{fetch(:pumactl_cmd)} -S fetch(:puma_state) stop"
18
21
  end
19
22
 
20
- desc "Restart puma"
23
+ desc 'Restart puma'
21
24
  task :restart, :roles => lambda { fetch(:puma_role) }, :on_no_matching_servers => :continue do
22
- run "cd #{current_path} && #{fetch(:bundle_cmd, "bundle")} exec pumactl -S #{shared_path}/sockets/puma.state restart"
25
+ run "cd #{current_path} && #{fetch(:pumactl_cmd)} -S fetch(:puma_state) restart"
23
26
  end
24
-
25
27
  end
26
28
  end
@@ -7,6 +7,7 @@ require 'puma/configuration'
7
7
  require 'puma/binder'
8
8
  require 'puma/detect'
9
9
  require 'puma/daemon_ext'
10
+ require 'puma/util'
10
11
 
11
12
  require 'rack/commonlogger'
12
13
  require 'rack/utils'
@@ -27,6 +28,7 @@ module Puma
27
28
  @stdout = stdout
28
29
  @stderr = stderr
29
30
 
31
+ @phase = 0
30
32
  @workers = []
31
33
 
32
34
  @events = Events.new @stdout, @stderr
@@ -35,6 +37,10 @@ module Puma
35
37
  @status = nil
36
38
 
37
39
  @restart = false
40
+ @phased_state = :idle
41
+
42
+ @io_redirected = false
43
+
38
44
 
39
45
  ENV['NEWRELIC_DISPATCHER'] ||= "puma"
40
46
 
@@ -60,6 +66,7 @@ module Puma
60
66
 
61
67
  if s_env.ino == s_pwd.ino and s_env.dev == s_pwd.dev
62
68
  @restart_dir = dir
69
+ @options[:worker_directory] = dir
63
70
  end
64
71
  end
65
72
 
@@ -82,6 +89,10 @@ module Puma
82
89
  arg0 = [Gem.ruby, "-S", $0]
83
90
  end
84
91
 
92
+ # Detect and reinject -Ilib from the command line
93
+ lib = File.expand_path "lib"
94
+ arg0[1,0] = ["-I", lib] if $:[0] == lib
95
+
85
96
  @restart_argv = arg0 + ARGV
86
97
  end
87
98
  end
@@ -104,10 +115,12 @@ module Puma
104
115
  end
105
116
 
106
117
  require 'puma/jruby_restart'
107
- JRubyRestart.chdir_exec(@restart_dir, Gem.ruby, *@restart_argv)
118
+ JRubyRestart.chdir_exec(@restart_dir, @restart_argv)
108
119
  else
120
+ redirects = {}
109
121
  @binder.listeners.each_with_index do |(l,io),i|
110
122
  ENV["PUMA_INHERIT_#{i}"] = "#{io.to_i}:#{l}"
123
+ redirects[io.to_i] = io.to_i
111
124
  end
112
125
 
113
126
  if cmd = @options[:restart_cmd]
@@ -117,6 +130,8 @@ module Puma
117
130
  end
118
131
 
119
132
  Dir.chdir @restart_dir
133
+
134
+ argv += [redirects] unless RUBY_VERSION < '1.9'
120
135
  Kernel.exec(*argv)
121
136
  end
122
137
  end
@@ -164,6 +179,7 @@ module Puma
164
179
  :binds => [],
165
180
  :workers => 0,
166
181
  :daemon => false,
182
+ :worker_boot => [],
167
183
  :environment => "development"
168
184
  }
169
185
 
@@ -201,6 +217,7 @@ module Puma
201
217
 
202
218
  o.on "--dir DIR", "Change to DIR before starting" do |d|
203
219
  @options[:directory] = d.to_s
220
+ @options[:worker_directory] = d.to_s
204
221
  end
205
222
 
206
223
  o.on "-e", "--environment ENVIRONMENT",
@@ -225,7 +242,7 @@ module Puma
225
242
  @options[:quiet] = true
226
243
  end
227
244
 
228
- o.on "--restart-cmd CMD",
245
+ o.on "-R", "--restart-cmd CMD",
229
246
  "The puma command to run during a hot restart",
230
247
  "Default: inferred" do |cmd|
231
248
  @options[:restart_cmd] = cmd
@@ -246,6 +263,11 @@ module Puma
246
263
  end
247
264
  end
248
265
 
266
+ o.on "-V", "--version", "Print the version information" do
267
+ puts "puma version #{Puma::Const::VERSION}"
268
+ exit 1
269
+ end
270
+
249
271
  o.on "-w", "--workers COUNT",
250
272
  "Activate cluster mode: How many worker processes to create" do |arg|
251
273
  unsupported "-w not supported on JRuby and Windows",
@@ -291,13 +313,16 @@ module Puma
291
313
  end
292
314
 
293
315
  def write_state
316
+ write_pid
317
+
294
318
  require 'yaml'
295
319
 
296
320
  if path = @options[:state]
297
321
  state = { "pid" => Process.pid }
298
322
 
299
323
  cfg = @config.dup
300
- cfg.options.delete :on_restart
324
+
325
+ [ :logger, :worker_boot, :on_restart ].each { |o| cfg.options.delete o }
301
326
 
302
327
  state["config"] = cfg
303
328
 
@@ -316,6 +341,10 @@ module Puma
316
341
  end
317
342
 
318
343
  @config = Puma::Configuration.new @options
344
+
345
+ # Advertise the Configuration
346
+ Puma.cli_config = @config
347
+
319
348
  @config.load
320
349
  end
321
350
 
@@ -333,11 +362,14 @@ module Puma
333
362
  append = @options[:redirect_append]
334
363
 
335
364
  if stdout
365
+ @io_redirected = true
336
366
  STDOUT.reopen stdout, (append ? "a" : "w")
367
+ STDOUT.puts "=== puma startup: #{Time.now} ==="
337
368
  end
338
369
 
339
370
  if stderr
340
- STDOUT.reopen stderr, (append ? "a" : "w")
371
+ STDERR.reopen stderr, (append ? "a" : "w")
372
+ STDERR.puts "=== puma startup: #{Time.now} ==="
341
373
  end
342
374
  end
343
375
 
@@ -364,9 +396,6 @@ module Puma
364
396
 
365
397
  set_rack_environment
366
398
 
367
- write_pid
368
- write_state
369
-
370
399
  if clustered
371
400
  run_cluster
372
401
  else
@@ -384,6 +413,12 @@ module Puma
384
413
 
385
414
  @binder.parse @options[:binds], self
386
415
 
416
+ if @options[:daemon]
417
+ Process.daemon(true, @io_redirected)
418
+ end
419
+
420
+ write_state
421
+
387
422
  server = Puma::Server.new @config.app, @events
388
423
  server.binder = @binder
389
424
  server.min_threads = min_t
@@ -441,9 +476,7 @@ module Puma
441
476
  log "*** Sorry signal SIGTERM not implemented, gracefully stopping feature disabled!"
442
477
  end
443
478
 
444
- if @options[:daemon]
445
- Process.daemon(true)
446
- else
479
+ unless @options[:daemon]
447
480
  log "Use Ctrl-C to stop"
448
481
  end
449
482
 
@@ -469,31 +502,51 @@ module Puma
469
502
  end
470
503
  end
471
504
 
472
- def worker
505
+ def worker(upgrade)
473
506
  $0 = "puma: cluster worker: #{@master_pid}"
474
507
  Signal.trap "SIGINT", "IGNORE"
475
508
 
509
+ @master_read.close
476
510
  @suicide_pipe.close
477
511
 
478
512
  Thread.new do
479
513
  IO.select [@check_pipe]
480
- log "! Detected parent died, dieing"
514
+ log "! Detected parent died, dying"
481
515
  exit! 1
482
516
  end
483
517
 
518
+ # Be sure to change the directory again before loading
519
+ # the app. This way we can pick up new code.
520
+ if upgrade
521
+ if dir = @options[:worker_directory]
522
+ log "+ Changing to #{dir}"
523
+ Dir.chdir dir
524
+ end
525
+ end
526
+
527
+ # Invoke any worker boot hooks so they can get
528
+ # things in shape before booting the app.
529
+ hooks = @options[:worker_boot]
530
+ hooks.each { |h| h.call }
531
+
484
532
  min_t = @options[:min_threads]
485
533
  max_t = @options[:max_threads]
486
534
 
487
535
  server = Puma::Server.new @config.app, @events
488
536
  server.min_threads = min_t
489
537
  server.max_threads = max_t
490
- server.binder = @binder
538
+ server.inherit_binder @binder
491
539
 
492
540
  Signal.trap "SIGTERM" do
493
541
  server.stop
494
542
  end
495
543
 
544
+ @worker_write << "b#{Process.pid}\n"
545
+
496
546
  server.run.join
547
+
548
+ ensure
549
+ @worker_write.close
497
550
  end
498
551
 
499
552
  def stop_workers
@@ -509,12 +562,27 @@ module Puma
509
562
  end
510
563
  end
511
564
 
565
+ def start_phased_restart
566
+ @phase += 1
567
+ log "- Starting phased worker restart, phase: #{@phase}"
568
+ end
569
+
512
570
  class Worker
513
- def initialize(pid)
571
+ def initialize(pid, phase)
514
572
  @pid = pid
573
+ @phase = phase
574
+ @stage = :started
515
575
  end
516
576
 
517
- attr_reader :pid
577
+ attr_reader :pid, :phase
578
+
579
+ def booted?
580
+ @stage == :booted
581
+ end
582
+
583
+ def boot!
584
+ @stage = :booted
585
+ end
518
586
 
519
587
  def term
520
588
  begin
@@ -527,15 +595,25 @@ module Puma
527
595
  def spawn_workers
528
596
  diff = @options[:workers] - @workers.size
529
597
 
598
+ upgrade = (@phased_state == :waiting)
599
+
530
600
  diff.times do
531
- pid = fork { worker }
601
+ pid = fork { worker(upgrade) }
532
602
  debug "Spawned worker: #{pid}"
533
- @workers << Worker.new(pid)
603
+ @workers << Worker.new(pid, @phase)
604
+ end
605
+
606
+ if diff > 0
607
+ @phased_state = :idle
534
608
  end
535
609
  end
536
610
 
611
+ def all_workers_booted?
612
+ @workers.count { |w| !w.booted? } == 0
613
+ end
614
+
537
615
  def check_workers
538
- while true
616
+ while @workers.any?
539
617
  pid = Process.waitpid(-1, Process::WNOHANG)
540
618
  break unless pid
541
619
 
@@ -543,6 +621,20 @@ module Puma
543
621
  end
544
622
 
545
623
  spawn_workers
624
+
625
+ if @phased_state == :idle && all_workers_booted?
626
+ # If we're running at proper capacity, check to see if
627
+ # we need to phase any workers out (which will restart
628
+ # in the right phase).
629
+ #
630
+ w = @workers.find { |x| x.phase != @phase }
631
+
632
+ if w
633
+ @phased_state = :waiting
634
+ log "- Stopping #{w.pid} for phased upgrade..."
635
+ w.term
636
+ end
637
+ end
546
638
  end
547
639
 
548
640
  def run_cluster
@@ -553,9 +645,7 @@ module Puma
553
645
 
554
646
  @binder.parse @options[:binds], self
555
647
 
556
- @master_pid = Process.pid
557
-
558
- read, write = IO.pipe
648
+ read, write = Puma::Util.pipe
559
649
 
560
650
  Signal.trap "SIGCHLD" do
561
651
  write.write "!"
@@ -572,9 +662,29 @@ module Puma
572
662
  rescue Exception
573
663
  end
574
664
 
665
+ master_pid = Process.pid
666
+
575
667
  begin
576
668
  Signal.trap "SIGTERM" do
577
- stop = true
669
+ # The worker installs there own SIGTERM when booted.
670
+ # Until then, this is run by the worker and the worker
671
+ # should just exit if they get it.
672
+ if Process.pid != master_pid
673
+ log "Early termination of worker"
674
+ exit! 0
675
+ else
676
+ stop = true
677
+ write.write "!"
678
+ end
679
+ end
680
+ rescue Exception
681
+ end
682
+
683
+ phased_restart = false
684
+
685
+ begin
686
+ Signal.trap "SIGUSR1" do
687
+ phased_restart = true
578
688
  write.write "!"
579
689
  end
580
690
  rescue Exception
@@ -585,16 +695,21 @@ module Puma
585
695
  # master has exited and @suicide_pipe has been automatically
586
696
  # closed.
587
697
  #
588
- @check_pipe, @suicide_pipe = IO.pipe
698
+ @check_pipe, @suicide_pipe = Puma::Util.pipe
589
699
 
590
700
  if @options[:daemon]
591
- Process.daemon(true)
701
+ Process.daemon(true, @io_redirected)
592
702
  else
593
703
  log "Use Ctrl-C to stop"
594
704
  end
595
705
 
706
+ @master_pid = Process.pid
707
+
596
708
  redirect_io
597
709
 
710
+ write_state
711
+
712
+ @master_read, @worker_write = read, write
598
713
  spawn_workers
599
714
 
600
715
  Signal.trap "SIGINT" do
@@ -605,8 +720,30 @@ module Puma
605
720
  begin
606
721
  while !stop
607
722
  begin
608
- IO.select([read], nil, nil, 5)
723
+ res = IO.select([read], nil, nil, 5)
724
+
725
+ if res
726
+ req = read.read_nonblock(1)
727
+
728
+ if req == "b"
729
+ pid = read.gets.to_i
730
+ w = @workers.find { |x| x.pid == pid }
731
+ if w
732
+ w.boot!
733
+ log "- Worker #{pid} booted, phase: #{w.phase}"
734
+ else
735
+ log "! Out-of-sync worker list, no #{pid} worker"
736
+ end
737
+ end
738
+ end
739
+
609
740
  check_workers
741
+
742
+ if phased_restart
743
+ start_phased_restart
744
+ phased_restart = false
745
+ end
746
+
610
747
  rescue Interrupt
611
748
  stop = true
612
749
  end
@@ -615,6 +752,10 @@ module Puma
615
752
  stop_workers
616
753
  ensure
617
754
  delete_pidfile
755
+ @check_pipe.close
756
+ @suicide_pipe.close
757
+ read.close
758
+ write.close
618
759
  end
619
760
 
620
761
  if @restart