2013/12/24

Futures advent day 24

Day 24 - Futures compared to Callbacks

It would seem at first glance that futures provide similar benefits to managing control flow by callbacks. However, they provide several advantages in comparison.

When performing a sequence of many operations using callbacks, the ever-increasing nesting nature of the callback functions leads to an ugly indenting pyramid look in the source code.

FIRST_CB( $arg1, sub {
  SECOND_CB( $arg2, sub {
    THIRD_CB( $arg3, sub {
      FINISHED()
    });
  });
});

Because futures are connected together using the return value of a function, not through a value passed into it, they can avoid this mess and remain at a fixed indentation level. This also allows, for example, a new stage to be added between existing stages without upsetting the indentation of the following code; making neater diff output in revision control systems, and giving less chance of a merge conflict when branching.

FIRST_F( $arg1 )->then(sub {
  SECOND_F( $arg2 )
})->then(sub {
  THIRD_F( $arg3 )
})->then(sub {
  FINISHED()
});

Moreover, many other shapes of control flow start to look much more like their synchronous counterparts, precisely because they are linked together using the return values out of the individual units and require no other values to be passed in.

Possibly the most simple example of concurrent control flow is a two-way merge case, where two operations are started concurrently waiting for the result of both before continuing. Using callbacks this would need to be solved by each callback storing its result in a variable they both lexically capture, and checking in each whether both results have been provided.

my $one_result; my $two_result;

ONE_CB( sub {
  $one_result = shift;
  if( defined $two_result ) {
    FINISHED($one_result, $two_result);
  }
});
TWO_CB( sub {
  $two_result = shift;
  if( defined $one_result ) {
    FINISHED($one_result, $two_result);
  }
});

Immediately two issues come to light here. First is the repeated FINISHED code - if that were itself a further chain of operations with callbacks, this would be impossible (or at least very tedious) to repeat twice, and of course gets much worse beyond two concurrent branches. Secondly, we are testing the results for definedness - maybe undef is a perfectly valid result from each function. In that case we'd have to track two further variables to simply remark whether each operation has completed:

my $one_done; my $one_result;
my $two_done; my $two_result;

ONE_CB( sub {
  $one_result = shift;
  $one_done++;
  if( $two_done ) { ... }
});
...

This example of course only handles the success case. Imagine how much more complex the code would be if each function took two code references, one for success and one for failure, and additionally returned some kind of operation ID that would be used to cancel the operation in progress if it was no longer required. This would now need eight lexically captured variables, adding much more boilerplate control-flow noise to the code. Moreover, now there are more variables being shared among code blocks, it creates the possibility that strong reference cycles remain long after the operation has finished, failed, or been cancelled that retain an object in memory long after it was required. It may end up looking something like (and keep in mind this is the most simple case of two concurrent operations and a single "afterwards"):

my $one_done; my $one_result; my $one_failed; my $one_id;
my $two_done; my $two_result; my $two_failed; my $two_id;

my $finished = sub {
  undef $one_id; undef $two_id;
  FINISHED();
};

$one_id = ONE_CB(
  sub { $one_result = shift; $one_done++;
        $finished->() if $two_done; },
  sub { $one_failed++;
        TWO_CANCEL($two_id) if !$two_done; undef $two_id;
        FAILED() },
);
$two_id = TWO_CB(
  sub { $two_result = shift; $two_done++;
        $finished->() if $one_done; },
  sub { $two_failed++;
        ONE_CANCEL($one_id) if !$one_done; undef $one_id;
        FAILED() },
);

By comparison, the Future needs_all constructor neatly wraps up all this implicit behaviour, removing the control- and data-flow noise from the code, and much more concisely expressing its intent.

Future->needs_all(
  ONE_F(), TWO_F(),
)->then(sub {
  my ( $one_result, $two_result ) = @_;
  FINISHED($one_result, $two_result);
})->else(sub {
  FAILED();
})->get;

So, there we have it. In the past 24 posts we have seen how Futures can neatly express all the various kinds of control-flow logic we typically find in a Perl program, and also express the additional shapes of code we find useful when working with asynchronous and concurrent programming. This neatness ultimately comes from the fact that a Future object is a first-class value representing the operation itself, and being first-class comes the ability to combine it with others to produce new first-class values to represent combinations of this operation with others.

Futures allow the control- and data-flow structure of a program to be inherently expressed together, describing the dependency relationships between individual operations. Both successful results and failures are automatically propagated up from the atomic units that create them, through the various layers of logic up towards the topmost level of the program. Actions in progress can be abandoned when no longer required, causing a graceful cancellation of the activity that had been pending up until that time.

