From 9509d5cdff5051efd28be8af2b9b7ded5fce70bf Mon Sep 17 00:00:00 2001 From: raggi Date: Sun, 20 Jun 2010 10:35:35 -0300 Subject: [PATCH] Handle exceptions inside of body blocks that are run asynchronously --- examples/basic.ru | 9 +++++++++ lib/sinatra/async.rb | 39 ++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/examples/basic.ru b/examples/basic.ru index 31821cf..9c5f43f 100755 --- a/examples/basic.ru +++ b/examples/basic.ru @@ -18,6 +18,15 @@ class AsyncTest < Sinatra::Base raise 'boom' end + aget '/araise' do + EM.add_timer(1) { body { raise "boom" } } + end + + # This will blow up in thin currently + aget '/raise/die' do + EM.add_timer(1) { raise 'die' } + end + end run AsyncTest.new diff --git a/lib/sinatra/async.rb b/lib/sinatra/async.rb index aa3230a..593fd65 100644 --- a/lib/sinatra/async.rb +++ b/lib/sinatra/async.rb @@ -71,18 +71,18 @@ def aroute(verb, path, opts = {}, &block) #:nodoc: module Helpers # Send the given body or block as the final response to the asynchronous # request. - def body(*args, &blk) - b = super + def body(*args) if @async_running + block_given? ? async_handle_exception { super yield } : super request.env['async.callback'][ [response.status, response.headers, response.body] ] else - b + super end end - # By default schedule_async calls EventMachine#next_tick, if you're using + # By default async_schedule calls EventMachine#next_tick, if you're using # threads or some other scheduling mechanism, it must take the block # passed here. def async_schedule(&b) @@ -102,29 +102,30 @@ def async_response def async_runner(method, *bargs) async_schedule do @async_running = true - begin - h = catch(:halt) { __send__(method, *bargs); nil } - if h + async_handle_exception do + if h = catch(:halt) { __send__(method, *bargs); nil } invoke { halt h } invoke { error_block! response.status } body(response.body) end - rescue ::Exception => boom - if options.show_exceptions? - # HACK: handle_exception! re-raises the exception if show_exceptions?, - # so we ignore any errors and instead create a ShowExceptions page manually - handle_exception!(boom) rescue nil - s, h, b = Sinatra::ShowExceptions.new(proc{ raise boom }).call(request.env) - response.status = s - response.headers.replace(h) - body(b) - else - body(handle_exception!(boom)) - end end end end + def async_handle_exception + yield + rescue ::Exception => boom + if options.show_exceptions? + printer = Sinatra::ShowExceptions.new(proc{ raise boom }) + s, h, b = printer.call(request.env) + response.status = s + response.headers.replace(h) + response.body = b + else + body(handle_exception!(boom)) + end + end + # Asynchronous halt must be used when the halt is occuring outside of # the original call stack. def ahalt(*args)