puma 0.8.2 → 0.9.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,7 +1,16 @@
1
1
  require 'thread'
2
2
 
3
3
  module Puma
4
+ # A simple thread pool management object.
5
+ #
4
6
  class ThreadPool
7
+
8
+ # Maintain a minimum of +min+ and maximum of +max+ threads
9
+ # in the pool.
10
+ #
11
+ # The block passed is the work that will be performed in each
12
+ # thread.
13
+ #
5
14
  def initialize(min, max, &blk)
6
15
  @todo = Queue.new
7
16
  @mutex = Mutex.new
@@ -15,11 +24,15 @@ module Puma
15
24
 
16
25
  @workers = []
17
26
 
27
+ @auto_trim = nil
28
+
18
29
  min.times { spawn_thread }
19
30
  end
20
31
 
21
32
  attr_reader :spawned
22
33
 
34
+ # How many objects have yet to be processed by the pool?
35
+ #
23
36
  def backlog
24
37
  @todo.size
25
38
  end
@@ -27,6 +40,7 @@ module Puma
27
40
  Stop = Object.new
28
41
  Trim = Object.new
29
42
 
43
+ # :nodoc:
30
44
  def spawn_thread
31
45
  @mutex.synchronize do
32
46
  @spawned += 1
@@ -64,6 +78,7 @@ module Puma
64
78
  th
65
79
  end
66
80
 
81
+ # Add +work+ to the todo list for a Thread to pickup and process.
67
82
  def <<(work)
68
83
  if @todo.num_waiting == 0 and @spawned < @max
69
84
  spawn_thread
@@ -72,6 +87,9 @@ module Puma
72
87
  @todo << work
73
88
  end
74
89
 
90
+ # If too many threads are in the pool, tell one to finish go ahead
91
+ # and exit.
92
+ #
75
93
  def trim
76
94
  @mutex.synchronize do
77
95
  if @spawned - @trim_requested > @min
@@ -81,7 +99,40 @@ module Puma
81
99
  end
82
100
  end
83
101
 
102
+ class AutoTrim
103
+ def initialize(pool, timeout)
104
+ @pool = pool
105
+ @timeout = timeout
106
+ @running = false
107
+ end
108
+
109
+ def start!
110
+ @running = true
111
+
112
+ @thread = Thread.new do
113
+ while @running
114
+ @pool.trim
115
+ sleep @timeout
116
+ end
117
+ end
118
+ end
119
+
120
+ def stop
121
+ @running = false
122
+ @thread.wakeup
123
+ end
124
+ end
125
+
126
+ def auto_trim!(timeout=5)
127
+ @auto_trim = AutoTrim.new(self, timeout)
128
+ @auto_trim.start!
129
+ end
130
+
131
+ # Tell all threads in the pool to exit and wait for them to finish.
132
+ #
84
133
  def shutdown
134
+ @auto_trim.stop if @auto_trim
135
+
85
136
  @spawned.times do
86
137
  @todo << Stop
87
138
  end
@@ -2,23 +2,23 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "puma"
5
- s.version = "0.8.2"
5
+ s.version = "0.9.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Evan Phoenix"]
9
- s.date = "2011-11-22"
9
+ s.date = "2011-12-06"
10
10
  s.description = "Puma is a small library that provides a very fast and concurrent HTTP 1.1 server for Ruby web applications. It is designed for running rack apps only.\n\nWhat makes Puma so fast is the careful use of an Ragel extension to provide fast, accurate HTTP 1.1 protocol parsing. This makes the server scream without too many portability issues."
11
11
  s.email = ["evan@phx.io"]
12
- s.executables = ["puma"]
12
+ s.executables = ["puma", "pumactl"]
13
13
  s.extensions = ["ext/puma_http11/extconf.rb"]
14
14
  s.extra_rdoc_files = ["History.txt", "Manifest.txt"]