Futures change state from pending to complete when they are provided with a result, meaning that when they become ready they already have the results stored in them. This makes for convenient control-flow that coincides with data-flow; ensuring that the result of an operation is passed to the next operation in the sequence at the time it is executed. This convenient pairing of control- and data-flow stands in contrast to the split nature of other kinds of concurrency control, such as callback functions or locks and mutexes, which generally only manage the flow of control and require other techniques like lexical variables shared between multiple closures to provide the data flow. Such sharing of mutable state between domains of concurrency is the source of many kinds of concurrency bug which cannot happen with Futures.

In summary, Futures provide a useful abstraction to build all kinds of program logic on top of, whether it is initially intended to be asynchronous or not. Middle-level library modules especially will benefit from using Futures to express intent and combine actions together, as they will then automatically be able to make use of asynchronous and concurrent abilities of the base layers they are built from, without having to expressly depend on those being present.

<< First | < Prev

2013/12/23

Futures advent day 23

Day 23 - Additional Benefits of Futures

Beyond simply being able replicate regular perl control-flow styles, building program logic on top of Futures has many additional benefits.

The primary benefit is of course the ability to work asynchronously, allowing the concurrency of being able to start multiple operations and wait for them all to succeed. We have seen this with the tree-forming needs_all, needs_any and wait_any constructor methods, and the fmap utility.

my $f = Future->needs_all(
  ONE(), TWO(), THREE(),
);

my ( $one, $two, $three ) = $f->get;
my $f = fmap1 {
  FUNC($_),
} foreach => [ @VALUES ], concurrent => 10;

my @results = $f->get;

Because Futures represent an operation in progress they are an ideal place to provide cancellation logic, allowing the consumer of the would-be result to abandon it and declare it no longer useful. This can be done explicitly by calling the cancel method.

sub PROCESS_REQUEST {
  my ( $req ) = @_;
  my $f = GET_RESULT( $req->PARAMS );

  $f->on_done(sub {
    $req->REPLY( @_ );
  });
  $req->ON_CLIENT_CLOSE(sub {
    $f->cancel;
  });

  return $f;
}

A failed Future provides an analog to a thrown exception, causing an entire chain or tree of operations to be abandoned and propagating back up towards the caller until a suitable error-handling block is found. In addition however, a failed Future can provide a full list of values as well as a single string. This allows error handlers to be much more fine-grained in their ability to distinguish different types of error.

my $f = GET("http://my-site-here.com/")
  ->else_with_f(sub {
    my ( $f, $failure, $op ) = @_;
    # may be           http, $request, $response
    if( $op eq "http" and $_[3]->code == 500 ) {
      say "Server is unavailable";
      return Future->new->done( $HOLDING_PAGE );
    }
    return $f;
  });

Middleware library functions can easily be built on top of basic actions implemented by futures and providing more of their own. When writing and testing sub libraries it becomes a simple matter to use these futures within the unit-tests themselves as a way to mock out responses from lower levels of logic in order to test the library code in isolation. For example, if we wish to unit-test a middleware function that uses an HTTP user agent to fetch a page, parse it, and return the page title we can provide a simple tiny user agent wrapper that just returns a new future, and does nothing else:

my $resp_f; my $url;
sub GET {
  ( $url ) = @_;
  return $resp_f = Future->new;
}
...

Our unit test can then drive the behaviour of that "user agent", as well as testing the function's results:

...
my $f = get_page_title( \&GET, "http://my-site-here.com/" );

isa_ok( $f, "Future" );
is( $url, "http://my-site-here.com/" );
ok( defined $resp_f, 'Response future created' );
is( !$f->is_ready, '$f is not yet ready' );

$resp_f->done( HTTP::Response->new(
  200, "OK", [ Content_type => "text/html" ],
  "<html><head><title>My title</title></head><body /></html>",
));

ok( $f->is_ready, '$f is now ready after HTML response' );
is( scalar $f->get, "My title", '$f->get returns title' );

Because we have a future to represent both sides of the function (its caller and the inner GET function it uses to fetch the page content) we have been able to easily test the function in the middle. The unit test script itself at various times takes on the role either of the outside caller or the inner HTTP user agent, and is able to easily interleave the two to ensure a neat test.

<< First | < Prev | Next >


Edit 2013/12/29: Updated to use else_with_f

2013/12/22

Futures advent day 22

Day 22 - Equivalence of Control Flow

Over the past twenty or so posts we have seen many examples of control-flow structures using Futures to help write possibly-asynchronous, possibly-concurrent code in simple, neat ways which mirror the regular kinds of synchronous control flow that Perl already provides. You may by now have come to the conclusion that it is possible to duplicate any kind of control-flow logic using futures.

Simple sequencing of one operation then the next is done using then:

FIRST();
SECOND();
THIRD();
FIRST()
  ->then(sub { SECOND() })
  ->then(sub { THIRD() });

Sequencing is interrupted by thrown exceptions, which may be caught using else:

try { FIRST();
      SECOND();
} catch {
      CATCH();
};
FIRST()
  ->then(sub { SECOND () })
  ->else(sub {
     CATCH()
  });

