From 291fc95983ffcbd0a4816e31683c4da960586693 Mon Sep 17 00:00:00 2001 From: Matt Green Date: Thu, 3 Oct 2013 21:49:21 -0400 Subject: [PATCH] Timeout support --- lib/elevate/operation.rb | 10 +++ lib/elevate/task.rb | 57 ++++++++++------- spec/elevate_spec.rb | 132 ++++++++++++++++----------------------- 3 files changed, 100 insertions(+), 99 deletions(-) diff --git a/lib/elevate/operation.rb b/lib/elevate/operation.rb index 88d1e64..6dea7ea 100644 --- a/lib/elevate/operation.rb +++ b/lib/elevate/operation.rb @@ -73,5 +73,15 @@ def main # # @api public attr_reader :result + + # Cancels any waiting operation with a TimeoutError, interrupting + # execution. This is not the same as #cancel. + # + # @return [void] + # + # @api public + def timeout + @coordinator.cancel(TimeoutError) + end end end diff --git a/lib/elevate/task.rb b/lib/elevate/task.rb index 374823f..ae8b0f0 100644 --- a/lib/elevate/task.rb +++ b/lib/elevate/task.rb @@ -1,38 +1,32 @@ module Elevate class Task def initialize(definition, controller, active_tasks) - @name = definition.name + @definition = definition @controller = WeakRef.new(controller) @active_tasks = active_tasks @operation = nil @channel = Channel.new(method(:on_update)) - @handlers = definition.handlers + @timer = nil end def cancel if @operation @operation.cancel - end - end - - attr_reader :name - - def on_finish=(block) - @handlers[:on_finish] = block - end - def on_start=(block) - @handlers[:on_start] = block + if @timer + @timer.invalidate + end + end end - def on_update=(block) - @handlers[:on_update] = block + def name + @definition.name end def start(args) - raise "invalid argument count" if args.length != @handlers[:body].arity + raise "invalid argument count" if args.length != handlers[:body].arity - @operation = ElevateOperation.alloc.initWithTarget(@handlers[:body], + @operation = ElevateOperation.alloc.initWithTarget(handlers[:body], args: args, channel: WeakRef.new(@channel)) @@ -40,6 +34,14 @@ def start(args) queue.addOperation(@operation) @active_tasks << self + if interval = @definition.options[:timeout_interval] + @timer = NSTimer.scheduledTimerWithTimeInterval(interval, + target: self, + selector: :"on_timeout:", + userInfo: nil, + repeats: false) + end + performSelectorOnMainThread(:on_start, withObject: nil, waitUntilDone: false) end @@ -55,8 +57,12 @@ def error_handler_for(exception) handler_name.to_sym end + def handlers + @definition.handlers + end + def invoke(block, *args) - return if @operation.isCancelled + return if @operation.cancelled? return unless block @controller.instance_exec(*args, &block) @@ -79,19 +85,26 @@ def observeValueForKeyPath(path, ofObject: operation, change: change, context: c end def on_start - invoke(@handlers[:on_start]) + invoke(handlers[:on_start]) end def on_finish @operation.removeObserver(self, forKeyPath: "isFinished") - @active_tasks.delete(self) + if @timer + @timer.invalidate + end + if exception = @operation.exception - invoke(@handlers.fetch(error_handler_for(exception), @handlers[:on_error]), exception) + invoke(handlers.fetch(error_handler_for(exception), handlers[:on_error]), exception) end - invoke(@handlers[:on_finish], @operation.result, @operation.exception) + invoke(handlers[:on_finish], @operation.result, @operation.exception) + end + + def on_timeout(timer) + @operation.timeout end def on_update(args) @@ -100,7 +113,7 @@ def on_update(args) return end - invoke(@handlers[:on_update], *args) + invoke(handlers[:on_update], *args) end end end diff --git a/spec/elevate_spec.rb b/spec/elevate_spec.rb index 81a9bc2..d6d6f4c 100644 --- a/spec/elevate_spec.rb +++ b/spec/elevate_spec.rb @@ -100,6 +100,24 @@ def initialize #Dispatch::Queue.main.async { resume } end end + + task :timeout_test do + timeout 0.3 + + task do + Elevate::HTTP.get("http://example.com/") + end + + on_timeout do |e| + self.invocations[:timeout] = counter + self.counter += 1 + end + + on_finish do |result, ex| + self.invocations[:finish] = counter + self.counter += 1 + end + end end describe Elevate do @@ -213,8 +231,8 @@ def initialize end end - describe 'error handling' do - it "invokes specific error handlers" do + describe "error handling" do + it "invokes an error handling correspding to the raised exception" do @controller.launch(:custom_error_handlers) wait 0.5 do @@ -224,92 +242,52 @@ def initialize invocations[:error].should.be.nil end end - end - - #describe "#async" do - #describe "timeouts" do - #before do - #stub_request(:get, "http://example.com/"). - #to_return(body: "Hello!", content_type: "text/plain", delay: 1.0) - #end - - #it "does not cancel the operation if it completes in time" do - #@timed_out = false - - #async do - #timeout 3.0 - - #task do - #Elevate::HTTP.get("http://example.com/") - #"finished" - #end - - #on_finish do |result, exception| - #@result = result - #resume - #end - #end - - #wait_max 5.0 do - #@result.should == "finished" - #@timed_out.should.be.false - #end - #end - - #it "stops the operation when timeout interval has elapsed" do - #@result = nil - - #@task = async do - #timeout 0.5 + it "invokes on_error if there is not a specific handler" do + @controller.launch(:test_task, true) - #task do - #Elevate::HTTP.get("http://example.com/") + wait 0.5 do + invocations = @controller.invocations - #"finished" - #end + invocations[:error].should.not.be.nil + end + end + end - #on_finish do |result, exception| - #@result = result - #resume - #end - #end + describe "timeouts" do + it "does not cancel the operation if it completes in time" do + stub_request(:get, "http://example.com/"). + to_return(body: "Hello!", content_type: "text/plain") - #wait_max 5.0 do - #@result.should.not == "finished" + @controller.launch(:timeout_test) - #@task.timed_out?.should.be.true - #end - #end + wait 0.5 do + @controller.invocations[:timeout].should.be.nil + @controller.invocations[:finish].should.not.be.nil + end + end - #it "invokes on_timeout when a timeout occurs" do - #@result = "" - #@timed_out = false + it "stops the operation when it exceeds the timeout" do + stub_request(:get, "http://example.com/"). + to_return(body: "Hello!", content_type: "text/plain", delay: 1.0) - #async do - #timeout 0.5 + @controller.launch(:timeout_test) - #task do - #Elevate::HTTP.get("http://example.com/") + wait 0.5 do + @controller.invocations[:finish].should.not.be.nil + end + end - #"finished" - #end + it "invokes on_timeout when the operation times out" do + stub_request(:get, "http://example.com/"). + to_return(body: "Hello!", content_type: "text/plain", delay: 1.0) - #on_timeout do - #@timed_out = true - #end + @controller.launch(:timeout_test) - #on_finish do |result, exception| - #@result = result - #resume - #end - #end + wait 0.5 do + @controller.invocations[:timeout].should.not.be.nil + end - #wait_max 5.0 do - #@result.should.not == "finished" - #@timed_out.should.be.true - #end - #end - #end - #end + end + end end