spider-gazelle 0.2.1 → 1.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 79738b050fb90a8bb950a269f6b5e73b4b4361b9
4
- data.tar.gz: 0c8658ee11829adfd2cb08b4903334e10930dab4
3
+ metadata.gz: 2363b13c6111e7a4df5fd28d780ec4adcf5d76ab
4
+ data.tar.gz: c0e3e7e4da343f021cdda39e1a6a1fe829b6951a
5
5
  SHA512:
6
- metadata.gz: a7aecd49cd24a1929d6edd7ec652965dc666252ed1ebd225434f18bf2d3c86b17cf7af5970084f88abf05795f3ee1afbc476acf70d6c8cc9c7b613b3013fdb28
7
- data.tar.gz: 67bd17a7bf6397bc5b94b0e353395457e2ea0b50c8b38e2525ad747e718b9ce44faee61bfffac8a813611613e161962745962960d9ad90d39c2cad6df506dafe
6
+ metadata.gz: 34856dafcf2b8e138b2299f40739ae6847886671bbb67101918d0b82e4d66cca77ea64d8c36f4a427d2cfc1b8c54615c1cbd88639fcc8ce6df76ec55f148d3c1
7
+ data.tar.gz: 46ef37b2469b5dc3a69a7138e20af7895a1666c27c4659f9d4f7923e2662351aab45aba7d34daacdbb2d1801caaf41ad8156ead058e81190637dc12cd82cd636
@@ -0,0 +1,42 @@
1
+ require 'thread'
2
+ require 'rack/body_proxy'
3
+
4
+ require 'rack/lock' # ensure this loads first
5
+
6
+ module Rack
7
+ # Rack::Lock locks every request inside a mutex, so that every request
8
+ # will effectively be executed synchronously.
9
+ class Lock
10
+ # FLAG = 'rack.multithread'.freeze # defined in rack/lock
11
+
12
+ def initialize(app, mutex = Mutex.new)
13
+ @app, @mutex = app, mutex
14
+ @sig = ConditionVariable.new
15
+ @count = 0
16
+ end
17
+
18
+ def call(env)
19
+ old, env[FLAG] = env[FLAG], false
20
+ @mutex.lock
21
+ @count += 1
22
+ @sig.wait(@mutex) if @count > 1
23
+ response = @app.call(env)
24
+ body = BodyProxy.new(response[2]) {
25
+ @mutex.synchronize { unlock }
26
+ }
27
+ response[2] = body
28
+ response
29
+ ensure
30
+ unlock unless body
31
+ @mutex.unlock
32
+ env[FLAG] = old
33
+ end
34
+
35
+ private
36
+
37
+ def unlock
38
+ @count -= 1
39
+ @sig.signal
40
+ end
41
+ end
42
+ end
@@ -1,6 +1,7 @@
1
1
  require "http-parser" # C based, fast, http parser
2
2
  require "libuv" # Ruby Libuv FFI wrapper
3
3
  require "rack" # Ruby webserver abstraction
4
+ require "rack/lock_patch" # Single threaded in development mode
4
5
 
5
6
  require "spider-gazelle/request" # Holds request information and handles request processing
6
7
  require "spider-gazelle/connection" # Holds connection information and handles request pipelining
@@ -126,7 +126,7 @@ module SpiderGazelle
126
126
  # Unlinks the connection from the rack app
127
127
  # This occurs when requested and when the socket closes
128
128
  def unlink
129
- if not @gazelle.nil?
129
+ if @gazelle
130
130
  # Unlink the progress callback (prevent funny business)
131
131
  @socket.progress &DUMMY_PROGRESS
132
132
  @gazelle.discard self
@@ -149,7 +149,12 @@ module SpiderGazelle
149
149
  # Pass the hijack response to the captor using the promise. This forwards the socket and
150
150
  # environment as well as moving continued execution onto the event loop.
151
151
  @request.hijacked.resolve Hijack.new(@socket, @request.env)
152
- elsif !@socket.closed
152
+ elsif @socket.closed
153
+ unless result.nil? || @request.deferred
154
+ body = result[2]
155
+ body.close if body.respond_to?(:close)
156
+ end
157
+ else
153
158
  if @request.deferred
154
159
  # Wait for the response using this promise
155
160
  promise = @request.deferred.promise
@@ -162,7 +167,7 @@ module SpiderGazelle
162
167
 
163
168
  return promise
164
169
  # NOTE:: Somehow getting to here with a nil request... needs investigation