Conditional execution can be performed by using a regular if inside a then_with_f sequence, to return either a new or the original future:

FIRST();

if( $COND ) {
  SECOND();
}
FIRST()
  ->then_with_f(sub { my ( $f ) = @_;
    if( $COND ) {
      return SECOND() }
    return $f; });

Repeated loops such as do {} while can be implemented using Future::Utils::repeat():

do {
  BODY()
} while( $COND )
repeat {
  BODY();
} while => sub { $COND };

A pre-condition while {} loop is a little trickier because it still needs a future to return if the loop body doesn't execute at all. The simplest way is simply to skip the repeat call if it isn't required:

 
while( $COND ) {
  BODY();
}
!$COND ? Future->new->done()
    : repeat {
        BODY();
      } while => sub { $COND };

A foreach {} loop can also be written using repeat:

foreach my $VAR ( @LIST ) {
  BODY();
}
repeat { my $VAR = shift;
  BODY();
} foreach => [ @LIST ];

The values generated by a map {} call can be created using Future::Utils::fmap:

my @values = map {
  BODY();
} @LIST;
my $value_f = fmap {
  BODY();
} foreach => [ @LIST ];

In every case here, the future version of the control flow structure yields a future, which of course can then be combined inside other structures as required:

do {
  FIRST();
  foreach ( @LIST ) {
    SECOND($_);
  }
  if( $C ) {
    THIRD();
  }
} until( $HAPPY )
repeat {
  FIRST()
    ->then(sub { repeat {
      SECOND(shift);
    } foreach => [ @LIST ] })
    ->then_with_f(sub { my ( $f ) = @_;
      if( $C ) { return THIRD(); }
      return $f; });
} until => sub { $HAPPY };

<< First | < Prev | Next >


Edit 2013/12/29: Updated to use then_with_f

2013/12/21

Futures advent day 21

Day 21 - Implementing Timeouts

Suppose now we have a function called TIMEOUT(), similar to SLEEP() except it returns a future that will fail at some later time. We can combine this with another future using the wait_any combination function, to create a timeout. Like needs_any and needs_all, this combination function takes a list of futures and returns a future representing their combination. In this case the combination will complete as soon as any of the individual futures completes, regardless of success or failure. At this point, any other pending futures are cancelled.

my $f = Future->wait_any(
  GET( "http://my-site-here.com/might-be-slow" ),
  TIMEOUT( 20 ),
);

my $page = $f->get;

Here we are using wait_any to add timeout behaviour to a page get operation. If the HTTP operation either succeeds or fails before the 20 seconds are up then $f will complete with the same result or failure, and the timeout will be cancelled. Alternatively, if it still has no result after 20 seconds then the timeout future will fail, causing the overall future $f to fail, and the HTTP operation will be cancelled. In this way we can easily add timeout behaviour to any future-returning function, without every operation individually having to implement timeouts of any kind.

<< First | < Prev | Next >

2013/12/20

Futures advent day 20

Day 20 - Automatic Cancellation

A few days ago we saw two similar ways to combine a list of futures into a single future; needs_all and needs_any. In both of these combinations it can happen that the combined future has its result determined before all of its components are ready - needs_all if any component fails, or needs_any if any component is successful. In that case there is no need to continue running the remaining operations, as their outcome cannot further influence the combination.

Yesterday we saw how futures support a cancel operation which cascades back up the tree of operations, causing individual components of an action to be cancelled, and generally all the operations halted. Naturally, both needs_all and needs_any will do this when they are cancelled, causing all of their still-pending components to be cancelled. But additionally, both will cancel remaining pending futures automatically when their result is already known. needs_all will cancel any futures that remain pending when it has to fail because a component failed, and needs_any will cancel any that remain when it already has a result.

For example, recalling day 13 where we first saw needs_all:

my $f = Future->needs_all(
  GET( "http://my-site-here.com/page1" ),
  GET( "http://my-site-here.com/page2" ),
  GET( "http://my-site-here.com/page3" ),
);

my @pages = $f->get;

As was mentioned before, if any of these page fetches fails then the overall operation will fail too, and the get method will throw this exception. What wasn't mentioned before is that since at this point any other pending fetch operations have no further bearing on the result of the combined future, there is no need to continue running them. In this situation needs_all will cancel them.

<< First | < Prev | Next >

2013/12/19

Futures advent day 19

Day 19 - Cancellation

As a future is a first-class object that represents an ongoing operation or activity, it serves as an ideal place to interact with that activity. As well as waiting for success or failure of this operation, we can also cancel it midway through by using the cancel method.

my $f = repeat {
  my ( $prev ) = @_;
  SLEEP($prev ? 10 : 0)->then( sub {
    GET("http://my-site-here.com/try-a-thing");
  });
} until => sub { $_[0]->get->code == 200 };

$SIG{INT} = sub { $f->cancel };

