Skip to content

Commit

Permalink
use a proxy object to call close on the mutex when the body is closed.
Browse files Browse the repository at this point in the history
…closes #87
  • Loading branch information
tenderlove authored and josh committed Dec 17, 2010
1 parent f80df39 commit 3bf8655
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 8 deletions.
21 changes: 18 additions & 3 deletions lib/rack/lock.rb
Expand Up @@ -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
Expand Down
82 changes: 77 additions & 5 deletions test/spec_lock.rb
Expand Up @@ -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

0 comments on commit 3bf8655

Please sign in to comment.