AnyEvent-Task
view release on metacpan or search on metacpan
But in an asynchronous program, typically "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 in the future, the program needs a way
to map the exception that is thrown back to the original context.
AnyEvent::Task accomplishes this mapping with Callback::Frame.
Callback::Frame lets you preserve error handlers (and "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 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 asynchronous program. In your
server code, when there is an error condition you should simply "die" or
"croak" as in a synchronous program.
my $back_trace = shift;
say "Error is: $@";
say "Full back-trace: $back_trace";
})->(); ## <-- frame is created and then immediately executed
Of course if "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, AnyEvent::Task uses Callback::Frame
to throw errors if the worker process times out, so if the bcrypt "cost"
is really cranked up it might hit the default 30 second time limit.
Rationale for Callback::Frame
Why not just call the callback but set $@ and indicate an error has
occurred? This is the approach taken with AnyEvent::DBI for example. I
believe the Callback::Frame interface is superior to this method. In a
synchronous program, exceptions are out-of-band messages and code
doesn't need to locally handle them. It can let them "bubble up" the
stack, perhaps to a top-level error handler. Invoking the callback when
an error occurs forces exceptions to be handled in-band.
How about having AnyEvent::Task expose an error callback? This is the
approach taken by 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 to write error
handler callbacks and do something relevant locally instead of allowing
the exception to bubble up to an error handler.
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 handler specific to
that connection. This lets you send an error response to the client and
collect associated log messages in a Log::Defer object specific to that
connection.
"eval"s). This is useful when you wish to have a top-level "bail-out"
error handler and also nested error handlers that know how to retry or
recover from an error in an async sub-operation.
Callback::Frame is designed to be easily used with callback-based
libraries that don't know about Callback::Frame. "fub" is a shortcut for
"frame" with just the "code" argument. Instead of passing "sub { ... }"
into libraries you can pass in "fub { ... }". When invoked, this wrapped
callback will first re-establish any error handlers that you installed
with "frame" and then run your provided code. Libraries that force
in-band error signalling can be handled with callbacks such as "fub {
die $@ if $@; ... }". Separate error callbacks should simply be "fub {
die "failed becase ..." }".
It's important that all callbacks be created with "fub" (or "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: These are automatically wrapped in
frames for you (although explicitly passing in fubs is fine too).
The Callback::Frame documentation explains how this works in much more
detail.
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),
lib/AnyEvent/Task.pm view on Meta::CPAN
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 {
lib/AnyEvent/Task.pm view on Meta::CPAN
});
}, 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...
lib/AnyEvent/Task/Client.pm view on Meta::CPAN
$self->{max_workers} = defined $arg{max_workers} ? $arg{max_workers} : 20;
$self->{min_workers} = $self->{max_workers} if $self->{min_workers} > $self->{max_workers};
$self->{timeout} = $arg{timeout} if exists $arg{timeout};
$self->{max_checkouts} = $arg{max_checkouts} if exists $arg{max_checkouts};
$self->{dont_refork_after_error} = 1 if $arg{dont_refork_after_error};
$self->{total_workers} = 0;
$self->{connecting_workers} = {};
$self->{available_workers} = {};
$self->{occupied_workers} = {};
$self->{workers_to_checkouts} = {}; # used to map errors detected on worker connection to checkout callbacks
$self->{worker_checkout_counts} = {}; # used for max_checkouts "memory leak protection"
$self->{pending_checkouts} = [];
$self->populate_workers;
return $self;
}
lib/AnyEvent/Task/Client.pm view on Meta::CPAN
sub make_worker_available {
my ($self, $worker) = @_;
if (exists $self->{max_checkouts}) {
if ($self->{worker_checkout_counts}->{0 + $worker} >= $self->{max_checkouts}) {
$self->destroy_worker($worker);
return;
}
}
## Cancel any push_read callbacks installed while worker was occupied
$worker->{_queue} = [];
delete $self->{occupied_workers}->{0 + $worker};
$self->{available_workers}->{0 + $worker} = $worker;
}
sub destroy_worker {
my ($self, $worker) = @_;
t/setup-errors.t view on Meta::CPAN
use Callback::Frame;
use AnyEvent::Util;
use AnyEvent::Task::Server;
use AnyEvent::Task::Client;
use Test::More tests => 2;
## The point of this test is to verify that exceptions thrown in
## setup callbacks are propagated to the client. It also validates
## that by default workers are restarted on setup errors.
my $attempt = 0;
AnyEvent::Task::Server::fork_task_server(
listen => ['unix/', '/tmp/anyevent-task-test.socket'],
setup => sub {
$attempt++;
die "SETUP EXCEPTION $attempt";
( run in 0.682 second using v1.01-cache-2.11-cpan-9b1e4054eb1 )