$f->get;

Here we have started an HTTP GET operation in a repeat loop, hoping to eventually achieve a success. If in the meantime the user gets bored and hits Ctrl-C, the SIGINT handler cancels the future representing the repeat loop. This will cause it to not continue executing another attempt, and also cascades the cancel call into its currently-running attempt. This cancel call continues to cascade down towards the individual basic futures that the entire operation is composed of. The behaviour of the then chain, for example, depends on how far the operation has progressed; in this case cancelling either the SLEEP or the GET.

The basic futures provided by event systems (such as the SLEEP call) can use the on_cancel method to register a code block to call if the future is cancelled. This could perhaps stop the event that they would use to implement the timer behaviour.

sub SLEEP {
  my ( $delay ) = @_;

  my $f = Future->new;

  my $id = EventSystem->timed_event(
    $delay, sub { $f->done }
  );

  $f->on_cancel( sub {
    EventSystem->stop_timed_event( $id );
  });

  return $f;
}

This cascading of cancellation requests allows future-based code to easily support cancelling partially-complete operations without having to implement the logic to track progress and direct the request appropriately. Simply provide on_cancel handlers for the basic future operations that make the overall activity and the request will be handled appropriately.

<< First | < Prev | Next >

2013/12/18

Futures advent day 18

Day 18 - Using fmap Concurrently

The main difference between these seemingly-similar utilities of repeat and fmap is that repeat is intended for performing a given action repeatedly where each attempt may in some way depend on the result of the previous, whereas fmap is intended for performing a given action independently across a given list of items. Because these attempts are independent, there is no requirement to run just one of them at once. As we are able to perform actions asynchronously and concurrently using futures, it makes sense to allow fmap to do this. This is done by passing a number to the concurrent argument of fmap.

my $f = fmap {
  my ( $id ) = @_;
  GET("http://my-site-here.com/items/$id")
} foreach => [ 1 .. 200 ],
  concurrent => 10;

my @pages = $f->get;

The fmap utility can then start as many concurrent HTTP GET operations as we have asked for, 10 in this case, and tries to keep this number of them running as they complete; starting another each time. When finally all of them are complete, the returned $f itself then completes, giving the results as before.

Because we are now running multiple operations concurrently, it could be the case that the ten concurrently-running items complete in a different order than the order the IDs appeared in the original list. fmap takes care of this, ensuring it returns results from the $f->get call in the original input order regardless. It behaves similar to a perl map {} operator, concatenating the results of each individual attempt. Because it can't know in advance how many results will come back and in which order, to achieve this concatenation it has store the results of each call into a list of array references, and flatten it at the end when it is returned. Often this is unnecessary as we know each attempt will only yield a single reply - in this case we can use the more efficient fmap1, which takes exactly one result from each attempt.

my $f = fmap1 {
  my ( $id ) = @_;
  GET("http://my-site-here.com/items/$id")
} foreach => [ 1 .. 200 ],
  concurrent => 10;

my @pages = $f->get;

In other situations we may not even need to return any results at all, and are using it simply to iterate a block of code concurrently (rather than using repeat). For that situation, fmap_void is similar again, but does not bother to collect results at all; when it completes it yields an empty list from its future.

<< First | < Prev | Next >

2013/12/17

Futures advent day 17

Day 17 - Returning a List of Results

We have now seen how Future::Utils::repeat can create control structures to repeatedly run a piece of code returning a future, until some ending condition occurs. When it finishes, the result of the repeat future yields the value of the final attempt it tried.

Sometimes though, we want to run an operation repeatedly and collect up all the results it yielded, rather than just the final one. If we have a list of things to iterate on and perform an operation on each we may wish to gather all the results from this. To do that we can use Future::Utils::fmap.

my $f = fmap {
  my ( $id ) = @_;
  GET("http://my-site-here.com/items/$id")
} foreach => [ 1 .. 200 ];

my @pages = $f->get;

Similar to the cases of repeat, here the returned future once again represents the overall operation of running the loop, and will only complete once the loop has finished running. Each item in the foreach list is given to each call to the code block inside. However, instead of the future yielding just the last result, all of the results are collected up and returned in a list.

<< First | < Prev | Next >

2013/12/16

Futures advent day 16

Day 16 - Iterating Over a List

Yesterday we looked at the repeat utility, and how it can form a future representing a repeated sequence of attempts to perform some action, when each attempt returns a future to represent it. That example used the result of each attempt to decide if the overall operation should be considered a success, or to have another go.

Sometimes, the number of times to run a loop is known in advance, or at least does not depend on the result, because we wish instead to iterate over items of some given input data. To perform this we can use repeat like a perl foreach {} loop, taking a list of items from an array reference, and invoking the action block once for each item in the list. As before, it returns a future which this time will complete once the input list is exhausted and the final item's attempt has completed.

my %items = ...;

