puma 2.5.1 → 2.6.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.

@@ -1,3 +1,16 @@
1
+ === 2.6.0 / 2013-09-13
2
+
3
+ * 2 minor features:
4
+ * Add support for event hooks
5
+ ** Add a hook for state transitions
6
+ * Add phased restart to capistrano recipe.
7
+
8
+ * 4 bug fixes:
9
+ * Convince workers to stop by SIGKILL after timeout
10
+ * Define RSTRING_NOT_MODIFIED for Rubinius performance
11
+ * Handle BrokenPipe, StandardError and IOError in fat_wrote and break out
12
+ * Return success status to the invoking environment
13
+
1
14
  === 2.5.1 / 2013-08-13
2
15
 
3
16
  * 2 bug fixes:
data/README.md CHANGED
@@ -202,6 +202,7 @@ and then
202
202
  $ bundle exec cap puma:start
203
203
  $ bundle exec cap puma:restart
204
204
  $ bundle exec cap puma:stop
205
+ $ bundle exec cap puma:phased_restart
205
206
  ```
206
207
 
207
208
 
@@ -6,6 +6,7 @@
6
6
  #ifndef http11_parser_h
7
7
  #define http11_parser_h
8
8
 
9
+ #define RSTRING_NOT_MODIFIED 1
9
10
  #include "ruby.h"
10
11
 
11
12
  #include <sys/types.h>
@@ -1,3 +1,4 @@
1
+ #define RSTRING_NOT_MODIFIED 1
1
2
  #include "ruby.h"
2
3
 
3
4
  #include <sys/types.h>
@@ -1,3 +1,4 @@
1
+ #define RSTRING_NOT_MODIFIED 1
1
2
  #include <ruby.h>
2
3
  #include <rubyio.h>
3
4
  #include <openssl/bio.h>
@@ -8,34 +8,41 @@ Capistrano::Configuration.instance.load do
8
8
  # v2 but is fixed in v3.
9
9
  shared_children.push('tmp/sockets')
10
10
 
11
- _cset(:puma_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec puma" }
11
+ _cset(:puma_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec puma" }
12
12
  _cset(:pumactl_cmd) { "#{fetch(:bundle_cmd, 'bundle')} exec pumactl" }
13
- _cset(:puma_state) { "#{shared_path}/sockets/puma.state" }
13
+ _cset(:puma_env) { fetch(:rack_env, fetch(:rails_env, 'production')) }
14
+ _cset(:puma_state) { "#{shared_path}/sockets/puma.state" }
14
15
  _cset(:puma_socket) { "unix://#{shared_path}/sockets/puma.sock" }
15
- _cset(:puma_role) { :app }
16
+ _cset(:puma_role) { :app }
16
17
 
17
18
  namespace :puma do
18
19
  desc 'Start puma'
19
- task :start, :roles => lambda { fetch(:puma_role) }, :on_no_matching_servers => :continue do
20
- run "cd #{current_path} && #{fetch(:puma_cmd)} #{start_options}", :pty => false
20
+ task :start, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
21
+ run "cd #{current_path} && #{puma_cmd} #{start_options}", :pty => false
21
22
  end
22
23
 
23
24
  desc 'Stop puma'
24
- task :stop, :roles => lambda { fetch(:puma_role) }, :on_no_matching_servers => :continue do
25
- run "cd #{current_path} && #{fetch(:pumactl_cmd)} -S #{state_path} stop"
25
+ task :stop, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
26
+ run "cd #{current_path} && #{pumactl_cmd} -S #{state_path} stop"
26
27
  end
27
28
 
28
29
  desc 'Restart puma'
29
- task :restart, :roles => lambda { fetch(:puma_role) }, :on_no_matching_servers => :continue do
30
- run "cd #{current_path} && #{fetch(:pumactl_cmd)} -S #{state_path} restart"
30
+ task :restart, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
31
+ run "cd #{current_path} && #{pumactl_cmd} -S #{state_path} restart"
31
32
  end
33
+
34
+ desc 'Restart puma (phased restart)'
35
+ task :phased_restart, :roles => lambda { puma_role }, :on_no_matching_servers => :continue do
36
+ run "cd #{current_path} && #{pumactl_cmd} -S #{state_path} phased-restart"
37
+ end
38
+
32
39
  end
33
40
 
34
41
  def start_options
35
42
  if config_file
36
43
  "-q -d -e #{puma_env} -C #{config_file}"
37
44
  else
38
- "-q -d -e #{puma_env} -b '#{fetch(:puma_socket)}' -S #{state_path} --control 'unix://#{shared_path}/sockets/pumactl.sock'"
45
+ "-q -d -e #{puma_env} -b '#{puma_socket}' -S #{state_path} --control 'unix://#{shared_path}/sockets/pumactl.sock'"
39
46
  end
40
47
  end
41
48
 
@@ -52,7 +59,7 @@ Capistrano::Configuration.instance.load do
52
59
  end
53
60
 
54
61
  def state_path
55
- (config_file ? configuration.options[:state] : nil) || fetch(:puma_state)
62
+ (config_file ? configuration.options[:state] : nil) || puma_state
56
63
  end
57
64
 
58
65
  def configuration
@@ -33,9 +33,10 @@ module Puma
33
33
  @pid = pid
34
34
  @phase = phase
35
35
  @stage = :started
36
+ @signal = "TERM"
36
37
  end
37
38
 
38
- attr_reader :pid, :phase
39
+ attr_reader :pid, :phase, :signal
39
40
 
40
41
  def booted?
41
42
  @stage == :booted
@@ -47,7 +48,13 @@ module Puma
47
48
 
48
49
  def term
49
50
  begin
50
- Process.kill "TERM", @pid
51
+ if @first_term_sent && (Time.new - @first_term_sent) > 30
52
+ @signal = "KILL"
53
+ else
54
+ @first_term_sent ||= Time.new
55
+ end
56
+
57
+ Process.kill @signal, @pid
51
58
  rescue Errno::ESRCH
52
59
  end
53
60
  end
@@ -85,7 +92,7 @@ module Puma
85
92
 
86
93
  spawn_workers
87
94
 
88
- if @phased_state == :idle && all_workers_booted?
95
+ if all_workers_booted?
89
96
  # If we're running at proper capacity, check to see if
90
97
  # we need to phase any workers out (which will restart
91
98
  # in the right phase).
@@ -93,9 +100,13 @@ module Puma
93
100
  w = @workers.find { |x| x.phase != @phase }
94
101
 
95
102
  if w
96
- @phased_state = :waiting
97
- log "- Stopping #{w.pid} for phased upgrade..."
103
+ if @phased_state == :idle
104
+ @phased_state = :waiting
105
+ log "- Stopping #{w.pid} for phased upgrade..."
106
+ end
107
+
98
108
  w.term
109
+ log "- #{w.signal} sent to #{w.pid}..."
99
110
  end
100
111
  end
101
112
  end
@@ -28,8 +28,8 @@ module Puma
28
28
  # too taxing on performance.
29
29
  module Const
30
30
 
31
- PUMA_VERSION = VERSION = "2.5.1".freeze
32
- CODE_NAME = "Astronaut Shoelaces"
31
+ PUMA_VERSION = VERSION = "2.6.0".freeze
32
+ CODE_NAME = "Pantsuit Party"
33
33
 
34
34
  FAST_TRACK_KA_TIMEOUT = 0.2
35
35
 
@@ -23,10 +23,32 @@ module Puma
23
23
  @debug = ENV.key? 'PUMA_DEBUG'
24
24
 
25
25
  @on_booted = []
26
+
27
+ @hooks = Hash.new { |h,k| h[k] = [] }
26
28
  end
27
29
 
28
30
  attr_reader :stdout, :stderr
29
31
 
32
+ # Fire callbacks for the named hook
33
+ #
34
+ def fire(hook, *args)
35
+ @hooks[hook].each { |t| t.call(*args) }
36
+ end
37
+
38
+ # Register a callbock for a given hook
39
+ #
40
+ def register(hook, obj=nil, &blk)
41
+ if obj and blk
42
+ raise "Specify either an object or a block, not both"
43
+ end
44
+
45
+ h = obj || blk
46
+
47
+ @hooks[hook] << h
48
+
49
+ h
50
+ end
51
+
30
52
  # Write +str+ to +@stdout+
31
53
  #
32
54
  def log(str)
@@ -156,6 +156,8 @@ module Puma
156
156
  client.close unless env['detach']
157
157
  end
158
158
 
159
+ @events.fire :state, :running
160
+
159
161
  if background
160
162
  @thread = Thread.new { handle_servers_lopez_mode }
161
163
  return @thread
@@ -194,6 +196,8 @@ module Puma
194
196
  end
195
197
  end
196
198
 
199
+ @events.fire :state, @status
200
+
197
201
  graceful_shutdown if @status == :stop || @status == :restart
198
202
 
199
203
  rescue Exception => e
@@ -207,6 +211,8 @@ module Puma
207
211
  @binder.close
208
212
  end
209
213
  end
214
+
215
+ @events.fire :state, :done
210
216
  end
211
217
  # Runs the server.
212
218
  #
@@ -217,6 +223,8 @@ module Puma
217
223
  def run(background=true)
218
224
  BasicSocket.do_not_reverse_lookup = true
219
225
 
226
+ @events.fire :state, :booting
227
+
220
228
  @status = :run
221
229
 
222
230
  if @mode == :tcp
@@ -255,6 +263,8 @@ module Puma
255
263
  @thread_pool.auto_trim!(@auto_trim_time)
256
264
  end
257
265
 
266
+ @events.fire :state, :running
267
+
258
268
  if background
259
269
  @thread = Thread.new { handle_servers }
260
270
  return @thread
@@ -293,6 +303,8 @@ module Puma
293
303
  end
294
304
  end
295
305
 
306
+ @events.fire :state, @status
307
+
296
308
  graceful_shutdown if @status == :stop || @status == :restart
297
309
  @reactor.clear! if @status == :restart
298
310
 
@@ -308,6 +320,8 @@ module Puma
308
320
  @binder.close
309
321
  end
310
322
  end
323
+
324
+ @events.fire :state, :done
311
325
  end
312
326
 
313
327
  # :nodoc:
@@ -763,6 +777,8 @@ module Puma
763
777
  rescue Errno::EAGAIN, Errno::EWOULDBLOCK
764
778
  IO.select(nil, [io], nil, 1)
765
779
  retry
780
+ rescue Errno::EPIPE, SystemCallError, IOError
781
+ return false
766
782
  end
767
783
 
768
784
  return if n == str.bytesize
@@ -58,7 +58,7 @@ module Puma
58
58
  # Must use exit! so we don't unwind and run the ensures
59
59
  # that will be run by the new child (such as deleting the
60
60
  # pidfile)
61
- exit!
61
+ exit!(true)
62
62
  end
63
63
 
64
64
  Signal.trap "SIGCHLD" do
@@ -266,4 +266,27 @@ class TestPumaServer < Test::Unit::TestCase
266
266
 
267
267
  assert_equal "HTTP/1.0 200 OK\r\nContent-Type: application/pdf\r\nContent-Length: 4242\r\n\r\n", data
268
268
  end
269
+
270
+ def test_status_hook_fires_when_server_changes_states
271
+
272
+ states = []
273
+
274
+ @events.register(:state) { |s| states << s }
275
+
276
+ @server.app = proc { |env| [200, {}, [""]] }
277
+
278
+ @server.add_tcp_listener @host, @port
279
+ @server.run
280
+
281
+ sock = TCPSocket.new @host, @port
282
+ sock << "HEAD / HTTP/1.0\r\n\r\n"
283
+
284
+ sock.read
285
+
286
+ assert_equal [:booting, :running], states
287
+
288
+ @server.stop(true)
289
+
290
+ assert_equal [:booting, :running, :stop, :done], states
291
+ end
269
292
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puma
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.1
4
+ version: 2.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-08-13 00:00:00.000000000 Z
12
+ date: 2013-09-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack