package Apache::Backend::POE; use strict; BEGIN { eval { require Apache } } use Apache::Backend::POE::Connection; use Carp qw(carp); our $VERSION = '0.02'; # a lot of code used from Apache::DBI... # 1: report about new connect # 2: full debug output $Apache::Backend::POE::DEBUG = 0; my %Connected; # cache for objects my @ChildConnect; # connections to be established when a new httpd child is created my %Rollback; # keeps track of pushed PerlCleanupHandler which can do a rollback after the request has finished my %PingTimeOut; # stores the timeout values per data_source, a negative value de-activates ping, default = 0 my %LastPingTime; # keeps track of last ping per data_source my $Idx; # key of %Connected and %Rollback. # supposed to be called in a startup script. # stores the data_source of all connections, which are supposed to be created upon # server startup, and creates a PerlChildInitHandler, which initiates the connections. sub connect_on_init { # provide a handler which creates all connections during server startup # TODO - Should check for mod_perl 2 and do the right thing there carp "Apache.pm was not loaded\n" and return unless $INC{'Apache.pm'}; # push the init handler ONCE if(!@ChildConnect and Apache->can('push_handlers')) { Apache->push_handlers(PerlChildInitHandler => \&childinit); } # store connections push @ChildConnect, [@_]; } # supposed to be called in a startup script. # stores the timeout per data_source for the ping function. # use a DSN without attribute settings specified within ! sub setPingTimeOut { my $class = shift; my $timeout = shift || 0; my $alias = shift || 'backend'; # sanity check if ($timeout =~ /\-*\d+/) { $PingTimeOut{"poe:$alias"} = $timeout; } } # the connect method called from POE::connect sub connect { my $poe = shift; my $prefix = "$$ Apache::Backend::POE "; print STDERR "$prefix ref: ".ref($poe)." in connect\n" if $Apache::Backend::POE::DEBUG > 1; my @args = map { defined $_ ? $_ : "" } @_; $Idx = join(',',@args); my %opts = @args; # defaults $opts{alias} ||= 'backend'; my $dsn = "poe:$opts{alias}"; print STDERR "$prefix dsn: $dsn args:".join(',',@args)."\n" if $Apache::Backend::POE::DEBUG; # don't cache connections created during server initialization; they # won't be useful after ChildInit, since multiple processes trying to # work over the same connection simultaneously will receive # unpredictable results. if ($Apache::ServerStarting and $Apache::ServerStarting == 1) { print STDERR "$prefix skipping connection during server startup, read the docs !!\n" if $Apache::Backend::POE::DEBUG > 1; return Apache::Backend::POE::Connection->new(@args)->connect($poe); } # I plan to have transaction support # this PerlCleanupHandler is supposed to initiate a rollback after the script has finished if AutoCommit is off. # my $needCleanup = ($opts{AutoCommit}) ? 1 : 0; # TODO - Fix mod_perl 2.0 here # if(!$Rollback{$Idx} and !$needCleanup and Apache->can('push_handlers')) { # print STDERR "$prefix push PerlCleanupHandler\n" if $Apache::Backend::POE::DEBUG > 1; # Apache->push_handlers("PerlCleanupHandler", \&cleanup); # # make sure, that the rollback is called only once for every # # request, even if the script calls connect more than once # $Rollback{$Idx} = 1; # } # do we need to ping the connection ? $PingTimeOut{$dsn} = 0 unless $PingTimeOut{$dsn}; $LastPingTime{$dsn} = 0 unless $LastPingTime{$dsn}; my $now = time; my $needping = (($PingTimeOut{$dsn} == 0 or $PingTimeOut{$dsn} > 0) and (($now - $LastPingTime{$dsn}) >= $PingTimeOut{$dsn}) ) ? 1 : 0; # print STDERR "$prefix need ping: ".($needping == 1 ? "yes" : "no")." \n" if $Apache::Backend::POE::DEBUG > 1; $LastPingTime{$dsn} = $now; # check first if there is already a object cached # if this is the case, possibly verify the object # using the ping-method. Use eval for checking the connection # handle in order to avoid problems (dying inside ping) when # handle is invalid. # require Data::Dumper; # print STDERR Data::Dumper->Dump([\%Connected]); #if ($Connected{$Idx} and (!$needping or eval{$Connected{$Idx}->ping})) { $needping = 1; PING: { if ($Connected{$Idx}) { if ($needping) { print STDERR "$prefix going to ping\n" if $Apache::Backend::POE::DEBUG > 1; my $rt = eval{ $Connected{$Idx}->ping }; print STDERR "$prefix ping rt: ----------- $rt\n" if $Apache::Backend::POE::DEBUG > 1; last PING unless ($rt == 1); if ($@) { print STDERR "$prefix ping error: $@\n" if $Apache::Backend::POE::DEBUG; last PING; } } print STDERR "$prefix using cached connection to '$Idx'\n" if $Apache::Backend::POE::DEBUG; return (bless $Connected{$Idx}, 'Apache::Backend::POE::Conn'); } } # either there is no object cached or it is not valid, # so get a new object and store it in the cache delete $Connected{$Idx}; $Connected{$Idx} = Apache::Backend::POE::Connection->new(@args)->connect($poe); return undef if !$Connected{$Idx}; # return the new object print STDERR "$prefix new connect to '$Idx'\n" if $Apache::Backend::POE::DEBUG; return (bless $Connected{$Idx}, 'Apache::Backend::POE::Conn'); } # The PerlChildInitHandler creates all connections during server startup. # Note: this handler runs in every child server, but not in the main server. sub childinit { my $prefix = "$$ Apache::Backend::POE "; print STDERR "$prefix PerlChildInitHandler\n" if $Apache::Backend::POE::DEBUG > 1; if (@ChildConnect) { foreach my $aref (@ChildConnect) { my $class = shift @$aref; my $conn = Apache::Backend::POE::Connection->new(@$aref); my $idx = join(',',(map { defined $_ ? $_ : "" } @$aref)); delete $Connected{$idx}; $Connected{$idx} = $conn->connect($class); my %opts = @$aref; # defaults $opts{alias} ||= 'backend'; my $dsn = "poe:$opts{alias}"; print STDERR "$prefix PerlChildInitHandler created new connection for $dsn\n" if $Apache::Backend::POE::DEBUG > 1; $LastPingTime{$dsn} = time; } } 1; } # The PerlCleanupHandler is supposed to initiate a rollback after the script has finished if AutoCommit is off. # Note: the PerlCleanupHandler runs after the response has been sent to the client # TODO cleanup rollback code sub cleanup { my $prefix = "$$ Apache::Backend::POE "; print STDERR "$prefix PerlCleanupHandler\n" if $Apache::Backend::POE::DEBUG > 1; my $dbh = $Connected{$Idx}; #if ($Rollback{$Idx} and $dbh and $dbh->{Active} and !$dbh->{AutoCommit} and eval {$dbh->rollback}) { # print STDERR "$prefix PerlCleanupHandler rollback for $Idx\n" if $Apache::Backend::POE::DEBUG > 1; #} delete $Rollback{$Idx}; 1; } # This function can be called from other handlers to perform tasks on all cached objects. sub all_handlers { return \%Connected; } # overload disconnect # I have plans for a non mod_perl backend module, so this disconnect { package Apache::Backend::POE::Conn; no strict; @ISA=qw(Apache::Backend::POE::Connection); use strict; sub disconnect { my $prefix = "$$ Apache::Backend::POE "; print STDERR "$prefix disconnect (overloaded)\n" if $Apache::Backend::POE::DEBUG > 1; 1; }; } # prepare menu item for Apache::Status Apache::Status->menu_item( 'POE' => 'Backend POE connections', sub { my($r, $q) = @_; my(@s) = qw(
| Datasource | Username |
| ', join(' | ', (split($;, $_))[0,1]), " |