my $f = repeat {
  my ( $key ) = @_;
  PUT("http://my-site-here.com/items/$key",
    $items{$key}
  )
} foreach => [ keys %items ];

$f->get;

Because a foreach list is provided, the repeat function passes the body code successive values from it on each iteration. We don't need to supply a while or until condition here, because the loop knows to terminate when the list is exhausted.

<< First | < Prev | Next >

2013/12/15

Futures advent day 15

Day 15 - Performing an Action Repeatedly

So far in this series we have been performing future-based actions that might succeed, or might fail, and combined them up into larger operations of success or failure. One fairly standard way of handling failures in application logic is by attempting to retry. We couldn't simply put a future get call inside a perl while {} loop, because then the loop itself would block until success and we couldn't do anything else at the same time.

Instead, we can use a function from the Future::Utils package called repeat. This takes a block of code which returns a future, and returns a future representing the operation of repeatedly calling that code and waiting for its future to finish, until some ending condition is satisfied. This condition is specified by a second block of code given as either the until or while arguments.

use Future::Utils qw( repeat );

my $f = repeat {
  POST("http://my-site-here.com/new", $form)
} until => sub { $_[0]->get->code == 200 };

my $page = $f->get;

Each time around the loop we will attempt to POST to the resource. When this HTTP operation finishes the until condition is tested. In this example, we are looking for a 200 OK response from HTTP. If we get any other response, we'll try again. The entire operation is represented by the future returned in $f. When we eventually get a response that is deemed acceptable, the overall future $f will receive its result.

In this rather simple example we'll retry each attempt immediately, with no limit on the number of retries. A more practical example would apply a delay between each call, and limit the number of retries. Let us now suppose we have a sleep-like function, DELAY, which returns a future that will complete some number of seconds later.

my $retries = 5;

my $f = repeat {
  my ( $prev ) = @_;

  DELAY($prev ? 10 : 0)
    ->then( sub {
      POST("http://my-site-here.com/new", $form)
    })
} while => sub { $_[0]->get->code != 200 and
                 $retries-- };

my $page = $f->get;

Here we keep a count of the number of retry attempts, and stop retrying once we run out of them. We can implement the pause between retries by asking the DELAY function for a pause of either 0 or 10 seconds, depending on whether there had been a previous failure (this being indicated by the presence or absence of the previous future being passed in to the code body).

<< First | < Prev | Next >

2013/12/14

Futures advent day 14

Day 14 - Waiting for Alternatives

Yesterday's look at needs_all was our first look into an actual concurrent use-case where we can really benefit from the asynchronous nature of a Future, to combine multiple page requests concurrently and wait for them all to respond.

Today we look at a similar function, needs_any, which also takes a list of individual futures and returns a new future to represent their combination. In this instance, the new future will complete the first time any of its components completes successfully, or will fail once all of its individual components have failed.

my $f = Future->needs_any(
  GET( "http://uk.my-site-here.com/cache/a" ),
  GET( "http://de.my-site-here.com/cache/a" ),
  GET( "http://us.my-site-here.com/cache/a" ),
);

my $resp = $f->get;

Here we have started three different HTTP GET operations from three different servers, in the hope that whichever one is closest will respond first, thus making the overall future return the page. If that server happened to return an error, this will be ignored while there are still other alternatives available. The overall future will only yield an error if all the servers do. This gives us an easy way to attempt a variety of strategies to provide an answer to a given question.

<< First | < Prev | Next >

2013/12/13

Futures advent day 13

Day 13 - Waiting For Multiple Futures

Yesterday we took our first look at some actually-asynchronous uses of a Future, by taking a look at Net::Async::HTTP, but so far in this series we haven't actually seen anything that properly makes use of a Future, that could not be done just as easily synchronously.

Now we can take our first look at some code that uses the asynchronous nature of an HTTP user agent to fetch multiple resources concurrently. By using the Future->needs_all constructor we can create multiple futures to fetch individual pages, then combine them together into a single future which we can then wait on by calling get.

my $f = Future->needs_all(
  GET( "http://my-site-here.com/page1" ),
  GET( "http://my-site-here.com/page2" ),
  GET( "http://my-site-here.com/page3" ),
);

my @pages = $f->get;

Here we have created three individual futures representing an HTTP page GET, and wrapped them all in a single wrapper future which will complete when all of its components are complete. The get call on this future then returns a list composed of the result of each individual future. Because we know each one only returns the HTTP::Response object itself, we can just get these as a list. If any of the individual HTTP GET futures fails, the overall combined future will fail too. This means we can neatly handle any exceptions that happen.

When using a properly asynchronous HTTP user agent we are now able to perform these multiple GETs concurrently. Each call to GET starts the fetch operation, which can now all complete concurrently eventually returning their results. This is our first real example of futures providing neat concurrency-enabling code.

<< First | < Prev | Next >

2013/12/12