15
- s.files = ["COPYING", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "examples/builder.rb", "examples/camping/README", "examples/camping/blog.rb", "examples/camping/tepee.rb", "examples/httpd.conf", "examples/mime.yaml", "examples/mongrel.conf", "examples/monitrc", "examples/random_thrash.rb", "examples/simpletest.rb", "examples/webrick_compare.rb", "ext/puma_http11/PumaHttp11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/org/jruby/puma/Http11.java", "ext/puma_http11/org/jruby/puma/Http11Parser.java", "ext/puma_http11/puma_http11.c", "lib/puma.rb", "lib/puma/cli.rb", "lib/puma/const.rb", "lib/puma/events.rb", "lib/puma/gems.rb", "lib/puma/mime_types.yml", "lib/puma/rack_patch.rb", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/puma/utils.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "tasks/gem.rake", "tasks/java.rake", "tasks/native.rake", "tasks/ragel.rake", "test/lobster.ru", "test/mime.yaml", "test/test_cli.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_persistent.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb", "test/testhelp.rb", "tools/trickletest.rb", ".gemtest"]
15
+ s.files = ["COPYING", "Gemfile", "History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "TODO", "bin/puma", "bin/pumactl", "ext/puma_http11/PumaHttp11Service.java", "ext/puma_http11/ext_help.h", "ext/puma_http11/extconf.rb", "ext/puma_http11/http11_parser.c", "ext/puma_http11/http11_parser.h", "ext/puma_http11/http11_parser.java.rl", "ext/puma_http11/http11_parser.rl", "ext/puma_http11/http11_parser_common.rl", "ext/puma_http11/org/jruby/puma/Http11.java", "ext/puma_http11/org/jruby/puma/Http11Parser.java", "ext/puma_http11/puma_http11.c", "lib/puma.rb", "lib/puma/app/status.rb", "lib/puma/cli.rb", "lib/puma/const.rb", "lib/puma/control_cli.rb", "lib/puma/events.rb", "lib/puma/null_io.rb", "lib/puma/rack_patch.rb", "lib/puma/server.rb", "lib/puma/thread_pool.rb", "lib/rack/handler/puma.rb", "puma.gemspec", "test/ab_rs.rb", "test/lobster.ru", "test/mime.yaml", "test/test_app_status.rb", "test/test_cli.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_persistent.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb", "test/testhelp.rb", "tools/trickletest.rb"]
16
16
  s.rdoc_options = ["--main", "README.md"]
17
17
  s.require_paths = ["lib"]
18
18
  s.rubyforge_project = "puma"
19
19
  s.rubygems_version = "1.8.10"
20
20
  s.summary = "Puma is a small library that provides a very fast and concurrent HTTP 1.1 server for Ruby web applications"
21
- s.test_files = ["test/test_cli.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_persistent.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb"]
21
+ s.test_files = ["test/test_app_status.rb", "test/test_cli.rb", "test/test_http10.rb", "test/test_http11.rb", "test/test_persistent.rb", "test/test_rack_handler.rb", "test/test_rack_server.rb", "test/test_thread_pool.rb", "test/test_unix_socket.rb", "test/test_ws.rb"]
22
22
 
23
23
  if s.respond_to? :specification_version then
24
24
  s.specification_version = 3
@@ -0,0 +1,22 @@
1
+ url = ARGV.shift
2
+ count = (ARGV.shift || 1000).to_i
3
+
4
+ STDOUT.sync = true
5
+
6
+ 1.upto(5) do |i|
7
+ print "#{i}: "
8
+ str = `ab -n #{count} -c #{i} #{url} 2>/dev/null`
9
+
10
+ rs = /Requests per second:\s+([\d.]+)\s/.match(str)
11
+ puts rs[1]
12
+ end
13
+
14
+ puts "Keep Alive:"
15
+
16
+ 1.upto(5) do |i|
17
+ print "#{i}: "
18
+ str = `ab -n #{count} -k -c #{i} #{url} 2>/dev/null`
19
+
20
+ rs = /Requests per second:\s+([\d.]+)\s/.match(str)
21
+ puts rs[1]
22
+ end
@@ -0,0 +1,69 @@
1
+ require 'test/unit'
2
+ require 'puma/app/status'
3
+
4
+ class TestAppStatus < Test::Unit::TestCase
5
+ class FakeServer
6
+ def initialize
7
+ @status = :running
8
+ @backlog = 0
9
+ @running = 0
10
+ end
11
+
12
+ attr_reader :status
13
+ attr_accessor :backlog, :running
14
+
15
+ def stop
16
+ @status = :stop
17
+ end
18
+
19
+ def halt
20
+ @status = :halt
21
+ end
22
+ end
23
+
24
+ def setup
25
+ @server = FakeServer.new
26
+ @app = Puma::App::Status.new(@server, @server)
27
+ end
28
+
29
+ def test_unsupported
30
+ env = { 'PATH_INFO' => "/not-real" }
31
+
32
+ status, header, body = @app.call env
33
+
34
+ assert_equal 404, status
35
+ end
36
+
37
+ def test_stop
38
+ env = { 'PATH_INFO' => "/stop" }
39
+
40
+ status, header, body = @app.call env
41
+
42
+ assert_equal :stop, @server.status
43
+ assert_equal 200, status
44
+ assert_equal ['{ "status": "ok" }'], body
45
+ end
46
+
47
+ def test_halt
48
+ env = { 'PATH_INFO' => "/halt" }
49
+
50
+ status, header, body = @app.call env
51
+
52
+ assert_equal :halt, @server.status
53
+ assert_equal 200, status
54
+ assert_equal ['{ "status": "ok" }'], body
55
+ end
56
+
57
+ def test_stats
58
+ env = { 'PATH_INFO' => "/stats" }
59
+
60
+ @server.backlog = 1
61
+ @server.running = 9
62
+
63
+ status, header, body = @app.call env
64
+
65
+ assert_equal 200, status
66
+ assert_equal ['{ "backlog": 1, "running": 9 }'], body
67
+ end
68
+
69
+ end
@@ -4,16 +4,102 @@ require 'tempfile'
4
4
 
5
5
  class TestCLI < Test::Unit::TestCase
6
6
  def setup
7
- @pid_file = Tempfile.new("puma-test")
8
- @pid_path = @pid_file.path
9
- @pid_file.close!
7
+ @tmp_file = Tempfile.new("puma-test")
8
+ @tmp_path = @tmp_file.path
9
+ @tmp_file.close!
10
+
11
+ @tmp_path2 = "#{@tmp_path}2"
12
+
13
+ File.unlink @tmp_path if File.exist? @tmp_path
14
+ File.unlink @tmp_path2 if File.exist? @tmp_path2
15
+ end
16
+
17
+ def teardown
18
+ File.unlink @tmp_path if File.exist? @tmp_path
19
+ File.unlink @tmp_path2 if File.exist? @tmp_path2
10
20
  end
11
21
 
12
22
  def test_pid_file
13
- cli = Puma::CLI.new ["--pidfile", @pid_path]
23
+ cli = Puma::CLI.new ["--pidfile", @tmp_path]
14
24
  cli.parse_options
15
25
  cli.write_pid
16
26
 
17
- assert_equal File.read(@pid_path).strip.to_i, Process.pid
27
+ assert_equal File.read(@tmp_path).strip.to_i, Process.pid
28
+ end
29
+
30
+ unless defined? JRUBY_VERSION
31
+ def test_status
32
+ url = "unix://#{@tmp_path}"
33
+
34
+ sin = StringIO.new
35
+ sout = StringIO.new
36
+
37
+ cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--status", url, "test/lobster.ru"], sin, sout
38
+ cli.parse_options
39
+
40
+ t = Thread.new { cli.run }
41
+
42
+ sleep 1
43
+
44
+ s = UNIXSocket.new @tmp_path
45
+ s << "GET /stats HTTP/1.0\r\n\r\n"
46
+ body = s.read
47
+
48
+ assert_equal '{ "backlog": 0, "running": 0 }', body.split("\r\n").last
49
+
50
+ cli.stop
51
+ t.join
52
+ end
53
+
54
+ def test_status_stop
55
+ url = "unix://#{@tmp_path}"
56
+
57
+ sin = StringIO.new
58
+ sout = StringIO.new
59
+
60
+ cli = Puma::CLI.new ["-b", "unix://#{@tmp_path2}", "--status", url, "test/lobster.ru"], sin, sout
61
+ cli.parse_options
62
+
63
+ t = Thread.new { cli.run }
64
+
65
+ sleep 1
66
+
67
+ s = UNIXSocket.new @tmp_path
68
+ s << "GET /stop HTTP/1.0\r\n\r\n"
69
+ body = s.read
70
+
71
+ assert_equal '{ "status": "ok" }', body.split("\r\n").last
72
+
73
+ t.join
74
+ end
75
+
76
+ def test_tmp_status
77
+ url = "tcp://127.0.0.1:8232"
78
+ cli = Puma::CLI.new ["--state", @tmp_path, "--status"]
79
+ cli.parse_options
80
+ cli.write_state
81
+
82
+ data = YAML.load_file(@tmp_path)
83
+
84
+ assert_equal Process.pid, data["pid"]
85
+
86
+ url = data["status_address"]
87
+
88
+ m = %r!unix://(.*)!.match(url)
89
+
90
+ assert m, "'#{url}' is not a URL"
91
+ end
92
+ end
93
+
94
+ def test_state
95
+ url = "tcp://127.0.0.1:8232"
96
+ cli = Puma::CLI.new ["--state", @tmp_path, "--status", url]
97
+ cli.parse_options
98
+ cli.write_state
99
+
100
+ data = YAML.load_file(@tmp_path)
101
+
102
+ assert_equal Process.pid, data["pid"]
103
+ assert_equal url, data["status_address"]
18
104
  end
19
105
  end
@@ -6,7 +6,7 @@ require 'test/testhelp'
6
6
  include Puma
7
7
 
8
8
  class Http11ParserTest < Test::Unit::TestCase
9
-
9
+
10
10
  def test_parse_simple
11
11
  parser = HttpParser.new
12
12
  req = {}
@@ -130,22 +130,5 @@ class Http11ParserTest < Test::Unit::TestCase
130
130
  end
131
131
 
132
132
  end
133
-
134
-
135
-
136
- def test_query_parse
137
- res = Utils.query_parse("zed=1&frank=#{Utils.escape('&&& ')}")
138
- assert res["zed"], "didn't get the request right"
139
- assert res["frank"], "no frank"
140
- assert_equal "1", res["zed"], "wrong result"
141
- assert_equal "&&& ", Utils.unescape(res["frank"]), "wrong result"
142
-
143
- res = Utils.query_parse("zed=1&zed=2&zed=3&frank=11;zed=45")
144
- assert res["zed"], "didn't get the request right"
145
- assert res["frank"], "no frank"
146
- assert_equal 4,res["zed"].length, "wrong number for zed"
147
- assert_equal "11",res["frank"], "wrong number for frank"
148
- end
149
-
150
133
  end
151
134
 
@@ -123,7 +123,7 @@ class TestPersistent < Test::Unit::TestCase
123
123
  @client << @keep_request
124
124
  sz = @body[0].size.to_s
125
125
 
126
- assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
126
+ assert_equal "HTTP/1.0 200 OK\r\nX-Header: Works\r\nConnection: Keep-Alive\r\nContent-Length: #{sz}\r\n\r\n", lines(5)
127
127
  assert_equal "Hello", @client.read(5)
128
128
  end
129
129
 
@@ -197,8 +197,8 @@ class TestPersistent < Test::Unit::TestCase
197
197
  assert_equal "HTTP/1.1 200 OK\r\nX-Header: Works\r\nContent-Length: #{sz}\r\n\r\n", lines(4)
198
198
  assert_equal "Hello", @client.read(5)
199
199
 
200
- assert_equal "", @inputs[0].string
201
- assert_equal "", @inputs[1].string
200
+ assert_kind_of Puma::NullIO, @inputs[0]
201
+ assert_kind_of Puma::NullIO, @inputs[1]
202
202
 
203
203
  end
204
204
 
@@ -68,6 +68,8 @@ class TestRackServer < Test::Unit::TestCase
68
68
 
69
69
  hit(['http://localhost:9998/test'])
70
70
 
71
+ stop
72
+
71
73
  if exc = @checker.exception
72
74
  raise exc
73
75
  end
@@ -80,6 +82,8 @@ class TestRackServer < Test::Unit::TestCase
80
82
 
81
83
  hit(['http://localhost:9998/test/a/b/c'])
82
84
 
85
+ stop
86
+
83
87
  assert_equal "/test/a/b/c", input['PATH_INFO']
84
88
  end
85
89
 
@@ -95,6 +99,8 @@ class TestRackServer < Test::Unit::TestCase
95
99
 
96
100
  hit(['http://localhost:9998/test'])
97
101
 
102
+ stop
103
+
98
104
  assert_equal true, closed
99
105
  end
100
106
 
@@ -99,4 +99,28 @@ class TestThreadPool < Test::Unit::TestCase
99
99
 
100
100
  assert_equal 1, pool.spawned
101
101
  end
102
+
103
+ def test_autotrim
104
+ finish = false
105
+ pool = new_pool(1, 2) { Thread.pass until finish }
106
+
107
+ pool << 1
108
+ pool << 2
109
+
110
+ assert_equal 2, pool.spawned
111
+
112
+ finish = true
113
+
114
+ pause
115
+
116
+ assert_equal 2, pool.spawned
117
+
118
+ pool.auto_trim! 1
119
+
120
+ sleep 1
121
+
122
+ pause
123
+
124
+ assert_equal 1, pool.spawned
125
+ end
102
126
  end
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 8
9
- - 2
10
- version: 0.8.2
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Evan Phoenix
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-11-22 00:00:00 Z
18
+ date: 2011-12-06 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rack
@@ -56,13 +56,28 @@ dependencies:
56
56
  requirements:
57
57
  - - ~>
58
58
  - !ruby/object:Gem::Version
59
- hash: 23
59
+ hash: 27
60
60
  segments:
61
61
  - 2
62
- - 10
63
- version: "2.10"
62
+ - 12
63
+ version: "2.12"
64
64
  type: :development
65
65
  version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: rdoc
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ hash: 19
75
+ segments:
76
+ - 3
77
+ - 10
78
+ version: "3.10"
79
+ type: :development
80
+ version_requirements: *id004
66
81
  description: |-
67
82
  Puma is a small library that provides a very fast and concurrent HTTP 1.1 server for Ruby web applications. It is designed for running rack apps only.
68
83
 
@@ -71,6 +86,7 @@ email:
71
86
  - evan@phx.io
72
87
  executables:
73
88
  - puma
89
+ - pumactl
74
90
  extensions:
75
91
  - ext/puma_http11/extconf.rb
76
92
  extra_rdoc_files:
@@ -86,17 +102,7 @@ files:
86
102
  - Rakefile
87
103
  - TODO
88
104
  - bin/puma
89
- - examples/builder.rb
90
- - examples/camping/README
91
- - examples/camping/blog.rb
92
- - examples/camping/tepee.rb
93
- - examples/httpd.conf
94
- - examples/mime.yaml
95
- - examples/mongrel.conf
96
- - examples/monitrc
97
- - examples/random_thrash.rb
98
- - examples/simpletest.rb
99
- - examples/webrick_compare.rb
105
+ - bin/pumactl
100
106
  - ext/puma_http11/PumaHttp11Service.java
101
107
  - ext/puma_http11/ext_help.h
102
108
  - ext/puma_http11/extconf.rb
@@ -109,23 +115,21 @@ files:
109
115
  - ext/puma_http11/org/jruby/puma/Http11Parser.java
110
116
  - ext/puma_http11/puma_http11.c
111
117
  - lib/puma.rb
118
+ - lib/puma/app/status.rb
112
119
  - lib/puma/cli.rb
113
120
  - lib/puma/const.rb
121
+ - lib/puma/control_cli.rb
114
122
  - lib/puma/events.rb
115
- - lib/puma/gems.rb
116
- - lib/puma/mime_types.yml
123
+ - lib/puma/null_io.rb
117
124
  - lib/puma/rack_patch.rb
118
125
  - lib/puma/server.rb
119
126
  - lib/puma/thread_pool.rb
120
- - lib/puma/utils.rb
121
127
  - lib/rack/handler/puma.rb
122
128
  - puma.gemspec
123
- - tasks/gem.rake
124
- - tasks/java.rake
125
- - tasks/native.rake
126
- - tasks/ragel.rake
129
+ - test/ab_rs.rb
127
130
  - test/lobster.ru
128
131
  - test/mime.yaml
132
+ - test/test_app_status.rb
129
133
  - test/test_cli.rb
130
134
  - test/test_http10.rb
131
135
  - test/test_http11.rb
@@ -137,7 +141,6 @@ files:
137
141
  - test/test_ws.rb
138
142
  - test/testhelp.rb
139
143
  - tools/trickletest.rb
140
- - .gemtest
141
144
  homepage:
142
145
  licenses: []
143
146
 
@@ -168,11 +171,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
168
171
  requirements: []
169
172
 
170
173
  rubyforge_project: puma
171
- rubygems_version: 1.8.10
174
+ rubygems_version: 1.8.12
172
175
  signing_key:
173
176
  specification_version: 3
174
177
  summary: Puma is a small library that provides a very fast and concurrent HTTP 1.1 server for Ruby web applications
175
178
  test_files:
179
+ - test/test_app_status.rb
176
180
  - test/test_cli.rb
177
181
  - test/test_http10.rb
178
182
  - test/test_http11.rb