165
- elsif not result.nil?
170
+ elsif result
166
171
  # clear any cached responses just in case
167
172
  # could be set by error in the rack application
168
173
  @deferred_responses = nil if @deferred_responses
@@ -182,6 +187,10 @@ module SpiderGazelle
182
187
 
183
188
  write_headers status, headers
184
189
 
190
+ # Send the body in parallel without blocking the next request in dev
191
+ # Also if this is a head request we still want the body closed
192
+ body.close if body.respond_to?(:close)
193
+
185
194
  if send_body
186
195
  file = @loop.file body.to_path, File::RDONLY
187
196
  file.progress do
@@ -205,6 +214,7 @@ module SpiderGazelle
205
214
  if send_body
206
215
  write_response status, headers, body
207
216
  else
217
+ body.close if body.respond_to?(:close)
208
218
  write_headers status, headers
209
219
  @socket.shutdown if @request.keep_alive == false
210
220
  end
@@ -246,6 +256,8 @@ module SpiderGazelle
246
256
  @async_state = :chunked
247
257
  end
248
258
  end
259
+
260
+ body.close if body.respond_to?(:close)
249
261
  end
250
262
 
251
263
  def write_headers(status, headers)
@@ -280,9 +292,6 @@ module SpiderGazelle
280
292
 
281
293
  # Callback from a response that was marked async
282
294
  def deferred_callback(data)
283
- # We call close here, like on a regular response
284
- body = data[2]
285
- body.close if body.respond_to?(:close)
286
295
  @loop.next_tick { callback(data) }
287
296
  end
288
297
 
@@ -310,6 +319,8 @@ module SpiderGazelle
310
319
  # Respond with the headers here
311
320
  write_response status, headers, body
312
321
  elsif body.empty?
322
+ body.close if body.respond_to?(:close)
323
+
313
324
  @socket.write CLOSE_CHUNK
314
325
  @socket.shutdown if @request.keep_alive == false
315
326
 
@@ -322,6 +333,7 @@ module SpiderGazelle
322
333
  else
323
334
  # Send the chunks provided
324
335
  body.each &@write_chunk
336
+ body.close if body.respond_to?(:close)
325
337
  end
326
338
 
327
339
  nil
@@ -27,7 +27,7 @@ module SpiderGazelle
27
27
  # REMOTE_USER, or REMOTE_HOST parameters since those are either a security problem or
28
28
  # too taxing on performance.
29
29
  module Const
30
- SPIDER_GAZELLE_VERSION = VERSION = "0.2.1".freeze
30
+ SPIDER_GAZELLE_VERSION = VERSION = "1.0.0.rc3".freeze
31
31
  # CODE_NAME = "Earl of Sandwich Partition"
32
32
  SERVER = "SpiderGazelle".freeze
33
33
 
@@ -82,18 +82,23 @@ module SpiderGazelle
82
82
 
83
83
  # Execute the request
84
84
  @response = catch :async, &@execute
85
- @deferred = @loop.defer if (@response.nil? || @response[0] == -1)
85
+ if @response.nil? || @response[0] == -1
86
+ @deferred = @loop.defer
87
+
88
+ # close the body for deferred responses
89
+ unless @response.nil?
90
+ body = @response[2]
91
+ body.close if body.respond_to?(:close)
92
+ end
93
+ end
86
94
  @response
87
95
  end
88
96
 
89
97
  protected
90
98
 
91
- # Execute the request then close the body
92
- # NOTE:: closing the body here might cause issues (see connection.rb)
99
+ # Execute the request then return the result to the event loop
93
100
  def execute(*args)
94
101
  result = @app.call @env
95
- body = result[2]
96
- body.close if body.respond_to?(:close)
97
102
  result
98
103
  end
99
104
 
@@ -61,8 +61,13 @@ module SpiderGazelle
61
61
  # id => [bind1, bind2]
62
62
  # }
63
63
 
64
- mode = ENV['SG_MODE'] || :thread
65
- @mode = mode.to_sym
64
+ # Single reactor in development
65
+ if ENV['RACK_ENV'].to_sym == :development
66
+ @mode = :no_ipc
67
+ else
68
+ mode = ENV['SG_MODE'] || :thread
69
+ @mode = mode.to_sym
70
+ end
66
71
 
67
72
  @delegate = method(no_ipc? ? :direct_delegate : :delegate)
68
73
  @squash = method(:squash)
