File Coverage

blib/lib/Memoize/Memcached/Attribute.pm
Criterion Covered Total %
statement 100 115 86.9
branch 17 28 60.7
condition 1 5 20.0
subroutine 24 25 96.0
pod 3 3 100.0
total 145 176 82.3


line stmt bran cond sub pod time code
1             package Memoize::Memcached::Attribute;
2             # ABSTRACT: auto-memoize function results using memcached and subroutine attributes
3              
4 1     1   263182 use strict;
  1         2  
  1         32  
5 1     1   4 use warnings;
  1         2  
  1         23  
6              
7 1     1   796 use Sub::Attribute;
  1         1642  
  1         55  
8              
9 1     1   6 use Digest::MD5 ();
  1         1  
  1         12  
10 1     1   3 use Storable ();
  1         3  
  1         91  
11              
12             our $VERSION = '0.11'; # VERSION
13             our $MEMCACHE;
14             our %CLIENT_PARAMS = (
15             servers => ['127.0.0.1:11211']
16             );
17              
18             sub import {
19 1     1   16 my $package = shift;
20 1 50       9 my %attrs = (UNIVERSAL::isa($_[0], 'HASH')) ? %{ $_[0] } : @_;
  0         0  
21              
22 1 50       5 unless ($attrs{'-noattrimport'}) {
23 1         2 my ($caller) = caller();
24 1     1   5 no strict 'refs';
  1         1  
  1         298  
25 1         2 *{ "$caller\::CacheMemoize" } = \&CacheMemoize;
  1         7  
26 1         2 *{ "$caller\::MODIFY_CODE_ATTRIBUTES" } = \&MODIFY_CODE_ATTRIBUTES;
  1         5  
27             }
28              
29 1         9 delete $attrs{'-noattrimport'};
30              
31 1 50       11 if ($attrs{'-client'}) {
32 1         84 $MEMCACHE = $attrs{'-client'};
33             }
34             else {
35 0         0 %CLIENT_PARAMS = %attrs;
36 0         0 $MEMCACHE = _connect();
37             }
38             }
39              
40             sub reset {
41 1     1 1 754 undef $MEMCACHE;
42 1 50       23 if (@_) {
    0          
43 1         4 $MEMCACHE = $_[0];
44             }
45             elsif (%CLIENT_PARAMS) {
46 0         0 $MEMCACHE = _connect();
47             }
48             }
49              
50             sub _connect {
51 0     0   0 my $memcache_pkg;
52 0         0 eval {
53 0         0 require Cache::Memcached::Fast;
54 0         0 $memcache_pkg = 'Cache::Memcached::Fast';
55             };
56 0 0       0 if ($@) {
57 0         0 require Cache::Memcached;
58 0         0 $memcache_pkg = 'Cache::Memcached';
59             }
60 0         0 return $memcache_pkg->new(\%CLIENT_PARAMS);
61             }
62              
63             sub CacheMemoize :ATTR_SUB {
64 4     4 1 482 my ($package, $symbol, $referent, $attr, $params) = @_;
65              
66 1     1   10 no strict 'refs';
  1         2  
  1         89  
67              
68 4         9 $params = _parse_attr_params($params);
69              
70 4         7 my $is_method = 0;
71 4 100       7 if (@$params > 1) {
72 2         3 my $type = shift @$params;
73 2 100       8 $is_method = 1 if (lc($type) eq 'method');
74             }
75              
76 4         6 my $duration = $params->[0];
77              
78 4         4 my $symbol_name = join('::', $package, *{ $symbol }{NAME});
  4         12  
79              
80 1     1   4 no warnings 'redefine';
  1         2  
  1         238  
81 4         4 my $original = \&{ $symbol_name };
  4         12  
82 4         66 *{$symbol_name} = sub {
83 20     20   2014221 my @args = @_;
84              
85             # if we're in a method, don't use the object to build the key
86 20         131 my @key_args = @args;
87 20 100       61 shift @key_args if ($is_method);
88              
89 20         48 my $key = _build_key($symbol_name, @key_args);
90              
91 20 50       715 if (wantarray) {
92 0         0 $key .= '-wantarray';
93 0   0     0 my $ref = $MEMCACHE->get($key) || do {
94             my @list = $original->(@args);
95             $MEMCACHE->set($key, \@list, $duration) if (@list);
96             \@list;
97             };
98 0         0 return @$ref;
99             }
100              
101              
102              
103 20         71 my $cached = $MEMCACHE->get($key);
104 20 100       198 return $cached if (defined $cached);
105              
106 12         67 my $result = $original->(@args);
107 12 50       94 $MEMCACHE->set($key, $result, $duration) if (defined $result);
108 12         113 return $result;
109 4         16 };
110 1     1   5 }
  1         1  
  1         8  
111              
112             sub invalidate {
113 2     2 1 2146 my $symbol_name = shift;
114 2 100       14 if ($symbol_name !~ /::/) {
115             # build the full method from the caller's namespace if necessary
116 1         7 $symbol_name = join('::', (caller)[0], $symbol_name);
117             }
118              
119 2         9 my $key = Memoize::Memcached::Attribute::_build_key($symbol_name, @_);
120 2         111 $MEMCACHE->delete($key);
121 2         23 $MEMCACHE->delete("${key}-wantarray");
122             }
123              
124             sub _parse_attr_params {
125 4     4   4 my ($string) = @_;
126              
127 4 50       9 return [] unless defined $string;
128              
129 1     1   5 my $data = eval "
  1     1   1  
  1     1   30  
  1     1   4  
  1     1   1  
  1     1   17  
  1     1   4  
  1     1   1  
  1         29  
  1         4  
  1         1  
  1         15  
  1         5  
  1         2  
  1         36  
  1         4  
  1         2  
  1         28  
  1         4  
  1         1  
  1         21  
  1         4  
  1         2  
  1         20  
  4         246  
130             no warnings;
131             no strict;
132             [$string]
133             ";
134              
135 4   50     14 return $data || [$string];
136             }
137              
138             sub _build_key {
139 22     22   35 local $Storable::canonical = 1;
140 22         96 return Digest::MD5::md5_base64(Storable::nfreeze(\@_));
141             }
142              
143             1;
144              
145              
146             =pod
147              
148             =head1 NAME
149              
150             Memoize::Memcached::Attribute - auto-memoize function results using memcached and subroutine attributes
151              
152             =head1 VERSION
153              
154             version 0.11
155              
156             =head1 SYNOPSIS
157              
158             If you're running memcache on your local box, with the default port, you can initialize without passing any
159             parameters:
160              
161             use Memoize::Memcached::Attribute;
162              
163             This will use the default server list of 127.0.0.1:11211
164              
165             If you want to specify the constructor parameters for your Cache::Memcached or Cache::Memcached::Fast client object,
166             you can pass them in during import:
167              
168             use Memoize::Memcached::Attribute (
169             servers => [ '192.168.1.2:11211', '192.168.1.3:11211' ],
170             _connect_timeout => 0.1,
171             max_failures => 5,
172             );
173              
174             Alternatively, you can pass in your memcache client object entirely (we use this because
175             we subclass Cache::Memcached::Fast to add some additional methods and default parameters):
176              
177             use Memoize::Memcached::Attribute (-client => Cache::Memcached::Fast->new(\%some_params));
178              
179             Or you can specify it at runtime, the only caveat being that you must do this prior to calling any memoized function:
180              
181             use Memoize::Memcache::Attribute;
182             Memoize::Memcache::Attribute::reset(Cache::Memcached::Fast->new(\%some_params));
183              
184             And that's basically it. Now you have a :CacheMemoize subroutine attribute that will memoize subroutine results
185             based on their parameters, storing the memoized data in memcache for a specified duration.
186              
187             To use the memoization, you just pass your cache duration to the :CacheMemoize subroutine attribute:
188              
189             # cache the results in memcache for 5 minutes
190             sub myfunc :CacheMemoize(300) {
191             my @params = @_;
192             my $total;
193             $total += $_ for @params;
194             return $total;
195             }
196              
197             Sometimes you have an object method that is not dependent on object state, and you want to memoize those results,
198             independent of the object used to generate them. So we provide that option by passing in 'method' as your first
199             parameter with the cache duration as the second:
200              
201             # cache the results in memcache for 30 seconds
202             # but don't look at the object as part of the input data
203             sub mymethod :CacheMemoize(method => 30) {
204             my $self = shift;
205             my @params = @_;
206             return join('.', @params);
207             }
208              
209             Really, you can pass anything in as a first parameter and it will be ignored if it isn't case-insensitively equal to 'method'.
210              
211             While not generally recommended as good design, we do support the ability to
212             invalidate caches. If you find yourself using the invalidation often, this module
213             is probably not really how you want to go about achieving your caching strategy.
214             Here's how you do it:
215              
216             Memoize::Memcached::Attribute::invalidate('Some::Package::myfunc', @params);
217              
218             If you're invalidating the cache from inside the same package as the cached function (which
219             is probably the only place you should be), you can omit the package name:
220              
221             Memoize::Memcached::Attribute::invalidate('mymethod', @params);
222              
223             =head1 DESCRIPTION
224              
225             Memoization is a process whereby you cache the results of a function, based on its input,
226             in memory so that repeated calls to the same function don't waste cycles recalculating the results. Generally you use
227             it with functions that are somewhat expensive to run (or that you have to run so frequently they become expensive), and that
228             always return the same results based on the same input (i.e. they have no side effects). This module expands that concept to
229             use memcache to provide a shared memory cache, rather than a per-process cache like a lot of other memoization modules, so
230             that multiple processes can reuse the results. It gives you the added benefit that the memoization is not permanent, because
231             you specify a timeout on the cached data. So, if you have a method that has no side effects, but the data it's returning might
232             become stale, you can still get the benefits of memoization while also having it automatically recalculate the results
233             from time to time.
234              
235             =head1 METHODS
236              
237             =head2 reset
238              
239             Allows you to reset the package global memcache client after forking.
240              
241             =head2 invalidate
242              
243             Allows you to invalidate cached data.
244              
245             =head2 import
246              
247             Allows you to specify memcache connection parameters or your own client object to be used.
248              
249             =head2 CacheMemoize
250              
251             Should not be called directly. This is the subroutine attribute handler exported by this package.
252              
253             =head1 METHODS
254              
255             =head1 OPTIONS
256              
257             When you import the package, you can pass a few options in:
258              
259             =over 4
260              
261             =item -noattrimport - By default, we import some methods to make the attribute work properly in subclasses.
262             This flag prevents that behavior. It allows you to avoid cluttering your namespace a little, at the expense of
263             not working with inheritance.
264              
265             =item -client - Allows you to specify your own memcache client object. Useful if you subclass
266             Cache::Memcached in your codebase.
267              
268             =back
269              
270             Any remaining options will be used to _connect to the Cache::Memcached client object, if passed.
271              
272             =head1 THREADS/FORKING
273              
274             Because this module internally stores the memcached client as a package global, and the memcached clients
275             have issues with threads and forking, it would be wise to reset the package global after forking or creating
276             a new thread. This can be done like this:
277              
278             if (my $pid = fork) {
279             # parent
280             }
281             else {
282             # create a new client using the parameters you used to create the original object
283             Memoize::Memcached::Attribute::reset();
284             # or pass in your own object
285             Memoize::Memcached::Attribute::reset($new_memcached_client);
286             }
287              
288             =head1 ACKNOWLEDGEMENTS
289              
290             Thanks to Chris Reinhardt and David Dierauer for finding and fixing some issues. And to
291             LiquidWeb for allowing me to contribute this to CPAN.
292              
293             =head1 BUGS
294              
295             None known. This has been in use in LiquidWeb production code for a few years without any known issues.
296             It was slightly modified to remove some dependence on other LiquidWeb code, so there's an extremely
297             remote chance that something broke in the process.
298              
299             If you find a bug, or have a feature request, submit it here:
300             https://github.com/jimbobhickville/perl-Memoize-Memcached-Attribute/issues/new
301              
302             =head1 AUTHOR
303              
304             Greg Hill
305              
306             =head1 COPYRIGHT AND LICENSE
307              
308             This software is copyright (c) 2012 by Greg Hill.
309              
310             This is free software; you can redistribute it and/or modify it under
311             the same terms as the Perl 5 programming language system itself.
312              
313             =cut
314              
315              
316             __END__