Futures advent day 12

Day 12 - Asynchronous Await

Up until now in these posts I have been deliberately vague on the subject of how the HTTP GET function actually works. All I have implied is that it takes a page URL and returns a Future that will eventually yield an HTTP::Response containing the resource. As I said right back in the first post on day 1, Futures work just fine if every result is in fact a synchronous return. Thus, we could choose to implement an entirely synchronous version of this function using LWP::UserAgent (and taking care not to confuse its get method with the unrelated Future one):

use Future;
use LWP::UserAgent;
my $ua = LWP::UserAgent;

sub GET
{
  my ( $url ) = @_;
  
  Future->call( sub {
    Future->wrap( $ua->get( $url ) )
  });
}

Here we have used Future->wrap to conveniently create a future to contain the result of a successful call to the UserAgent's get method (remember, this is HTTP GET and unrelated to the future get method). This call is itself wrapped in a Future->call block to ensure that if the UserAgent throws an exception, this will be wrapped in a failed future.

Alternatively, if we wanted some level of asynchronous behaviour, because we wish to perform multiple concurrent actions, or mix this with other code, we could instead use Net::Async::HTTP which already provides a GET method having the semantics we want:

use IO::Async::Loop;
use Net::Async::HTTP;

my $loop = IO::Async::Loop->new;
my $http = Net::Async::HTTP->new;
$loop->add( $http );

sub GET
{
  my ( $url ) = @_;
  return $http->GET( $url );
}

This one is implemented internally by Net::Async::HTTP returning a subclass of Future provided by IO::Async itself. This subclass understands how to wait for futures that are not yet ready, by invoking the containing loop until the result is available. Other event systems can be similarly catered for by subclassing Future to provide a suitable await method, which is used by get. We could even, if we were inclined towards threads, implement a subclass of Future which used some kind of thread-based synchronisation and communication to await the result being supplied by code running in a different thread.

Because they both conform to the interface of "returning a Future", either of these above implementations of GET are suitable for any of the examples we have seen so far, or will see in the next examples to come. So too would any other implementation that provides this interface. Because of this we find that Futures provide a powerful way to write the intermediate layers of processing and "business logic" in application libraries, which can remain agnostic on such low-level details as what event system is being used, or even if one is being used at all.

<< First | < Prev | Next >

2013/12/11

Futures advent day 11

Day 11 - Transforming Results or Failures

While we are on the subject of convenient shortcuts to neaten up certain kinds of Future result handling, another common pattern that arises is the case of an immediate return; either a then block that returns an immediate result based on the given values, or an else block that returns an immediate failure based on the given one. In each of these cases the transform method allows us to write this more conveniently.

sub GET_title
{
  my ( $url ) = @_;

  GET_checked($url)->transform(
    done => sub { get_page_title( $_[0] ) },
  );
}

If the GET_title is successful then the code passed in the done argument is invoked with the result as the argument list, and whatever it returns is used as the result of the future that transform returned. This is a little shorter and more convenient than the functionally-equivalent then block returning an immediate future.

We can similarly use transform on the failure message to create a different message, perhaps with more details in it.

sub GET_title
{
  my ( $url ) = @_;

  GET_checked($url)->transform(
    done => sub {
      get_page_title( $_[0] )
    },
    fail => sub {
      my $message = shift;
      "Cannot get title of $url - $message", @_;
    }
  );
}

Because the failure may contain other values giving more context, we need to be careful to preserve them. This is done most easily by shifting the message, so the remaining values appear in @_.

<< First | < Prev | Next >

2013/12/10

Futures advent day 10

Day 10 - Conditional Chaining

Over the past few days we have seen uses of then and else to chain sequences of code together. Sometimes the code in a then or else block will inspect its given arguments, and decide that it doesn't in fact want to perform any other action so just returns a new Future containing the same values again.

Instead of this, we can use the methods then_with_f and else_with_f, which are variations of then and else which pass the code block the actual Future object they are invoked on in addition to the result or failure list it contains. The code can then either construct a new Future containing different results, or just return that one directly.

sub GET_checked
{
  my ( $url ) = @_;

  GET( $url )->then_with_f( sub {
    my ( $f, $resp ) = @_;

    return $f if $resp->code =~ m/^[23]../;

    return Future->new->fail($resp->code." ".$resp->message, $resp);
  });
}

This is not only neater and clearer to read, but is also more efficient because it doesn't need to create yet another Future object just to contain the same result that the one it was invoked on already had. Equally, an else_with_f can neaten up the way we sometimes simply propagate a failure if we decide not to handle it.

my $f = GET_checked("http://my-site-here.com/a-page")
  ->else_with_f( sub {
     my ( $f, $failure, $response ) = @_;

     return $f if $response->code != 500;

     return Future->new->done(
       HTTP::Response->new( 200, "OK", [],
         "Server is down, but have some fluffy kittens instead")
     );
  });

