AnyEvent-Fork-RPC
view release on metacpan or search on metacpan
eval {
...
};
if ($@) {
AnyEvent::RPC::event (throw => "$@");
AnyEvent::RPC::flush ();
exit;
}
...
}
PROCESS EXIT
If and when the child process exits depends on the backend and
configuration. Apart from explicit exits (e.g. by calling "exit") or
runtime conditions (uncaught exceptions, signals etc.), the backends
exit under these conditions:
Synchronous Backend
The synchronous backend is very simple: when the process waits for
another request to arrive and the writing side (usually in the
parent) is closed, it will exit normally, i.e. as if your main
program reached the end of the file.
That means that if your parent process exits, the RPC process will
usually exit as well, either because it is idle anyway, or because
it executes a request. In the latter case, you will likely get an
error when the RPc process tries to send the results to the parent
(because agruably, you shouldn't exit your parent while there are
still outstanding requests).
The process is usually quiescent when it happens, so it should
rarely be a problem, and "END" handlers can be used to clean up.
Asynchronous Backend
For the asynchronous backend, things are more complicated: Whenever
it listens for another request by the parent, it might detect that
the socket was closed (e.g. because the parent exited). It will sotp
listening for new requests and instead try to write out any
remaining data (if any) or simply check whether the socket can be
written to. After this, the RPC process is effectively done - no new
requests are incoming, no outstanding request data can be written
back.
Since chances are high that there are event watchers that the RPC
server knows nothing about (why else would one use the async backend
if not for the ability to register watchers?), the event loop would
often happily continue.
This is why the asynchronous backend explicitly calls "CORE::exit"
when it is done (under other circumstances, such as when there is an
I/O error and there is outstanding data to write, it will log a
fatal message via AnyEvent::Log, also causing the program to exit).
You can override this by specifying a function name to call via the
"done" parameter instead.
ADVANCED TOPICS
Choosing a backend
So how do you decide which backend to use? Well, that's your problem to
solve, but here are some thoughts on the matter:
Synchronous
The synchronous backend does not rely on any external modules (well,
except common::sense, which works around a bug in how perl's warning
system works). This keeps the process very small, for example, on my
system, an empty perl interpreter uses 1492kB RSS, which becomes
2020kB after "use warnings; use strict" (for people who grew up with
C64s around them this is probably shocking every single time they
see it). The worker process in the first example in this document
uses 1792kB.
Since the calls are done synchronously, slow jobs will keep newer
jobs from executing.
The synchronous backend also has no overhead due to running an event
loop - reading requests is therefore very efficient, while writing
responses is less so, as every response results in a write syscall.
If the parent process is busy and a bit slow reading responses, the
child waits instead of processing further requests. This also limits
the amount of memory needed for buffering, as never more than one
response has to be buffered.
The API in the child is simple - you just have to define a function
that does something and returns something.
It's hard to use modules or code that relies on an event loop, as
the child cannot execute anything while it waits for more input.
Asynchronous
The asynchronous backend relies on AnyEvent, which tries to be
small, but still comes at a price: On my system, the worker from
example 1a uses 3420kB RSS (for AnyEvent, which loads EV, which
needs XSLoader which in turn loads a lot of other modules such as
warnings, strict, vars, Exporter...).
It batches requests and responses reasonably efficiently, doing only
as few reads and writes as needed, but needs to poll for events via
the event loop.
Responses are queued when the parent process is busy. This means the
child can continue to execute any queued requests. It also means
that a child might queue a lot of responses in memory when it
generates them and the parent process is slow accepting them.
The API is not a straightforward RPC pattern - you have to call a
"done" callback to pass return values and signal completion. Also,
more importantly, the API starts jobs as fast as possible - when
1000 jobs are queued and the jobs are slow, they will all run
concurrently. The child must implement some queueing/limiting
mechanism if this causes problems. Alternatively, the parent could
limit the amount of rpc calls that are outstanding.
Blocking use of condvars is not supported (in the main thread,
outside of e.g. Coro threads).
Using event-based modules such as IO::AIO, Gtk2, Tk and so on is
easy.
( run in 1.059 second using v1.01-cache-2.11-cpan-df04353d9ac )