AnyEvent-Task
view release on metacpan or search on metacpan
the normal case where no errors were thrown. This is a "safe-by-default"
behaviour that may help in the event that an exception thrown by a
worker leaves the worker process in a broken/inconsistent state for some
reason (for example a DBI connection died). This can be overridden by
setting the "dont_refork_after_error" option to 1 in the client
constructor. This will only matter if errors are being thrown frequently
and your "setup" routines take a long time (aside from the setup
routine, creating new workers is quite fast since the server has already
compiled all the application code and just has to fork).
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 exists any methods on the checkout will
immediately return the stored fatal error. Your client process can
invoke this behaviour manually by calling the "throw_fatal_error" method
on a checkout object to cancel an operation and force-terminate a
worker.
Another reason that a worker might not be returned to the worker pool is
if it has been checked out "max_checkouts" times. If "max_checkouts" is
specified as an argument to the Client constructor, then workers will be
destroyed and reforked after being checked out this number of times.
When not specified, workers are never re-forked for this reason. This
parameter is useful for coping with libraries that leak memory or
otherwise become slower/more resource-hungry over time.
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 in the future even
compressed like HTTP.
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 holding up other connections.
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 for supporting DB transactions
for example). With a FastCGI server it is assumed that requests are
stateless so you can't necessarily be sure you'll get the same process
for two consecutive requests. In fact, if an error is thrown in the
FastCGI handler you may never get the same process back again,
preventing you from being able to recover from the error, retry, or at
least collect process state for logging reasons.
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 as possible until the client
dismisses it.
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 lapses. As well as freeing you from having
to start your services in the "right" order, this also means servers can
be restarted without throwing any errors (aka "zero-downtime restarts").
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 sophistication is in the
asynchronous client.
SEE ALSO
The AnyEvent::Task github repo
<https://github.com/hoytech/AnyEvent-Task>
In order to handle exceptions in a meaningful way with this module, you
must use Callback::Frame. In order to maintain seamless request logging
across clients and workers, you should use Log::Defer.
There are many modules on CPAN similar to AnyEvent::Task.
This module is designed to be used in a non-blocking, process-based unix
program. Depending on your exact requirements you might find something
else useful: Parallel::ForkManager, Thread::Pool, or an HTTP server of
some kind.
If you're into AnyEvent, AnyEvent::DBI and AnyEvent::Worker (based on
AnyEvent::DBI), AnyEvent::ForkObject, and AnyEvent::Fork::RPC send and
receive commands from worker processes similar to this module.
AnyEvent::Worker::Pool also has an implementation of a worker pool.
AnyEvent::Gearman can interface with Gearman services.
If you're into POE there is POE::Component::Pool::DBI, POEx::WorkerPool,
POE::Component::ResourcePool, POE::Component::PreforkDispatch,
Cantella::Worker.
BUGS
Although this module's interface is now stable and has been in
production use for some time, there are few remaining TODO items (see
the bottom of Task.pm).
AUTHOR
Doug Hoyte, "<doug@hcsw.org>"
COPYRIGHT & LICENSE
Copyright 2012-2017 Doug Hoyte.
This module is licensed under the same terms as perl itself.
( run in 0.683 second using v1.01-cache-2.11-cpan-140bd7fdf52 )