diff --git a/lib/vcap/concurrency/errors.rb b/lib/vcap/concurrency/errors.rb new file mode 100644 index 0000000..7789353 --- /dev/null +++ b/lib/vcap/concurrency/errors.rb @@ -0,0 +1,7 @@ +module VCAP + module Concurrency + class Error < StandardError; end + class TimeoutError < Error; end + end +end + diff --git a/lib/vcap/concurrency/promise.rb b/lib/vcap/concurrency/promise.rb index aa09894..3a64575 100644 --- a/lib/vcap/concurrency/promise.rb +++ b/lib/vcap/concurrency/promise.rb @@ -1,5 +1,7 @@ require "thread" +require "vcap/concurrency/errors" + module VCAP module Concurrency end @@ -62,10 +64,22 @@ def fail(exception) # NB: If the promise failed to be fulfilled, the error that occurred while # fulfilling it will be raised here. # + # @param [Integer] timeout_secs If supplied, wait for no longer than this + # value before proceeding. An exception will be raised if the promise hasn't + # been fulfilled when the timeout occurs. + # + # @raise [VCAP::Concurrency::TimeoutError] Raised if the promise hasn't been + # fulfilled after +timeout_secs+ seconds since calling resolve(). + # # @return [Object] The result of the associated computation. - def resolve + def resolve(timeout_secs = nil) @lock.synchronize do - @cond.wait(@lock) unless @done + @cond.wait(@lock, timeout_secs) unless @done + + if !@done + emsg = "Timed out waiting on result after #{timeout_secs}s." + raise VCAP::Concurrency::TimeoutError.new(emsg) + end if @error raise @error diff --git a/lib/vcap/concurrency/version.rb b/lib/vcap/concurrency/version.rb index 95f8948..6559ed9 100644 --- a/lib/vcap/concurrency/version.rb +++ b/lib/vcap/concurrency/version.rb @@ -1,5 +1,5 @@ module VCAP module Concurrency - VERSION = "0.0.1" + VERSION = "0.1.0" end end diff --git a/spec/promise_spec.rb b/spec/promise_spec.rb index ae7bc76..060fc34 100644 --- a/spec/promise_spec.rb +++ b/spec/promise_spec.rb @@ -1,15 +1,15 @@ require "spec_helper" describe VCAP::Concurrency::Promise do + let(:promise) { VCAP::Concurrency::Promise.new } + describe "#deliver " do it "should deliver the supplied result to callers of resolve" do - promise = VCAP::Concurrency::Promise.new promise.deliver(:done) promise.resolve.should == :done end it "should raise an error if called more than once" do - promise = VCAP::Concurrency::Promise.new promise.deliver expect do promise.deliver @@ -17,7 +17,6 @@ end it "should wake up all threads that are resolving it" do - promise = VCAP::Concurrency::Promise.new lock = Mutex.new cond = ConditionVariable.new waiting = 0 @@ -52,7 +51,6 @@ describe "#fail" do it "should deliver the supplied exception to callers of resolve" do - promise = VCAP::Concurrency::Promise.new error_text = "test error" promise.fail(RuntimeError.new(error_text)) expect do @@ -61,7 +59,6 @@ end it "should raise an error if called more than once" do - promise = VCAP::Concurrency::Promise.new e = RuntimeError.new("test") promise.fail(e) expect do @@ -69,4 +66,18 @@ end.to raise_error(/completed once/) end end + + describe "#resolve" do + it "should raise an error when a timeout occurs" do + start = Time.now + + expect do + promise.resolve(0.5) + end.to raise_error(VCAP::Concurrency::TimeoutError) + + elapsed = Time.now - start + + elapsed.should be_within(1).of(0.5) + end + end end