<< First | < Prev | Next >


Edit 2013/12/29: Updated for then_with_f and else_with_f

2013/12/09

Futures advent day 9

Day 9 - The call and wrap methods

Over the past few days of code, a couple of common patterns have arisen. The first is that often a then or else block wishes to simply return a future immediately containing some results. Until now we have used the done method on a new future, which returns the future itself. Because this happens so often, a slightly neater (and internally more-efficient) form can be used instead, by using the wrap class method:

$f = Future->wrap( @values );

This is equivalent to constructing a new future and setting the values on it, except in the case that it was invoked with exactly one argument, and that argument was already a Future object. In that case, the future is returned directly.

Another pattern is that a block of code is called, and if it throws an exception it instead returns a future with that exception as its failure. The call class method takes a block of code and does this; returning either an immediately failed future if the code died, or returns the future that the code itself returned if it did not throw an exception.:

$f = Future->call( sub { ... } );
$f = Future->call( \&func, @arguments );

<< First | < Prev | Next >

2013/12/08

Futures advent day 8

Day 8 - Handling Successes and Failures

We have seen how the then and else methods allow us to chain a sequence of code together, dependent on whether the previous succeeded or failed, in order to implement control flow that processes values and handles exceptions. Because each code block itself returns a future, we can "catch" exceptions and turn them into successful results, or we can throw "exceptions" if we don't like a successful result we received. However, sometimes it's convenient to split the control flow after a future, invoking a choice of two different code blocks depending on its success or failure. This way, the "then" handling code isn't going to be confused by a successful return from an exception handler, nor is the "else" code going to get confused if the "then" block dies. To do this, we can use the two-argument version of then.

my $f = GET("http://my-site-here.com/news")
  ->then(
    sub { # on success
      my ( $response ) = @_;
      my $title = get_page_title( $response->content );
      return Future->new->done( $title );
    },
    sub { # on failure
      my ( $failure, $response ) = @_;
      if( $response->code == 204 ) { # No Content
         return Future->new->done( "(no title)" );
      }
      return Future->new->fail( $failure, $response );
    },
  );

It would not have been possible to easily write this code by a sequence of single-argument then and else; because either the else block following the then would be confused by any exceptions thrown by the get_page_title() function, or the then block following the else would be confused by receiving a string rather than the HTTP response. While this could be detected by checking the types of the arguments, it turns out to be more useful and safer in practice to have a two-argument form of then which splits the control flow appropriately.

<< First | < Prev | Next >

2013/12/07

Futures advent day 7

Day 7 - Failures with Values

Yesterday we saw how we can use an else block to catch an exception and make it return a different value, including a success. Of course, it was a rather blunt way to handle a failure because it just turned any failure into a 599 HTTP status.

This is perhaps a good time to explain that, like done can be passed a list of multiple values for on_done and then to receive, so too can fail. The first value in this list must be true and ought to be well-behaved as a string, because get will use it as the exception it will die() with if it has to. Any other values in the list are ignored by get, but are passed in to on_fail and else.

Lets now make a better version of GET_checked that passes the failing HTTP response along with the failure.

sub GET_checked
{
  my ( $url ) = @_;

  GET( $url )->then( sub {
    my ( $r ) = @_;
    if( $r->code !~ m/^[23]../ ) {
      return Future->new->fail( $r->code." ".$r->message, $r );
    }
    else {
      return Future->new->done( $r );
    }
  });
}

We can now handle our errors better by only masking one kind of error that we are interested in.

my $f = GET_checked("http://my-site-here.com/a-page")
  ->else( sub {
     my ( $failure, $response ) = @_;

     if( $response->code != 500 ) {
       return Future->new->fail( $failure, $response );
     }

     return Future->new->done(
       HTTP::Response->new( 200, "OK", [],
         "Server is down, but have some fluffy kittens instead")
     );
  });

my $response = $f->get;

Now if the server fails with a 500 error, we will think of fluffy kittens instead. But any other error is still reported as a real failure.

<< First | < Prev | Next >

2013/12/06

Futures advent day 6

Day 6 - Control Flow with Failures

We have now seen how done is used to mark a Future as successfully complete thus causing its get method to return, and how then is used to create a chain of activity between them when they succeed. We have also seen how fail can be used to mark a Future as having failed thus causing its get method to throw the exception. We can complete this set of methods by adding else, which is used to chain an activity after a Future if it fails.

my $f = GET("http://my-site-here.com/a-page")
  ->else( sub {
    return Future->new->done(
      HTTP::Response->new( 599, "Failed", [], "GET failed" )
    );
  });

my $response = $f->get;

Here we are using else to act as a kind of try/catch block, taking any exception and turning it into a successful response instead. If the GET future returns successfully, the else code doesn't get to run; instead the success gets passed right through to $f.

<< First | < Prev | Next >

2013/12/05

