File Coverage

blib/lib/Rose/DB/Cache.pm
Criterion Covered Total %
statement 35 96 36.4
branch 5 58 8.6
condition 3 9 33.3
subroutine 12 25 48.0
pod 11 11 100.0
total 66 199 33.1


line stmt bran cond sub pod time code
1             package Rose::DB::Cache;
2              
3 16     16   128 use strict;
  16         39  
  16         567  
4              
5 16     16   126 use base 'Rose::Object';
  16         33  
  16         7874  
6              
7 16     16   3357 use Scalar::Util qw(refaddr);
  16         35  
  16         956  
8 16     16   7394 use Rose::DB::Cache::Entry;
  16         46  
  16         910  
9              
10             our $VERSION = '0.755';
11              
12             our $Debug = 0;
13              
14             use Rose::Class::MakeMethods::Generic
15             (
16 16         133 inheritable_scalar =>
17             [
18             'entry_class',
19             '_default_use_cache_during_apache_startup',
20             ],
21 16     16   10325 );
  16         103764  
22              
23             __PACKAGE__->entry_class('Rose::DB::Cache::Entry');
24             __PACKAGE__->default_use_cache_during_apache_startup(0);
25              
26             our($MP2_Is_Child, $Apache_Has_Started);
27              
28             sub default_use_cache_during_apache_startup
29             {
30 16     16 1 33 my($class) = shift;
31 16 50       112 return $class->_default_use_cache_during_apache_startup($_[0] ? 1 : 0) if(@_);
    50          
32 0           return $class->_default_use_cache_during_apache_startup;
33             }
34              
35             sub use_cache_during_apache_startup
36             {
37 0     0 1   my($self) = shift;
38              
39 0 0         return $self->{'use_cache_during_apache_startup'} = $_[0] ? 1 : 0 if(@_);
    0          
40              
41 0 0         if(defined $self->{'use_cache_during_apache_startup'})
42             {
43 0           return $self->{'use_cache_during_apache_startup'};
44             }
45             else
46             {
47 0           return $self->{'use_cache_during_apache_startup'} =
48             ref($self)->default_use_cache_during_apache_startup;
49             }
50             }
51              
52             sub prepare_for_apache_fork
53             {
54 0     0 1   my($self) = shift;
55              
56 0           foreach my $entry ($self->db_cache_entries)
57             {
58 0 0         if($entry->created_during_apache_startup)
59             {
60 0           my $db = $entry->db;
61 0 0         $Debug && warn "$$ Disconnecting and undef-ing ", $db->dbh, " contained in $db";
62 0           $db->dbh->disconnect;
63 0           $db->dbh(undef);
64 0           $db = undef;
65 0 0         $Debug && warn "$$ Deleting cache entry for $db";
66 0           delete $self->{'cache'}{$entry->key};
67             }
68             }
69             }
70              
71             sub build_cache_key
72             {
73 0     0 1   my($class, %args) = @_;
74 0           return join("\0", $args{'domain'}, $args{'type'});
75             }
76              
77             QUIET:
78             {
79 16     16   9413 no warnings 'uninitialized';
  16         35  
  16         1052  
80 16 50 33 16   108 use constant MOD_PERL_1 => ($ENV{'MOD_PERL'} && !$ENV{'MOD_PERL_API_VERSION'}) ? 1 : 0;
  16         54  
  16         1605  
81 16 50 33 16   335 use constant MOD_PERL_2 => ($ENV{'MOD_PERL'} && $ENV{'MOD_PERL_API_VERSION'} == 2) ? 1 : 0;
  16         35  
  16         3324  
82 16 50 33 16   120 use constant APACHE_DBI => ($INC{'Apache/DBI.pm'} || $Apache::DBI::VERSION) ? 1 : 0;
  16         2013  
  16         1203  
83 16     16   115 use constant APACHE_DBI_MP2 => (APACHE_DBI && MOD_PERL_2) ? 1 : 0;
  16         202  
  16         961  
84 16     16   99 use constant APACHE_DBI_MP1 => (APACHE_DBI && MOD_PERL_1) ? 1 : 0;
  16         31  
  16         22273  
85             }
86              
87             sub db_cache_entries
88             {
89 0     0 1   my($self) = shift;
90 0 0         return wantarray ? values %{$self->{'cache'} || {}} :
91 0 0         [ values %{$self->{'cache'} || {}} ];
  0 0          
92             }
93              
94             sub db_cache_keys
95             {
96 0     0 1   my($self) = shift;
97 0 0         return wantarray ? keys %{$self->{'cache'} || {}} :
98 0 0         [ keys %{$self->{'cache'} || {}} ];
  0 0          
99             }
100              
101             sub get_db
102             {
103 0     0 1   my($self) = shift;
104              
105 0           my $key = $self->build_cache_key(@_);
106              
107 0 0         if(my $entry = $self->{'cache'}{$key})
108             {
109 0 0         if(my $db = $entry->db)
110             {
111 0           $self->prepare_db($db, $entry);
112 0           return $db;
113             }
114             }
115              
116 0           return undef;
117             }
118              
119             sub set_db
120             {
121 0     0 1   my($self, $db) = @_;
122              
123 0           my $key =
124             $self->build_cache_key(domain => $db->domain,
125             type => $db->type,
126             db => $db);
127              
128 0           my $entry = ref($self)->entry_class->new(db => $db, key => $key);
129              
130             # Don't cache anything during apache startup if use_cache_during_apache_startup
131             # is false. Weird conditional structure is meant to encourage code elimination
132             # thanks to the lone constants in the if/elsif conditions.
133 0           if(MOD_PERL_1)
134             {
135             if($Apache::Server::Starting)
136             {
137             if($self->use_cache_during_apache_startup)
138             {
139             $entry->created_during_apache_startup(1);
140             $entry->prepared(0);
141             }
142             else
143             {
144             $Debug && warn "Refusing to cache $db during apache server start-up ",
145             "because use_cache_during_apache_startup is false";
146              
147             return $db;
148             }
149             }
150             }
151              
152 0           if(MOD_PERL_2)
153             {
154             if(!$MP2_Is_Child)
155             {
156             if($self->use_cache_during_apache_startup)
157             {
158             $entry->created_during_apache_startup(1);
159             $entry->prepared(0);
160             }
161             else
162             {
163             $Debug && warn "Refusing to cache $db in pre-fork apache process ",
164             "because use_cache_during_apache_startup is false";
165             return $db;
166             }
167             }
168             }
169              
170 0           $self->{'cache'}{$key} = $entry;
171              
172 0           return $db;
173             }
174              
175 0     0 1   sub clear { shift->{'cache'} = {} }
176              
177             if(MOD_PERL_2)
178             {
179             require Apache2::ServerUtil;
180             require Apache2::RequestUtil;
181             require Apache2::Const;
182             Apache2::Const->import(-compile => qw(OK));
183              
184             $MP2_Is_Child = 0;
185              
186             if(__PACKAGE__->apache_has_started)
187             {
188             $Debug && warn "$$ is already MP2 child (not registering child init handler)\n";
189             $MP2_Is_Child = 1;
190             }
191             elsif(!$ENV{'ROSE_DB_NO_CHILD_INIT_HANDLER'})
192             {
193             Apache2::ServerUtil->server->push_handlers(
194             PerlChildInitHandler => \&__mod_perl_2_rose_db_child_init_handler);
195             }
196             }
197              
198             # http://mail-archives.apache.org/mod_mbox/perl-dev/200504.mbox/%3C4256B5FF.5060401@stason.org%3E
199             # To work around this issue, we'll use a named subroutine.
200             sub __mod_perl_2_rose_db_child_init_handler
201             {
202 0 0   0     $Debug && warn "$$ is MP2 child\n";
203 0           $MP2_Is_Child = 1;
204 0           return Apache2::Const::OK();
205             }
206              
207             sub apache_has_started
208             {
209 0     0 1   my($class) = shift;
210              
211 0 0         if(@_)
212             {
213 0 0         return $Apache_Has_Started = $_[0] ? 1 : 0;
214             }
215              
216 0 0         return $Apache_Has_Started if(defined $Apache_Has_Started);
217              
218 0           if(MOD_PERL_2)
219             {
220             return $Apache_Has_Started = $MP2_Is_Child;
221             }
222              
223 0           if(MOD_PERL_1)
224             {
225             return $Apache_Has_Started = $Apache::Server::Starting;
226             }
227              
228 0           return undef;
229             }
230              
231             sub prepare_db
232             {
233 0     0 1   my($self, $db, $entry) = @_;
234              
235 0           if(MOD_PERL_1)
236             {
237             if($Apache::Server::Starting)
238             {
239             $entry->created_during_apache_startup(1);
240             $entry->prepared(0);
241             }
242             elsif(!$entry->is_prepared)
243             {
244             if($entry->created_during_apache_startup)
245             {
246             if($db->has_dbh)
247             {
248             $Debug && warn "$$ Disconnecting and undef-ing dbh ", $db->dbh,
249             " created during apache startup from $db\n";
250              
251             my $error;
252              
253             TRY:
254             {
255             local $@;
256             eval { $db->dbh->disconnect }; # will probably fail!
257             $error = $@;
258             }
259              
260             warn "$$ Could not disconnect dbh created during apache startup: ",
261             $db->dbh, " - $error" if($error);
262              
263             $db->dbh(undef);
264             }
265              
266             $entry->created_during_apache_startup(0);
267             }
268              
269             Apache->push_handlers(PerlCleanupHandler => sub
270             {
271 0 0   0     $Debug && warn "$$ Clear dbh and prepared flag for $db, $entry\n";
272 0 0         $db->dbh(undef) if($db);
273 0 0         $entry->prepared(0) if($entry);
274             });
275              
276             $entry->prepared(1);
277             }
278             }
279              
280             # Not a chained elsif to help Perl eliminate the unused code (maybe unnecessary?)
281 0           if(MOD_PERL_2)
282             {
283             if(!$MP2_Is_Child)
284             {
285             $entry->created_during_apache_startup(1);
286             $entry->prepared(0);
287             }
288             elsif(!$entry->is_prepared)
289             {
290             if($entry->created_during_apache_startup)
291             {
292             if($db->has_dbh)
293             {
294             $Debug && warn "$$ Disconnecting and undef-ing dbh ", $db->dbh,
295             " created during apache startup from $db\n";
296              
297             my $error;
298              
299             TRY:
300             {
301             local $@;
302             eval { $db->dbh->disconnect }; # will probably fail!
303             $error = $@;
304             }
305              
306             warn "$$ Could not disconnect dbh created during apache startup: ",
307             $db->dbh, " - $error" if($error);
308              
309             $db->dbh(undef);
310             }
311              
312             $entry->created_during_apache_startup(0);
313             }
314              
315             my($r, $error);
316              
317             TRY:
318             {
319             local $@;
320             eval { $r = Apache2::RequestUtil->request };
321             $error = $@;
322             }
323              
324             if($error)
325             {
326             $Debug && warn "Couldn't get apache request (restart count is ",
327             Apache2::ServerUtil::restart_count(), ") - $error\n";
328             $entry->created_during_apache_startup(1); # tag for cleanup
329             $entry->prepared(0);
330              
331             return;
332             }
333             else
334             {
335             $r->push_handlers(PerlCleanupHandler => sub
336             {
337 0 0   0     $Debug && warn "$$ Clear dbh and prepared flag for $db, $entry\n";
338 0 0         $db->dbh(undef) if($db);
339 0 0         $entry->prepared(0) if($entry);
340 0           return Apache2::Const::OK();
341             });
342             }
343              
344             $entry->prepared(1);
345             }
346             }
347             }
348              
349             1;
350              
351             __END__
352              
353             =head1 NAME
354              
355             Rose::DB::Cache - A mod_perl-aware cache for Rose::DB objects.
356              
357             =head1 SYNOPSIS
358              
359             # Usage
360             package My::DB;
361              
362             use base 'Rose::DB';
363             ...
364              
365             $cache = My::DB->db_cache;
366              
367             $db = $cache->get_db(...);
368              
369             $cache->set_db($db);
370              
371             $cache->clear;
372              
373              
374             # Subclassing
375             package My::DB::Cache;
376              
377             use Rose::DB::Cache;
378             our @ISA = qw(Rose::DB::Cache);
379              
380             # Override methods as desired
381             sub get_db { ... }
382             sub set_db { ... }
383             sub prepare_db { ... }
384             sub build_cache_key { ... }
385             sub clear { ... }
386             ...
387              
388             =head1 DESCRIPTION
389              
390             L<Rose::DB::Cache> provides both an API and a default implementation of a caching system for L<Rose::DB> objects. Each L<Rose::DB>-derived class L<references|Rose::DB/db_cache> a L<Rose::DB::Cache>-derived object to which it delegates cache-related activities. See the L<new_or_cached|Rose::DB/new_or_cached> method for an example.
391              
392             The default implementation caches and returns L<Rose::DB> objects using the combination of their L<type|Rose::DB/type> and L<domain|Rose::DB/domain> as the cache key. There is no cache expiration or other cache cleaning.
393              
394             The only sophistication in the default implementation is that it is L<mod_perl>- and L<Apache::DBI>-aware. When running under mod_perl, with or without L<Apache::DBI>, the L<dbh|Rose::DB/dbh> attribute of each cached L<Rose::DB> object is set to C<undef> at the end of each request. Additionally, any db connections made in a pre-fork parent apache process are not cached.
395              
396             When running under L<Apache::DBI>, the behavior described above will ensure that L<Apache::DBI>'s "ping" and rollback features work as expected, keeping the L<DBI> database handles L<contained|Rose::DB/dbh> within each L<Rose::DB> object connected and alive.
397              
398             When running under mod_perl I<without> L<Apache::DBI>, the behavior described above will use a single L<DBI> database connection per cached L<Rose::DB> object per request, but will discard these connections at the end of each request.
399              
400             Both mod_perl 1.x and 2.x are supported. Under mod_perl 2.x, you should load L<Rose::DB> on server startup (e.g., in your C<startup.pl> file). If this is not possible, then you must explicitly tell L<Rose::DB::Cache> that apache has started up already by setting L<apache_has_started|/apache_has_started> to a true value.
401              
402             Subclasses can override any and all methods described below in order to implement their own caching strategy.
403              
404             =head1 CLASS METHODS
405              
406             =over 4
407              
408             =item B<apache_has_started [BOOL]>
409              
410             Get or set a boolean value indicating whether or not apache has completed its startup process. If this value is not set explicitly, a best guess as to the answer will be returned.
411              
412             =item B<build_cache_key PARAMS>
413              
414             Given the name/value pairs PARAMS, return a string representing the corresponding cache key. Calls to this method from within L<Rose::DB::Cache> will include at least C<type> and C<domain> parameters, but you may pass any parameters if you override all methods that call this method in your subclass.
415              
416             =item B<default_use_cache_during_apache_startup [BOOL]>
417              
418             Get or set a boolean value that determines the default value of the L<use_cache_during_apache_startup|/use_cache_during_apache_startup> object attribute. The default value is false. See the L<use_cache_during_apache_startup|/use_cache_during_apache_startup> documentation for more information.
419              
420             =item B<entry_class [CLASS]>
421              
422             Get or set the name of the L<Rose::DB::Cache::Entry>-derived class used to store cached L<Rose::DB> objects on behalf of this class. The default value is L<Rose::DB::Cache::Entry>.
423              
424             =back
425              
426             =head1 CONSTRUCTORS
427              
428             =over 4
429              
430             =item B<new PARAMS>
431              
432             Constructs a new L<Rose::DB::Cache> object based on PARAMS, where PARAMS are
433             name/value pairs. Any object method is a valid parameter name.
434              
435             =back
436              
437             =head1 OBJECT METHODS
438              
439             =over 4
440              
441             =item B<clear>
442              
443             Clear the cache entirely.
444              
445             =item B<db_cache_entries>
446              
447             Returns a list (in list context) or reference to an array (in scalar context) of L<cache entries|Rose::DB::Cache::Entry> for each cached db object.
448              
449             =item B<db_cache_keys>
450              
451             Returns a list (in list context) or reference to an array (in scalar context) of L<keys|Rose::DB::Cache::Entry/key> for each L <cache entries|Rose::DB::Cache::Entry>.
452              
453             =item B<get_db [PARAMS]>
454              
455             Return the cached L<Rose::DB>-derived object corresponding to the name/value pairs passed in PARAMS. PARAMS are passed to the L<build_cache_key|/build_cache_key> method, and the key returned is used to look up the cached object.
456              
457             If a cached object is found, the L<prepare_db|/prepare_db> method is called, passing the cached db object and its corresponding L<Rose::DB::Cache::Entry> object as arguments. The cached db object is then returned.
458              
459             If no such object exists in the cache, undef is returned.
460              
461             =item B<prepare_for_apache_fork>
462              
463             Prepares the cache for the initial fork of the apache parent process by L<disconnect()ing|DBI/disconnect> all database handles and deleting all cache entries that were L<created during apache startup|Rose::DB::Cache::Entry/created_during_apache_startup>. This call is only necessary if running under L<mod_perl> I<and> L<use_cache_during_apache_startup|/use_cache_during_apache_startup> set set to true. See the L<use_cache_during_apache_startup|/use_cache_during_apache_startup> documentation for more information.
464              
465             =item B<prepare_db [DB, ENTRY]>
466              
467             Prepare the cached L<Rose::DB>-derived object DB for usage. The cached's db object's L<Rose::DB::Cache::Entry> object, ENTRY, is also passed.
468              
469             When I<NOT> running under L<mod_perl>, this method does nothing.
470              
471             When running under L<mod_perl> (version 1.x or 2.x), this method will do the following:
472              
473             =over 4
474              
475             =item * Any L<DBI> database handle created inside a L<Rose::DB> object during apache server startup will be L<marked|Rose::DB::Cache::Entry/created_during_apache_startup> as such. Any attempt to use such an object after the apache startup process has completed (i.e., in a child apache process) will cause it to be discarded and replaced. Note that you usually don't want it to come to this. It's better to cleanly disconnect all such database handles before the first apache child forks off. See the documentation for the L<use_cache_during_apache_startup|/use_cache_during_apache_startup> and L<prepare_for_apache_fork|/prepare_for_apache_fork> methods for more information.
476              
477             =item * All L<DBI> database handles contained in cached L<Rose::DB> objects will be cleared at the end of each request using a C<PerlCleanupHandler>. This will cause L<DBI-E<gt>connect|DBI/connect> to be called the next time a L<dbh|Rose::DB/dbh> is requested from a cached L<Rose::DB> object, which in turn will trigger L<Apache::DBI>'s ping mechanism to ensure that the database handle is fresh.
478              
479             =back
480              
481             Putting all the pieces together, the following implementation of the L<init_db|Rose::DB::Object/init_db> method in your L<Rose::DB::Object>-derived common base class will ensure that database connections are shared and fresh under L<mod_perl> and (optionally) L<Apache::DBI>, but I<unshared> elsewhere:
482              
483             package My::DB::Object;
484              
485             use base 'Rose::DB::Object';
486              
487             use My::DB; # isa Rose::DB
488             ...
489              
490             BEGIN:
491             {
492             if($ENV{'MOD_PERL'})
493             {
494             *init_db = sub { My::DB->new_or_cached };
495             }
496             else # act "normally" when not under mod_perl
497             {
498             *init_db = sub { My::DB->new };
499             }
500             }
501              
502             =item B<set_db DB>
503              
504             Add the L<Rose::DB>-derived object DB to the cache. The DB's L<domain|Rose::DB/domain>, L<type|Rose::DB/type>, and the db object itself (under the parameter name C<db>) are all are passed to the L<build_cache_key|/build_cache_key> method and the DB object is stored under the key returned.
505              
506             If running under L<mod_perl> I<and> the apache server is starting up I<and> L<use_cache_during_apache_startup|/use_cache_during_apache_startup> is set to true, then the DB object is I<not> added to the cache, but merely returned.
507              
508             =item B<use_cache_during_apache_startup [BOOL]>
509              
510             Get or set a boolean value that determines whether or not to cache database objects during the apache server startup process. The default value is determined by the L<default_use_cache_during_apache_startup|/default_use_cache_during_apache_startup> class method.
511              
512             L<DBI> database handles created in the parent apache process cannot be used in child apache processes. Furthermore, in the case of at least L<one|DBD::Informix> one L<DBI driver class|DBI::DBD>, you must I<also> ensure that any database handles created in the apache parent process during server startup are properly L<disconnect()ed|DBI/disconnect> I<before> you fork off the first apache child. Failure to do so may cause segmentation faults(!) in child apache processes.
513              
514             The upshot is that if L<use_cache_during_apache_startup|/use_cache_during_apache_startup> is set to true, you should call L<prepare_for_apache_fork|/prepare_for_apache_fork> at the very end of the apache startup process (i.e., once all other Perl modules have been loaded and all other Perl code has run). This is usually done by placing a call at the bottom of the traditional C<startup.pl> file. Assuming C<My::DB> is your L<Rose::DB|Rose::DB>-derived class:
515              
516             My::DB->db_cache->prepare_for_apache_fork();
517              
518             A L<convenience method|Rose::DB/prepare_cache_for_apache_fork> exists in L<Rose::DB> as well, which simply translates into call shown above:
519              
520             My::DB->prepare_cache_for_apache_fork();
521              
522             =back
523              
524             =head1 AUTHOR
525              
526             John C. Siracusa (siracusa@gmail.com)
527              
528             =head1 LICENSE
529              
530             Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is
531             free software; you can redistribute it and/or modify it under the same terms
532             as Perl itself.