Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

use a proxy object to call close on the mutex when the body is closed…

…. closes #87
commit 3bf865524e23e4bd6207557dac16e41fd9c450db 1 parent f80df39
@tenderlove tenderlove authored josh committed
Showing with 95 additions and 8 deletions.
  1. +18 −3 lib/rack/lock.rb
  2. +77 −5 test/spec_lock.rb
View
21 lib/rack/lock.rb
@@ -2,15 +2,30 @@
module Rack
class Lock
+ class Proxy < Struct.new(:target, :mutex) # :nodoc:
+ def each
+ target.each { |x| yield x }
+ end
+
+ def close
+ target.close if target.respond_to?(:close)
+ ensure
+ mutex.unlock
+ end
+ end
+
FLAG = 'rack.multithread'.freeze
- def initialize(app, lock = Mutex.new)
- @app, @lock = app, lock
+ def initialize(app, mutex = Mutex.new)
+ @app, @mutex = app, mutex
end
def call(env)
old, env[FLAG] = env[FLAG], false
- @lock.synchronize { @app.call(env) }
+ @mutex.lock
+ response = @app.call(env)
+ response[2] = Proxy.new(response[2], @mutex)
+ response
ensure
env[FLAG] = old
end
View
82 test/spec_lock.rb
@@ -12,25 +12,97 @@ def synchronize
@synchronized = true
yield
end
+
+ def lock
+ @synchronized = true
+ end
+
+ def unlock
+ @synchronized = false
+ end
end
describe Rack::Lock do
+ describe 'Proxy' do
+ should 'delegate each' do
+ lock = Lock.new
+ env = Rack::MockRequest.env_for("/")
+ response = Class.new {
+ attr_accessor :close_called
+ def initialize; @close_called = false; end
+ def each; %w{ hi mom }.each { |x| yield x }; end
+ }.new
+
+ app = Rack::Lock.new(lambda { |inner_env| [200, {}, response] }, lock)
+ response = app.call(env)[2]
+ list = []
+ response.each { |x| list << x }
+ list.should.equal %w{ hi mom }
+ end
+ end
+
+ should 'call super on close' do
+ lock = Lock.new
+ env = Rack::MockRequest.env_for("/")
+ response = Class.new {
+ attr_accessor :close_called
+ def initialize; @close_called = false; end
+ def close; @close_called = true; end
+ }.new
+
+ app = Rack::Lock.new(lambda { |inner_env| [200, {}, response] }, lock)
+ app.call(env)
+ response.close_called.should.equal false
+ response.close
+ response.close_called.should.equal true
+ end
+
+ should "not unlock until body is closed" do
+ lock = Lock.new
+ env = Rack::MockRequest.env_for("/")
+ response = Object.new
+ app = Rack::Lock.new(lambda { |inner_env| [200, {}, response] }, lock)
+ lock.synchronized.should.equal false
+ response = app.call(env)[2]
+ lock.synchronized.should.equal true
+ response.close
+ lock.synchronized.should.equal false
+ end
+
+ should "return value from app" do
+ lock = Lock.new
+ env = Rack::MockRequest.env_for("/")
+ body = [200, {}, %w{ hi mom }]
+ app = Rack::Lock.new(lambda { |inner_env| body }, lock)
+ app.call(env).should.equal body
+ end
+
should "call synchronize on lock" do
lock = Lock.new
env = Rack::MockRequest.env_for("/")
- app = Rack::Lock.new(lambda { |inner_env| }, lock)
+ app = Rack::Lock.new(lambda { |inner_env|
+ [200, {}, %w{ a b c }]
+ }, lock)
lock.synchronized.should.equal false
app.call(env)
lock.synchronized.should.equal true
end
should "set multithread flag to false" do
- app = Rack::Lock.new(lambda { |env| env['rack.multithread'] })
- app.call(Rack::MockRequest.env_for("/")).should.equal false
+ app = Rack::Lock.new(lambda { |env|
+ env['rack.multithread'].should.equal false
+ [200, {}, %w{ a b c }]
+ })
+ app.call(Rack::MockRequest.env_for("/"))
end
should "reset original multithread flag when exiting lock" do
- app = Rack::Lock.new(lambda { |env| env })
- app.call(Rack::MockRequest.env_for("/"))['rack.multithread'].should.equal true
+ app = Class.new(Rack::Lock) {
+ def call(env)
+ env['rack.multithread'].should.equal true
+ super
+ end
+ }.new(lambda { |env| [200, {}, %w{ a b c }] })
+ app.call(Rack::MockRequest.env_for("/"))
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.