AnyEvent-Task
view release on metacpan or search on metacpan
lib/AnyEvent/Task.pm view on Meta::CPAN
$log_defer_object->info('going to compute some operation in a worker');
my $checkout = $client->checkout(log_defer_object => $log_defer_object);
my $cv = AE::cv;
$checkout->(sub {
$log_defer_object->info('finished some operation');
$cv->send;
});
$cv->recv;
When run, the above client will print something like this:
$VAR1 = {
'start' => '1363232705.96839',
'end' => '1.027309',
'logs' => [
[
'0.000179',
30,
'going to compute some operation in a worker'
],
[
'0.023881061050415',
30,
'about to compute some operation'
],
[
'1.025965',
30,
'finished some operation'
]
],
'timers' => {
'computing some operation' => [
'0.024089061050415',
'1.02470206105041'
]
}
};
=head1 ERROR HANDLING
In a synchronous program, if you expected some operation to throw an exception you might wrap it in C<eval> like this:
my $crypted;
eval {
$crypted = hash('secret');
};
if ($@) {
say "hash failed: $@";
} else {
say "hashed password is $crypted";
}
But in an asynchronous program, typically C<hash> would initiate some kind of asynchronous operation and then return immediately, allowing the program to go about other tasks while waiting for the result. Since the error might come back at any time i...
AnyEvent::Task accomplishes this mapping with L<Callback::Frame>.
Callback::Frame lets you preserve error handlers (and C<local> variables) across asynchronous callbacks. Callback::Frame is not tied to AnyEvent::Task, AnyEvent or any other async framework and can be used with almost all callback-based libraries.
However, when using AnyEvent::Task, libraries that you use in the client must be L<AnyEvent> compatible. This restriction obviously does not apply to your server code, that being the main purpose of this module: accessing blocking resources from an a...
As an example usage of Callback::Frame, here is how we would handle errors thrown from a worker process running the C<hash> method in an asychronous client program:
use Callback::Frame;
frame(code => sub {
$client->checkout->hash('secret', sub {
my ($checkout, $crypted) = @_;
say "Hashed password is $crypted";
});
}, catch => sub {
my $back_trace = shift;
say "Error is: $@";
say "Full back-trace: $back_trace";
})->(); ## <-- frame is created and then immediately executed
Of course if C<hash> is something like a bcrypt hash function it is unlikely to raise an exception so maybe that's a bad example. On the other hand, maybe it's a really good example: In addition to errors that occur while running your callbacks, L<An...
=head2 Rationale for Callback::Frame
Why not just call the callback but set C<$@> and indicate an error has occurred? This is the approach taken with L<AnyEvent::DBI> for example. I believe the L<Callback::Frame> interface is superior to this method. In a synchronous program, exceptions...
How about having AnyEvent::Task expose an error callback? This is the approach taken by L<AnyEvent::Handle> for example. I believe Callback::Frame is superior to this method also. Although separate callbacks are (sort of) out-of-band, you still have ...
In servers, Callback::Frame helps you maintain the "dynamic state" (error handlers and dynamic variables) installed for a single connection. In other words, any errors that occur while servicing that connection will be able to be caught by an error h...
Callback::Frame provides an error handler stack so you can have a top-level handler as well as nested handlers (similar to nested C<eval>s). This is useful when you wish to have a top-level "bail-out" error handler and also nested error handlers that...
Callback::Frame is designed to be easily used with callback-based libraries that don't know about Callback::Frame. C<fub> is a shortcut for C<frame> with just the C<code> argument. Instead of passing C<sub { ... }> into libraries you can pass in C<fu...
It's important that all callbacks be created with C<fub> (or C<frame>) even if you don't expect them to fail so that the dynamic context is preserved for nested callbacks that may. An exception is the callbacks provided to AnyEvent::Task checkouts: T...
The L<Callback::Frame> documentation explains how this works in much more detail.
=head2 Reforking of workers after errors
If a worker throws an error, the client receives the error but the worker process stays running. As long as the client has a reference to the checkout (and as long as the exception wasn't "fatal" -- see below), it can still be used to communicate wit...
However, once the checkout object is destroyed, by default the worker will be shutdown instead of returning to the client's worker pool as in the normal case where no errors were thrown. This is a "safe-by-default" behaviour that may help in the even...
There are cases where workers will never be returned to the worker pool: workers that have thrown fatal errors such as loss of worker connection or hung worker timeout errors. These errors are stored in the checkout and for as long as the checkout ex...
Another reason that a worker might not be returned to the worker pool is if it has been checked out C<max_checkouts> times. If C<max_checkouts> is specified as an argument to the Client constructor, then workers will be destroyed and reforked after b...
=head1 COMPARISON WITH HTTP
Why a custom protocol, client, and server? Can't we just use something like HTTP?
It depends.
AnyEvent::Task clients send discrete messages and receive ordered replies from workers, much like HTTP. The AnyEvent::Task protocol can be extended in a backwards-compatible manner like HTTP. AnyEvent::Task communication can be pipelined and possibly...
The current AnyEvent::Task server obeys a very specific implementation policy: It is like a CGI server in that each process it forks is guaranteed to be handling only one connection at once so it can perform blocking operations without worrying about...
But since a single process can handle many requests in a row without exiting, they are more like persistent FastCGI processes. The difference however is that while a client holds a checkout it is guaranteed an exclusive lock on that process (useful f...
The fundamental difference between the AnyEvent::Task protocol and HTTP is that in AnyEvent::Task the client is the dominant protocol orchestrator whereas in HTTP it is the server.
In AnyEvent::Task, the client manages the worker pool and the client decides if/when worker processes should terminate. In the normal case, a client will just return the worker to its worker pool. A worker is supposed to accept commands for as long a...
The client decides the timeout for each checkout and different clients can have different timeouts while connecting to the same server.
Client processes can be started and checkouts can be obtained before the server is even started. The client will continue trying to connect to the server to obtain worker processes until either the server starts or the checkout's timeout period lapse...
The client even decides how many minimum workers should be in the pool upon start-up and how many maximum workers to acquire before checkout creation requests are queued. The server is really just a dumb fork-on-demand server and most of the sophisti...
( run in 3.100 seconds using v1.01-cache-2.11-cpan-483215c6ad5 )