@@ -0,0 +1,138 @@
1
+ require 'rack/lint'
2
+ require 'rack/lock'
3
+ require 'rack/mock'
4
+
5
+ require 'rack/lock_patch'
6
+
7
+
8
+ module Rack
9
+ class Lock
10
+ def would_block
11
+ @count > 0
12
+ end
13
+ end
14
+ end
15
+
16
+
17
+ def lock_app(app)
18
+ app = Rack::Lock.new(app)
19
+ return app, Rack::Lint.new(app)
20
+ end
21
+
22
+
23
+ describe Rack::Lock do
24
+
25
+ describe 'Proxy' do
26
+
27
+ it 'delegate each' do
28
+ env = Rack::MockRequest.env_for("/")
29
+ response = Class.new {
30
+ attr_accessor :close_called
31
+ def initialize; @close_called = false; end
32
+ def each; %w{ hi mom }.each { |x| yield x }; end
33
+ }.new
34
+
35
+ app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] })[1]
36
+ response = app.call(env)[2]
37
+ list = []
38
+ response.each { |x| list << x }
39
+ expect(list).to eq(%w{ hi mom })
40
+ end
41
+
42
+ it 'delegate to_path' do
43
+ env = Rack::MockRequest.env_for("/")
44
+
45
+ res = ['Hello World']
46
+ def res.to_path ; "/tmp/hello.txt" ; end
47
+
48
+ app = Rack::Lock.new(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, res] })
49
+ body = app.call(env)[2]
50
+
51
+ expect(body.respond_to? :to_path).to eq(true)
52
+ expect(body.to_path).to eq("/tmp/hello.txt")
53
+ end
54
+
55
+ it 'not delegate to_path if body does not implement it' do
56
+ env = Rack::MockRequest.env_for("/")
57
+
58
+ res = ['Hello World']
59
+
60
+ app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, res] })[1]
61
+ body = app.call(env)[2]
62
+
63
+ expect(body.respond_to? :to_path).to eq(false)
64
+ end
65
+ end
66
+
67
+ it 'call super on close' do
68
+ env = Rack::MockRequest.env_for("/")
69
+ response = Class.new {
70
+ attr_accessor :close_called
71
+ def initialize; @close_called = false; end
72
+ def close; @close_called = true; end
73
+ }.new
74
+
75
+ app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] })[1]
76
+ app.call(env)
77
+ expect(response.close_called).to eq(false)
78
+ response.close
79
+ expect(response.close_called).to eq(true)
80
+ end
81
+
82
+ it "not unlock until body is closed" do
83
+ env = Rack::MockRequest.env_for("/")
84
+ response = Object.new
85
+ lock, app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, response] })
86
+
87
+ expect(lock.would_block).to eq(false)
88
+ response = app.call(env)[2]
89
+ expect(lock.would_block).to eq(true)
90
+ response.close
91
+ expect(lock.would_block).to eq(false)
92
+ end
93
+
94
+ it "return value from app" do
95
+ env = Rack::MockRequest.env_for("/")
96
+ body = [200, {"Content-Type" => "text/plain"}, %w{ hi mom }]
97
+ app = lock_app(lambda { |inner_env| body })[1]
98
+
99
+ res = app.call(env)
100
+ expect(res[0]).to eq(body[0])
101
+ expect(res[1]).to eq(body[1])
102
+ expect(res[2].to_enum.to_a).to eq(["hi", "mom"])
103
+ end
104
+
105
+ it "call synchronize on lock" do
106
+ env = Rack::MockRequest.env_for("/")
107
+ lock, app = lock_app(lambda { |inner_env| [200, {"Content-Type" => "text/plain"}, %w{ a b c }] })
108
+ expect(lock.would_block).to eq(false)
109
+ app.call(env)
110
+ expect(lock.would_block).to eq(true)
111
+ end
112
+
113
+ it "unlock if the app raises" do
114
+ env = Rack::MockRequest.env_for("/")
115
+ lock, app = lock_app(lambda { raise Exception })
116
+ expect { app.call(env) }.to raise_error(Exception)
117
+ expect(lock.would_block).to eq(false)
118
+ end
119
+
120
+ it "unlock if the app throws" do
121
+ env = Rack::MockRequest.env_for("/")
122
+ lock, app = lock_app(lambda {|_| throw :bacon })
123
+ expect { app.call(env) }.to raise_error(ArgumentError)
124
+ expect(lock.would_block).to eq(false)
125
+ end
126
+
127
+ it "set multithread flag to false" do
128
+ outer = nil
129
+ app = lock_app(lambda { |env|
130
+ outer = env
131
+ expect(env['rack.multithread']).to eq(false)
132
+ [200, {"Content-Type" => "text/plain"}, %w{ a b c }]
133
+ })[1]
134
+ resp = app.call(Rack::MockRequest.env_for("/"))[2]
135
+ resp.close
136
+ expect(outer['rack.multithread']).to eq(true)
137
+ end
138
+ end
@@ -1,12 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
- d = File.read(File.expand_path("../lib/spider-gazelle/const.rb", __FILE__))
5
- if d =~ /VERSION = "(\d+\.\d+\.\d+)"/
6
- version = $1
7
- else
8
- version = "0.0.1"
9
- end
4
+ require 'spider-gazelle/const'
5
+ version = SpiderGazelle::Const::VERSION
10
6
 