Futures advent day 5

Day 5 - Causing a Future to fail

Yesterday we looked at how to handle a Future that has failed. We can cause a Future to fail by invoking its fail method instead of done.

sub GET_checked
{
  my ( $url ) = @_;

  GET( $url )->then( sub {
    my ( $r ) = @_;
    if( $r->code !=~ m/^[23]../ ) {
      return Future->new->fail( $r->code." ".$r->message );
    }
    else {
      return Future->new->done( $r );
    }
  });
}

Here we have created a checked version of our hypothetical HTTP GET function, which returns a Future that will only be successful if the HTTP response was in the 2xx or 3xx ranges. If it gets an error (4xx or 5xx) then the Future will fail.

Another way to cause a future to fail is to simply throw a regular perl exception from a then or else code block. Each call to a code reference passed to these methods is wrapped in a eval {} block and causes the future to fail if the code throws an exception. This makes it easier to handle because now the chained future will fail, rather than causing the code that marked the preceding future as complete to propagate the exception it threw.

my $f = GET_checked("http://my-site-here.com/products.xml")
  ->then( sub {
    my ( $response ) = @_;
    if( $response->content_type ne "text/xml" ) {
      die "Expected Content-type: text/xml";
    }
    return Future->new->done( $response );
  });

This code is equivalent to code which uses Future->new->fail - the caller will not directly die with that exception, but instead the returned future will fail.

<< First | < Prev | Next >

2013/12/04

Futures advent day 4

Day 4 - Coping with failure

So far every Future example we have seen has resulted in an eventual success. But, like regular perl functions can either return a result or die() an exception, so too can Futures either complete with a result or a failure. Whereas the on_done method attaches code to handle a successful result we can use on_fail similarly to attach code to handle a failure.

my $f = GET("http://my-site-here.com/");

$f->on_done( sub {
  my ( $response ) = @_;
  print "Got a response\n";
});

$f->on_fail( sub {
  my ( $failure ) = @_;
  print STDERR "It failed: $failure\n";
});

If the get method receives a failure on the Future instead of a success, it throws the message from that failure as a perl exception, causing the method to die instead of return. This leads to many cases of Future code being able to look and act very similarly to regular perl function calls, with values being returned, or exceptions being thrown and caught.

<< First | < Prev | Next >

2013/12/03

Futures advent day 3

Day 3 - Chaining Futures to perform a sequence of actions

A more powerful ability than on_done, and one which in practice turns out to be used much more often, is provided by the then method. This method itself returns a Future, and expects code that will return a Future when it is invoked. In this way it provides an ability to perform a second action which returns a Future after the first one completes, and returns a Future that represents the complete combination of the first and the second.

my $f = GET("http://my-site-here.com/first")
  ->then( sub {
    my ( $first_response ) = @_;
    my $path = path_from_response($first_response);
    GET("http://my-site-here.com/$path);
  });

my $second_response = $f->get;

In this second example after the first page has been returned we then fetch a second page by somehow using the result of the first page's response to give a path name for the second. The returned Future in $f will be complete after this second response has been received. The call to get will then wait for this to happen.

Of course, we are not limited to simply two actions - because the then method returns another Future, we can simply call then on that as well to chain as many steps of a process as are necessary to complete it. This leads to a neat sequence of code, quite unlike the ever-indenting nature of passing callback functions.

<< First | < Prev | Next >

2013/12/02

Futures advent day 2

Day 2 - Doing something when a Future completes

Because a Future object represents an operation that is currently in progress or has finished it provides the ideal place to attach logic to ask for further activity to happen when the original has completed. The most immediate way is to pass a piece of code in a sub reference to the on_done method.

my $f = GET("http://my-site-here.com/");

$f->on_done( sub {
  my ( $response ) = @_;
  print "Got a response\n";
});

In this case, as for many posts yet to come, we are presuming some hypothetical GET function that returns a Future wrapping an HTTP operation in the obvious manner.

If the returned Future is already complete (perhaps because it is a synchronous client that always completes immediately, or because it was served internally from a cache) then the on_done method invokes the code immediately. If not, the code is stored inside the Future object itself for when it eventually gets its result.

<< First | < Prev | Next >

2013/12/01

Futures advent day 1

It is traditional around this time of year for Perl blogs to publish an advent calendar - a series of 24 short little posts around a common theme.

People have suggested I might write one about Futures, so here goes...

Day 1 - Futures can return values synchronously

You don't in fact have to use a Future for anything asynchronous. A simple synchronous-returning function can use them too.

use Future;

sub sum
{
   my @numbers = @_;
   my $total = 0;
   $total += $_ for @numbers;
   return Future->new->done( $total );
}

say "The total is " . sum( 10, 20, 30 )->get;

It may not be immediately obvious currently why you want to do this, but I hope to motivate why over the following 23 posts...

<< First | < Prev | Next >