File Coverage

blib/lib/Memoize/Expire/ByInstance.pm
Criterion Covered Total %
statement 134 134 100.0
branch 40 46 86.9
condition 13 18 72.2
subroutine 29 29 100.0
pod 2 2 100.0
total 218 229 95.2


line stmt bran cond sub pod time code
1             {
2              
3             package Memoize::Expire::ByInstance;
4 2     2   182409 use 5.006002;
  2         10  
  2         78  
5 2     2   11 use warnings;
  2         3  
  2         62  
6 2     2   9 use strict;
  2         7  
  2         63  
7 2     2   116045 use Time::HiRes qw(time);
  2         4720  
  2         12  
8 2     2   604 use Scalar::Util qw(weaken);
  2         5  
  2         284  
9 2     2   14 use constant FILE_SEPERATOR => chr(0x1C);
  2         4  
  2         3378  
10              
11             our $VERSION = 0.500005;
12              
13             ############################################################################################
14             ## Tie the hash to this class. Support passing a HASH => \$hashref argument to permit
15             ## chaining various tied-hashes together
16             ############################################################################################
17             sub TIEHASH
18             {
19 36     36   15643 my ( $proto, %opts ) = @_;
20 36   33     152 my $class = ref($proto) || $proto;
21              
22 36   50     84 my $default_lifetime = $opts{LIFETIME} || 0;
23 36   50     77 my $default_num_uses = $opts{NUM_USES} || 0;
24              
25 36         177 my $self =
26             { _meta => { _hash_data => {}, _expire => { _default => { lifetime => $default_lifetime, num_uses => $default_num_uses, }, }, } };
27 36 100 66     158 $self->{_hash} = ( exists( $opts{HASH} ) && ref( $opts{HASH} ) eq 'HASH' ) ? $opts{HASH} : {};
28 36         71 bless( $self, $class );
29              
30             # Memoize doesn't deal well with "memoize('Package::method', ...)"; hence it must be tied and memoized
31             # in the same package that its used in... kinda annoying for unit testing... but handy in that I can use caller
32 36 50       169 $self->__insert_destroy_wrapper( (caller)[0] ) if( $opts{AUTO_DESTROY} );
33 36   100     174 $self->_argument_seperator( $opts{ARGUMENT_SEPERATOR} || FILE_SEPERATOR );
34              
35 36         148 return $self;
36             }
37              
38             ############################################################################################
39             ## Reset num_uses and last_set_time, and store the new value.
40             ############################################################################################
41             sub STORE
42             {
43 18     18   2002 my ( $self, $key, $value ) = @_;
44 18         40 ( my $instance_id, $key ) = $self->_split_instance($key);
45 18 50       42 return unless($key);
46              
47 18         77 $self->{_meta}->{_hash_data}->{$key}->{last_set_time} = time();
48 18         37 $self->{_meta}->{_hash_data}->{$key}->{num_uses} = 0;
49              
50 18         35 $self->{_hash}->{$key} = $value;
51 18         52 return $value;
52             }
53              
54             ############################################################################################
55             ## Increment num_uses, and return the value for the specified key
56             ############################################################################################
57             sub FETCH
58             {
59 53     53   525 my ( $self, $key ) = @_;
60 53         110 ( my $instance_id, $key ) = $self->_split_instance($key);
61 53 50       117 return unless($key);
62              
63 53         115 $self->{_meta}->{_hash_data}->{$key}->{num_uses}++;
64              
65 53         346 return $self->{_hash}->{$key};
66             }
67              
68             ############################################################################################
69             ## Return a true value if the key both exists AND has not expired for the instance fetching
70             ## it
71             ############################################################################################
72             sub EXISTS
73             {
74 71     71   7014879 my ( $self, $key ) = @_;
75 71         178 ( my $instance_id, $key ) = $self->_split_instance($key);
76 71 50       158 return unless($key);
77              
78 71 100       158 return if( $self->_key_has_expired( $instance_id, $key ) );
79 67         256 return ( exists( $self->{_hash}->{$key} ) );
80             }
81              
82             ############################################################################################
83             ## Delete a member from the hash
84             ############################################################################################
85             sub DELETE
86             {
87 1     1   3 my ( $self, $key ) = @_;
88 1         5 ( my $instance_id, $key ) = $self->_split_instance($key);
89 1 50       5 return unless($key);
90              
91 1 50       6 delete( $self->{_meta}->{_hash_data}->{$key} ) if( exists( $self->{_meta}->{_hash_data}->{$key} ) );
92 1         7 return ( delete( $self->{_hash}->{$key} ) );
93             }
94              
95              
96             ############################################################################################
97             ## Return next key in hash
98             ############################################################################################
99             sub NEXTKEY
100             {
101 3     3   3 my ($self) = @_;
102 3         5 return ( each( %{ $self->{_hash} } ) );
  3         14  
103             }
104              
105              
106             ############################################################################################
107             ## Return first key in hash
108             ############################################################################################
109             sub FIRSTKEY
110             {
111 5     5   20 my ($self) = @_;
112 5         8 my @keys = keys( %{ $self->{_hash} } );
  5         13  
113 5         6 return ( each( %{ $self->{_hash} } ) );
  5         23  
114             }
115              
116              
117             ############################################################################################
118             ## Delete all members from hash
119             ############################################################################################
120             sub CLEAR
121             {
122 1     1   3 my ($self) = @_;
123              
124 1         1 map { delete( $self->{_meta}->{_hash_data}->{$_} ) } keys %{ $self->{_hash} };
  1         4  
  1         4  
125 1         3 %{ $self->{_hash} } = ();
  1         3  
126 1         3 return;
127             }
128              
129             ############################################################################################
130             ## Return scalar equivilency of hash
131             ############################################################################################
132             sub SCALAR
133             {
134 1     1   3 my ($self) = @_;
135 1         2 return ( scalar %{ $self->{_hash} } );
  1         7  
136             }
137              
138             ############################################################################################
139             ## Register an instance with this tied hash, which will be used for lifetime/num_uses
140             ## expiration uniqueness
141             ############################################################################################
142             sub register
143             {
144 16     16 1 3762 my ( $self, $instance_id, %opts ) = @_;
145 16 100       48 my $lifetime = defined( $opts{lifetime} ) ? $opts{lifetime} : $self->{_meta}->{_expire}->{_default}->{lifetime};
146 16 100       42 my $num_uses = defined( $opts{num_uses} ) ? $opts{num_uses} : $self->{_meta}->{_expire}->{_default}->{num_uses};
147              
148             ## we make instance_id a string, because when its Memoized, the
149             ## key will be be a string concatination of arguments
150 16         22 $instance_id = "$instance_id";
151              
152 16 100       53 $self->{_meta}->{_expire}->{$instance_id}->{lifetime} = $lifetime
153             if( $lifetime != $self->{_meta}->{_expire}->{_default}->{lifetime} );
154 16 100       52 $self->{_meta}->{_expire}->{$instance_id}->{num_uses} = $num_uses
155             if( $num_uses != $self->{_meta}->{_expire}->{_default}->{num_uses} );
156 16         43 return $self;
157             }
158              
159             ############################################################################################
160             ## Unregister an instance from this tied hash, freeing the memory used in the hash for it
161             ############################################################################################
162             sub unregister
163             {
164 28     28 1 45 my ( $self, $instance_id ) = @_;
165              
166             ## we make instance_id a string, because when its Memoized, the
167             ## key will be be a string concatination of arguments
168 28         34 $instance_id = "$instance_id";
169              
170 28 100       88 if( exists( $self->{_meta}->{_expire}->{$instance_id} ) ) {
171             ## undef explicitely to force immediate freeing of memory
172 10         25 undef( $self->{_meta}->{_expire}->{$instance_id} );
173 10         34 delete( $self->{_meta}->{_expire}->{$instance_id} );
174             }
175 28         37 return;
176             }
177              
178             ############################################################################################
179             ## Test if a key has expired for the particular instance testing EXISTS
180             ############################################################################################
181             sub _key_has_expired
182             {
183 71     71   102 my ( $self, $instance_id, $key ) = @_;
184              
185             # We will assume that if we do not have current _hash_data for the underlying storage
186             # that it has expired.
187 71 100       223 if( exists( $self->{_meta}->{_hash_data}->{$key} ) ) {
188 56         131 my $last_set_time = $self->{_meta}->{_hash_data}->{$key}->{last_set_time};
189 56         113 my $num_uses = $self->{_meta}->{_hash_data}->{$key}->{num_uses};
190              
191 56         79 my $lifetime_lookup_id = '_default';
192 56         66 my $num_uses_lookup_id = '_default';
193              
194 56 100       181 if( exists( $self->{_meta}->{_expire}->{$instance_id} ) ) {
195 30 100       117 $lifetime_lookup_id = $instance_id if( exists( $self->{_meta}->{_expire}->{$instance_id}->{lifetime} ) );
196 30 100       113 $num_uses_lookup_id = $instance_id if( exists( $self->{_meta}->{_expire}->{$instance_id}->{num_uses} ) );
197             }
198              
199 56         205 my $max_lifetime = $self->{_meta}->{_expire}->{$lifetime_lookup_id}->{lifetime};
200 56         146 my $max_num_uses = $self->{_meta}->{_expire}->{$num_uses_lookup_id}->{num_uses};
201              
202 56 100 100     330 return 1 if( ( $max_lifetime > 0 ) && ( time() >= $last_set_time + $max_lifetime ) );
203 54 100 100     239 return 1 if( ( $max_num_uses > 0 ) && ( $num_uses >= $max_num_uses ) );
204             }
205 67         195 return;
206             }
207              
208             ############################################################################################
209             ## split the key apart on the ascii File-Seperater character used by Memoize when creating hash-keys
210             ############################################################################################
211             sub _split_instance
212             {
213 143     143   227 my ( $self, $key ) = @_;
214 143         342 (my $instance_id, $key ) = split( $self->_argument_seperator(), $key, 2 );
215 143 100       601 return($instance_id, $key) if($key);
216 1         3 return( $instance_id, $instance_id ); ## XXX Hack, wont work if the key you are looking for contains argumement seperators itself
217             }
218              
219             ############################################################################################
220             ## get/set the seperator applicable to keys being inserted into our hash.
221             ############################################################################################
222             sub _argument_seperator
223             {
224 179     179   255 my ( $self, $value ) = @_;
225 179 100       454 $self->{_meta}->{_argument_seperator} = $value if( defined($value) );
226 179         869 return $self->{_meta}->{_argument_seperator};
227             }
228              
229             ############################################################################################
230             ## Attempt to insert a DESTROY method into the package that tied us. Wrap any existing.
231             ############################################################################################
232             sub __insert_destroy_wrapper
233             {
234 36     36   64 my ( $self, $class ) = @_;
235              
236 36         38 my $weakself = $self;
237 36         90 weaken($weakself);
238 36     12   116 my $sub = sub {return};
  12         65  
239              
240 2     2   16 no strict 'refs';
  2         4  
  2         292  
241 36 100       43 $sub = *{ '::' . $class . '::DESTROY' }{CODE} if( defined( *{ '::' . $class . '::DESTROY' }{CODE} ) );
  35         92  
  36         144  
242 2     2   11 use strict;
  2         4  
  2         74  
243              
244 2     2   9 no strict 'refs';
  2         4  
  2         58  
245 2     2   24 no warnings 'redefine';
  2         4  
  2         247  
246 36         102 *{ '::' . $class . '::DESTROY' } = sub {
247 134     134   13138 my ($this) = @_;
248 134 100       295 $weakself->unregister("$this") if( defined($weakself) );
249 134         250 return $sub->(@_);
250 36         193 };
251 2     2   11 use warnings;
  2         13  
  2         66  
252 2     2   10 use strict;
  2         4  
  2         124  
253              
254 36         57 return;
255             }
256             }
257             1;
258             __END__