11
7
  Gem::Specification.new do |s|
12
8
  s.name = "spider-gazelle"
@@ -24,7 +20,7 @@ Gem::Specification.new do |s|
24
20
 
25
21
  s.add_dependency 'rake'
26
22
  s.add_dependency 'http-parser' # Ruby FFI bindings for https://github.com/joyent/http-parser
27
- s.add_dependency 'libuv', '>= 0.12.0' # Ruby FFI bindings for https://github.com/joyent/libuv
23
+ s.add_dependency 'libuv', '>= 0.12.4' # Ruby FFI bindings for https://github.com/joyent/libuv
28
24
  s.add_dependency 'rack', '>= 1.0.0' # Ruby web server interface
29
25
  s.add_dependency 'websocket-driver' # Websocket parser
30
26
  s.add_dependency 'thread_safe' # Thread safe hashes
metadata CHANGED
@@ -1,139 +1,139 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spider-gazelle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 1.0.0.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen von Takach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-14 00:00:00.000000000 Z
11
+ date: 2014-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - '>='
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: http-parser
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - '>='
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: libuv
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - '>='
46
46
  - !ruby/object:Gem::Version
47
- version: 0.12.0
47
+ version: 0.12.4
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - '>='
53
53
  - !ruby/object:Gem::Version
54
- version: 0.12.0
54
+ version: 0.12.4
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rack
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: 1.0.0
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - '>='
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.0.0
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: websocket-driver
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - ">="
73
+ - - '>='
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - ">="
80
+ - - '>='
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: thread_safe
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - '>='
88
88
  - !ruby/object:Gem::Version
89
89
  version: '0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - '>='
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: radix
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - '>='
102
102
  - !ruby/object:Gem::Version
103
103
  version: '0'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rspec
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - '>='
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - '>='
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: yard
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - ">="
129
+ - - '>='
130
130
  - !ruby/object:Gem::Version
131
131
  version: '0'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">="
136
+ - - '>='
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  description: |2
@@ -149,6 +149,7 @@ extra_rdoc_files:
149
149
  - README.md
150
150
  files:
151
151
  - lib/rack/handler/spider-gazelle.rb
152
+ - lib/rack/lock_patch.rb
152
153
  - lib/spider-gazelle/app_store.rb
153
154
  - lib/spider-gazelle/binding.rb
154
155
  - lib/spider-gazelle/connection.rb
@@ -163,6 +164,7 @@ files:
163
164
  - spider-gazelle.gemspec
164
165
  - README.md
165
166
  - LICENSE
167
+ - spec/rack_lock_spec.rb
166
168
  homepage: https://github.com/cotag/spider-gazelle
167
169
  licenses:
168
170
  - MIT
@@ -173,19 +175,20 @@ require_paths:
173
175
  - lib
174
176
  required_ruby_version: !ruby/object:Gem::Requirement
175
177
  requirements:
176
- - - ">="
178
+ - - '>='
177
179
  - !ruby/object:Gem::Version
178
180
  version: '0'
179
181
  required_rubygems_version: !ruby/object:Gem::Requirement
180
182
  requirements:
181
- - - ">="
183
+ - - '>'
182
184
  - !ruby/object:Gem::Version
183
- version: '0'
185
+ version: 1.3.1
184
186
  requirements: []
185
187
  rubyforge_project:
186
- rubygems_version: 2.0.3
188
+ rubygems_version: 2.1.11
187
189
  signing_key:
188
190
  specification_version: 4
189
191
  summary: A fast, parallel and concurrent web server for ruby
190
- test_files: []
192
+ test_files:
193
+ - spec/rack_lock_spec.rb
191
194
  has_rdoc: