--- AnyEvent-Fork-RPC/RPC.pm 2013/08/25 22:21:15 1.30 +++ AnyEvent-Fork-RPC/RPC.pm 2016/05/12 16:43:08 1.37 @@ -177,7 +177,7 @@ =head2 Example 2: Asynchronous Backend This example implements multiple count-downs in the child, using -L timers. While this is a bit silly (one could use timers in te +L timers. While this is a bit silly (one could use timers in the parent just as well), it illustrates the ability to use AnyEvent in the child and the fact that responses can arrive in a different order then the requests. @@ -393,7 +393,7 @@ use AnyEvent; -our $VERSION = 1.1; +our $VERSION = 1.21; =item my $rpc = AnyEvent::Fork::RPC::run $fork, $function, [key => value...] @@ -456,6 +456,20 @@ used to load any modules that provide the serialiser or function. It can not, however, create events. +=item done => $function (default C) + +The function to call when the asynchronous backend detects an end of file +condition when reading from the communications socket I there are no +outstanding requests. It's ignored by the synchronous backend. + +By overriding this you can prolong the life of a RPC process after e.g. +the parent has exited by running the event loop in the provided function +(or simply calling it, for example, when your child process uses L you +could provide L as C function). + +Of course, in that case you are responsible for exiting at the appropriate +time and not returning from + =item async => $boolean (default: 0) The default server used in the child does all I/O blockingly, and only @@ -482,8 +496,10 @@ transferred between the processes. For this, they have to be frozen and thawed in both parent and child processes. -By default, only octet strings can be passed between the processes, which -is reasonably fast and efficient and requires no extra modules. +By default, only octet strings can be passed between the processes, +which is reasonably fast and efficient and requires no extra modules +(the C distribution does not provide these extra +serialiser modules). For more complicated use cases, you can provide your own freeze and thaw functions, by specifying a string with perl source code. It's supposed to @@ -500,11 +516,15 @@ =over 4 -=item octet strings - C<$AnyEvent::Fork::RPC::STRING_SERIALISER> +=item C<$AnyEvent::Fork::RPC::STRING_SERIALISER> - octet strings only -This serialiser concatenates length-prefixes octet strings, and is the -default. That means you can only pass (and return) strings containing -character codes 0-255. +This serialiser (currently the default) concatenates length-prefixes octet +strings, and is the default. That means you can only pass (and return) +strings containing character codes 0-255. + +The main advantages of this serialiser are the high speed and that it +doesn't need another module. The main disadvantage is that you are very +limited in what you can pass - only octet strings. Implementation: @@ -513,7 +533,30 @@ sub { unpack "(w/a*)*", shift } ) -=item json - C<$AnyEvent::Fork::RPC::JSON_SERIALISER> +=item C<$AnyEvent::Fork::RPC::CBOR_XS_SERIALISER> - uses L + +This serialiser creates CBOR::XS arrays - you have to make sure the +L module is installed for this serialiser to work. It can be +beneficial for sharing when you preload the L module in a template +process. + +L is about as fast as the octet string serialiser, but supports +complex data structures (similar to JSON) and is faster than any of the +other serialisers. If you have the L module available, it's the +best choice. + +The encoder enables C (so this serialisation method can +encode cyclic and self-referencing data structures). + +Implementation: + + use CBOR::XS (); + ( + sub { CBOR::XS::encode_cbor_sharing \@_ }, + sub { @{ CBOR::XS::decode_cbor shift } } + ) + +=item C<$AnyEvent::Fork::RPC::JSON_SERIALISER> - uses L or L This serialiser creates JSON arrays - you have to make sure the L module is installed for this serialiser to work. It can be beneficial for @@ -531,7 +574,7 @@ sub { @{ JSON::decode_json shift } } ) -=item storable - C<$AnyEvent::Fork::RPC::STORABLE_SERIALISER> +=item C<$AnyEvent::Fork::RPC::STORABLE_SERIALISER> - L This serialiser uses L, which means it has high chance of serialising just about anything you throw at it, at the cost of having @@ -546,7 +589,7 @@ sub { @{ Storable::thaw shift } } ) -=item portable storable - C<$AnyEvent::Fork::RPC::NSTORABLE_SERIALISER> +=item C<$AnyEvent::Fork::RPC::NSTORABLE_SERIALISER> - portable Storable This serialiser also uses L, but uses it's "network" format to serialise data, which makes it possible to talk to different @@ -571,7 +614,8 @@ =cut our $STRING_SERIALISER = '(sub { pack "(w/a*)*", @_ }, sub { unpack "(w/a*)*", shift })'; -our $JSON_SERIALISER = 'use JSON (); (sub { JSON::encode_json \@_ }, sub { @{ JSON::decode_json shift } })'; +our $CBOR_XS_SERIALISER = 'use CBOR::XS (); (sub { CBOR::XS::encode_cbor_sharing \@_ }, sub { @{ CBOR::XS::decode_cbor shift } })'; +our $JSON_SERIALISER = 'use JSON (); (sub { JSON::encode_json \@_ }, sub { @{ JSON::decode_json shift } })'; our $STORABLE_SERIALISER = 'use Storable (); (sub { Storable::freeze \@_ }, sub { @{ Storable::thaw shift } })'; our $NSTORABLE_SERIALISER = 'use Storable (); (sub { Storable::nfreeze \@_ }, sub { @{ Storable::thaw shift } })'; @@ -617,7 +661,7 @@ my $module = "AnyEvent::Fork::RPC::" . ($arg{async} ? "Async" : "Sync"); $self->require ($module) - ->send_arg ($function, $arg{init}, $serialiser) + ->send_arg ($function, $arg{init}, $serialiser, $arg{done} || "$module\::do_exit") ->run ("$module\::run", sub { $fh = shift; @@ -743,6 +787,56 @@ =back +=head2 PROCESS EXIT + +If and when the child process exits depends on the backend and +configuration. Apart from explicit exits (e.g. by calling C) or +runtime conditions (uncaught exceptions, signals etc.), the backends exit +under these conditions: + +=over 4 + +=item 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 C handlers can be used to clean up. + +=item 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 C 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 +L, also causing the program to exit). + +You can override this by specifying a function name to call via the C +parameter instead. + +=back + =head1 ADVANCED TOPICS =head2 Choosing a backend @@ -805,7 +899,8 @@ problems. Alternatively, the parent could limit the amount of rpc calls that are outstanding. -Blocking use of condvars is not supported. +Blocking use of condvars is not supported (in the main thread, outside of +e.g. L threads). Using event-based modules such as L, L, L and so on is easy. @@ -881,7 +976,7 @@ =head1 EXCEPTIONS There are no provisions whatsoever for catching exceptions at this time - -in the child, exeptions might kill the process, causing calls to be lost +in the child, exceptions might kill the process, causing calls to be lost and the parent encountering a fatal error. In the parent, exceptions in the result callback will not be caught and cause undefined behaviour.