Have you ever wanted to see into the future? Well, now you can! Calls to Backgroundable methods or Async Tasks in TorqueBox now return Futures that let you monitor the progress of the asynchronous task.
What the heck is a Future object?
I'm glad you asked! A Future is a proxy that provides a channel for communicating the progress and results of an asynchronous operation back to the caller. If you aren't familiar with the concept, there is a nice dry overview on Wikipedia.
Within TorqueBox, a Future allows you to see when an asynchronous task has started, completed, or raised an error. You can also access the return value of the task, and get periodic status updates from the task if the task is so configured. Some implementations of Futures allow you to cancel the running task from the Future itself, but the TorqueBox implementation is currently just one-way: data only flows from the task to the Future.
class AeroCar include TorqueBox::Messaging::Backgroundable always_background :recharge_battery, :auto_fly def recharge_battery until battery.fully_charged? battery.charge_some end end def auto_fly(to_location) flight_time = auto_pilot.fly_to( to_location ) flight_time end ... end
Checking the state of the task
Let's take a look at how you can use a Future to see the state of the asynchronous task:
future = @aerocar.auto_fly( :home ) # check to see if the task has started future.started? # check to see if the task completed future.complete? # check to see if an error occurred future.error?
Note: In all of these examples, we access the Future immediately, but you could easily stash it (in a session, for example) and access it later. In fact, it is probably much more useful when used in that fashion.
Accessing the result of the task
In addition to checking the state of the task, we can also access the result (return value) from the task:
future = @aerocar.auto_fly( :home ) # this will block until: # * the task completes # * the task generates an error # * a timeout of 30s expires puts "flying home took " + future.result # you can also specify your own timeout (in ms) puts "flying home took " + future.result( 60_000 )
A Future also implements a
method_missing that delegates to the result, blocking
with the same rules above using the default timeout.
Handling an error from the task
If an error occurs in the remote task, it will be available on the Future:
future = @aerocar.auto_fly( :home ) if future.error? puts "something went wrong: " + future.error end
This error will also contain the full backtrace from the task, making debugging a bit easier.
An asynchronous task can optionally report its status via its Future, which can be useful
for tasks that take a considerable amount of time to complete. Our
method is a good candidate for that, since it runs in a loop. First, we'll need to modify the
task method a bit to report its status:
def recharge_battery until battery.fully_charged? battery.charge_some future.status = battery.current_charge end end
The only real change we made was to add a call to
returns a proxy object with only a
status= method. This
method puts each status you give it onto a queue for the real Future object to consume.
You may call it as often as you like.
To access those statuses from the Future, simply call its
future = @aerocar.recharge_battery # each call pops the next status off of the queue, and # returns the most recent status if no new status messages # have been received puts future.status puts future.status puts future.status
The status you pass back can be any marshalable ruby object.
The behind the scenes tour
Under the hood, tasks use a HornetQ queue to communicate with the Future object. The messages sent to the Future have a time-to-live of 1 hour set on them so they won't hang around forever if you never have the Future retrieve them. The message types (:started, :status, :error, :result) have different priorities, so the Future will see the most important message first. If an error occurred, you'll want to see that instead of any status messages that may have queued up.