X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=scpubgit%2FObject-Remote.git;a=blobdiff_plain;f=lib%2FObject%2FRemote.pm;h=5cc2524ccef4b63f85d698cffc57e115f8de1885;hp=7bb33c673cd1aba3bebc5728c8b028ab57e442a1;hb=b7ebf243350cdf8892c004616f58a8f1e6e94513;hpb=ad4f54b29c5f84aa010b456303b24647c0e28ae7 diff --git a/lib/Object/Remote.pm b/lib/Object/Remote.pm index 7bb33c6..5cc2524 100644 --- a/lib/Object/Remote.pm +++ b/lib/Object/Remote.pm @@ -1,64 +1,260 @@ package Object::Remote; use Object::Remote::MiniLoop; -use Object::Remote::Proxy; -use Scalar::Util qw(weaken); -use Moo; +use Object::Remote::Handle; +use Object::Remote::Logging qw( :log ); +use Module::Runtime qw(use_module); -has connection => (is => 'ro', required => 1); +our $VERSION = '0.003002'; # 0.3.2 -has id => (is => 'rwp'); - -has disarmed_free => (is => 'rwp'); +sub new::on { + my ($class, $on, @args) = @_; + my $conn = __PACKAGE__->connect($on); + log_trace { sprintf("constructing instance of $class on connection for child pid of %i", $conn->child_pid) }; + return $conn->remote_object(class => $class, args => \@args); +} -sub disarm_free { $_[0]->_set_disarmed_free(1); $_[0] } +sub can::on { + my ($class, $on, $name) = @_; + my $conn = __PACKAGE__->connect($on); + log_trace { "Invoking remote \$class->can('$name')" }; + return $conn->remote_sub(join('::', $class, $name)); +} -sub proxy { - bless({ remote => $_[0], method => 'call' }, 'Object::Remote::Proxy'); +sub new { + shift; + Object::Remote::Handle->new(@_)->proxy; } -sub BUILD { - my ($self, $args) = @_; - unless ($self->id) { - die "No id supplied and no class either" unless $args->{class}; - $self->_set_id( - $self->_await( - $self->connection->send( - class_call => $args->{class}, - $args->{constructor}||'new', @{$args->{args}||[]} - ) - )->{remote}->disarm_free->id - ); - } - $self->connection->register_remote($self); +sub connect { + my ($class, $to, @args) = @_; + use_module('Object::Remote::Connection')->maybe::start::new_from_spec($to, @args); } sub current_loop { our $Current_Loop ||= Object::Remote::MiniLoop->new } -sub call { - my ($self, $method, @args) = @_; - $self->_await($self->connection->send(call => $self->id, $method, @args)); -} +1; -sub call_discard { - my ($self, $method, @args) = @_; - $self->connection->send_discard(call => $self->id, $method, @args); -} +=head1 NAME -sub _await { - my ($self, $future) = @_; - my $loop = $self->current_loop; - $future->on_ready(sub { $loop->stop }); - $loop->run; - ($future->get)[0]; -} +Object::Remote - Call methods on objects in other processes or on other hosts -sub DEMOLISH { - my ($self, $gd) = @_; - return if $gd or $self->disarmed_free; - $self->connection->send_free($self->id); -} +=head1 SYNOPSIS -1; +Creating a connection: + + use Object::Remote; + + my $conn = Object::Remote->connect('myserver'); # invokes ssh + +Calling a subroutine: + + my $capture = IPC::System::Simple->can::on($conn, 'capture'); + + warn $capture->('uptime'); + +Using an object: + + my $eval = Eval::WithLexicals->new::on($conn); + + $eval->eval(q{my $x = `uptime`}); + + warn $eval->eval(q{$x}); + +Importantly: 'myserver' only requires perl 5.8+ - no non-core modules need to +be installed on the far side, Object::Remote takes care of it for you! + +=head1 DESCRIPTION + +Object::Remote allows you to create an object in another process - usually +one running on another machine you can connect to via ssh, although there +are other connection mechanisms available. + +The idea here is that in many cases one wants to be able to run a piece of +code on another machine, or perhaps many other machines - but without having +to install anything on the far side. + +=head1 COMPONENTS + +=head2 Object::Remote + +The "main" API, which provides the L method to create a connection +to a remote process/host, L to create an object on a connection, +and L to retrieve a subref over a connection. + +=head2 Object::Remote::Connection + +The object representing a connection, which provides the +L and +L methods that are used by +L and L to return proxies for objects and subroutines +on the far side. + +=head2 Object::Remote::Future + +Code for dealing with asynchronous operations, which provides the +L syntax for calling a possibly +asynchronous method without blocking, and +L and L +to block until an asynchronous call completes or fails. + +=head1 METHODS + +=head2 connect + + my $conn = Object::Remote->connect('-'); # fork()ed connection + + my $conn = Object::Remote->connect('myserver'); # connection over ssh + + my $conn = Object::Remote->connect('user@myserver'); # connection over ssh + + my $conn = Object::Remote->connect('root@'); # connection over sudo + +=head2 new::on + + my $eval = Eval::WithLexicals->new::on($conn); + + my $eval = Eval::WithLexicals->new::on('myserver'); # implicit connect + + my $obj = Some::Class->new::on($conn, %args); # with constructor arguments + +=head2 can::on + + my $hostname = Sys::Hostname->can::on($conn, 'hostname'); + + my $hostname = Sys::Hostname->can::on('myserver', 'hostname'); + +=head1 ENVIRONMENT + +=over 4 + +=item OBJECT_REMOTE_PERL_BIN + +When starting a new Perl interpreter the contents of this environment +variable will be used as the path to the executable. If the variable +is not set the path is 'perl' + +=item OBJECT_REMOTE_LOG_LEVEL + +Setting this environment variable will enable logging and send all log messages +at the specfied level or higher to STDERR. Valid level names are: trace debug +verbose info warn error fatal + +=item OBJECT_REMOTE_LOG_FORMAT + +The format of the logging output is configurable. By setting this environment variable +the format can be controlled via printf style position variables. See +L. + +=item OBJECT_REMOTE_LOG_FORWARDING + +Forward log events from remote connections to the local Perl interpreter. Set to 1 to enable +this feature which is disabled by default. See L. + +=item OBJECT_REMOTE_LOG_SELECTIONS + +Space seperated list of class names to display logs for if logging output is enabled. Default +value is "Object::Remote::Logging" which selects all logs generated by Object::Remote. +See L. + +=back + +=head1 KNOWN ISSUES + +=over 4 + +=item Large data structures + +Object::Remote communication is encapsalated with JSON and values passed to remote objects +will be serialized with it. When sending large data structures or data structures with a lot +of deep complexity (hashes in arrays in hashes in arrays) the processor time and memory requirements +for serialization and deserialization can be either painful or unworkable. During times of +serialization the local or remote nodes will be blocked potentially causing all remote +interpreters to block as well under worse case conditions. + +To help deal with this issue it is possible to configure resource ulimits for a Perl interpreter +that is executed by Object::Remote. See C +for details on the perl_command attribute. + +=item User can starve run loop of execution opportunities + +The Object::Remote run loop is responsible for performing I/O and managing timers in a cooperative +multitasing way but it can only do these tasks when the user has given control to Object::Remote. +There are times when Object::Remote must wait for the user to return control to the run loop and +during these times no I/O can be performed and no timers can be executed. + +As an end user of Object::Remote if you depend on connection timeouts, the watch dog or timely +results from remote objects then be sure to hand control back to Object::Remote as soon as you +can. + +=item Run loop favors certain filehandles/connections + +=item High levels of load can starve timers of execution opportunities + +These are issues that only become a problem at large scales. The end result of these two +issues is quite similiar: some remote objects may block while the local run loop is either busy +servicing a different connection or is not executing because control has not yet been returned to +it. For the same reasons timers may not get an opportunity to execute in a timely way. + +Internally Object::Remote uses timers managed by the run loop for control tasks. Under +high load the timers can be preempted by servicing I/O on the filehandles and execution +can be severely delayed. This can lead to connection watchdogs not being updated or connection +timeouts taking longer than configured. + +=item Deadlocks + +Deadlocks can happen quite easily because of flaws in programs that use Object::Remote or +Object::Remote itself so the C is available. When used the run +loop will periodically update the watch dog object on the remote Perl interpreter. If the +watch dog goes longer than the configured interval with out being updated then it will +terminate the Perl process. The watch dog will terminate the process even if a deadlock +condition has occured. + +=item Log forwarding at scale can starve timers of execution opportunities + +Currently log forwarding can be problematic at large scales. When there is a large +amount of log events the load produced by log forwarding can be high enough that it starves +the timers and the remote object watch dogs (if in use) don't get updated in timely way +causing them to erroneously terminate the Perl process. If the watch dog is not in use +then connection timeouts can be delayed but will execute when load settles down enough. + +Because of the load related issues Object::Remote disables log forwarding by default. +See C for information on log forwarding. + +=back + +=head1 SUPPORT + +IRC: #web-simple on irc.perl.org + +=head1 AUTHOR + +mst - Matt S. Trout (cpan:MSTROUT) + +=head1 CONTRIBUTORS + +phaylon - Robert Sedlacek (cpan:PHAYLON) + +triddle - Tyler Riddle (cpan:TRIDDLE) + +=head1 SPONSORS + +Parts of this code were paid for by + + Socialflow L + + Shadowcat Systems L + +=head1 COPYRIGHT + +Copyright (c) 2012 the Object::Remote L, L and +L as listed above. + +=head1 LICENSE + +This library is free software and may be distributed under the same terms +as perl itself. + +=cut