| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Cache::Isolator; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 3 |  |  | 3 |  | 326706 | use strict; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 105 |  | 
| 4 | 3 |  |  | 3 |  | 17 | use warnings; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 91 |  | 
| 5 | 3 |  |  | 3 |  | 16 | use Carp; | 
|  | 3 |  |  |  |  | 10 |  | 
|  | 3 |  |  |  |  | 217 |  | 
| 6 | 3 |  |  | 3 |  | 10982 | use Try::Tiny; | 
|  | 3 |  |  |  |  | 5964 |  | 
|  | 3 |  |  |  |  | 194 |  | 
| 7 | 3 |  |  | 3 |  | 11686 | use Time::HiRes; | 
|  | 3 |  |  |  |  | 2283 |  | 
|  | 3 |  |  |  |  | 28 |  | 
| 8 | 3 |  |  | 3 |  | 366 | use List::Util qw/shuffle/; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 379 |  | 
| 9 |  |  |  |  |  |  | use Class::Accessor::Lite ( | 
| 10 | 3 |  |  |  |  | 27 | ro  => [ qw(cache interval timeout concurrency trial early_expires_ratio expires_before) ], | 
| 11 | 3 |  |  | 3 |  | 957 | ); | 
|  | 3 |  |  |  |  | 1220 |  | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | our $VERSION = '0.02'; | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | sub new { | 
| 16 | 0 |  |  | 0 | 1 |  | my $class = shift; | 
| 17 | 0 |  |  |  |  |  | my %args = ( | 
| 18 |  |  |  |  |  |  | interval => 0.01, | 
| 19 |  |  |  |  |  |  | timeout => 10, | 
| 20 |  |  |  |  |  |  | trial => 0, | 
| 21 |  |  |  |  |  |  | concurrency => 1, | 
| 22 |  |  |  |  |  |  | early_expires_ratio => 0, | 
| 23 |  |  |  |  |  |  | expires_before => 10, | 
| 24 |  |  |  |  |  |  | @_ | 
| 25 |  |  |  |  |  |  | ); | 
| 26 |  |  |  |  |  |  |  | 
| 27 | 0 | 0 | 0 |  |  |  | croak('cache value should be object and appeared add, set and delete methods.') | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 28 |  |  |  |  |  |  | unless ( $args{cache} | 
| 29 |  |  |  |  |  |  | && UNIVERSAL::can( $args{cache}, 'get' ) | 
| 30 |  |  |  |  |  |  | && UNIVERSAL::can( $args{cache}, 'set' ) | 
| 31 |  |  |  |  |  |  | && UNIVERSAL::can( $args{cache}, 'add' ) | 
| 32 |  |  |  |  |  |  | && UNIVERSAL::can( $args{cache}, 'delete' ) ); | 
| 33 |  |  |  |  |  |  |  | 
| 34 | 0 |  |  |  |  |  | bless \%args, $class; | 
| 35 |  |  |  |  |  |  | } | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | sub get_or_set { | 
| 38 | 0 |  |  | 0 | 1 |  | my ($self, $key, $cb, $expires ) = @_; | 
| 39 |  |  |  |  |  |  |  | 
| 40 | 0 |  |  |  |  |  | my $value; | 
| 41 | 0 |  |  |  |  |  | my $try = 0; | 
| 42 |  |  |  |  |  |  |  | 
| 43 | 0 |  |  |  |  |  | TRYLOOP: while  ( 1 ) { | 
| 44 | 0 |  |  |  |  |  | $value = $self->get($key); | 
| 45 | 0 | 0 |  |  |  |  | last TRYLOOP if $value; | 
| 46 |  |  |  |  |  |  |  | 
| 47 | 0 |  |  |  |  |  | $try++; | 
| 48 | 0 |  |  |  |  |  | my @lockkeys = map { $key .":lock:". $_ } shuffle 1..$self->concurrency; | 
|  | 0 |  |  |  |  |  |  | 
| 49 | 0 |  |  |  |  |  | foreach my $lockkey ( @lockkeys ) { | 
| 50 | 0 |  |  |  |  |  | my $locked = $self->cache->add($lockkey, 1, $self->timeout ); #lock | 
| 51 | 0 | 0 |  |  |  |  | if ( $locked ) { | 
| 52 |  |  |  |  |  |  | try { | 
| 53 | 0 |  |  | 0 |  |  | $value = $self->get($key); | 
| 54 | 0 | 0 |  |  |  |  | return 1 if $value; | 
| 55 | 0 |  |  |  |  |  | $value = $cb->(); | 
| 56 | 0 |  |  |  |  |  | $self->set( $key, $value, $expires ); | 
| 57 |  |  |  |  |  |  | } | 
| 58 |  |  |  |  |  |  | catch { | 
| 59 | 0 |  |  | 0 |  |  | die $_; | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  | finally { | 
| 62 | 0 |  |  | 0 |  |  | $self->cache->delete( $lockkey ); #lock | 
| 63 | 0 |  |  |  |  |  | }; | 
| 64 | 0 |  |  |  |  |  | last TRYLOOP; | 
| 65 |  |  |  |  |  |  | } | 
| 66 |  |  |  |  |  |  | } | 
| 67 | 0 | 0 | 0 |  |  |  | die "reached max trial count" if $self->trial > 0 && $try >= $self->trial; | 
| 68 | 0 |  |  |  |  |  | Time::HiRes::sleep( $self->interval ); | 
| 69 |  |  |  |  |  |  | } | 
| 70 | 0 |  |  |  |  |  | return $value; | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | sub set { | 
| 74 | 0 |  |  | 0 | 1 |  | my ($self, $key, $value, $expires) = @_; | 
| 75 | 0 |  |  |  |  |  | $self->cache->set($key, $value, $expires); | 
| 76 | 0 | 0 |  |  |  |  | if ( $self->early_expires_ratio > 0 ) { | 
| 77 | 0 |  |  |  |  |  | $expires = $expires - $self->expires_before; | 
| 78 | 0 | 0 |  |  |  |  | $expires = 1 if $expires <= 0; | 
| 79 | 0 |  |  |  |  |  | $self->cache->set($key . ":earlyexp", $value, $expires); | 
| 80 |  |  |  |  |  |  | } | 
| 81 |  |  |  |  |  |  | } | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | sub get { | 
| 84 | 0 |  |  | 0 | 1 |  | my ($self, $key) = @_; | 
| 85 | 0 | 0 | 0 |  |  |  | if ( $self->early_expires_ratio > 0  && | 
| 86 |  |  |  |  |  |  | int(rand($self->early_expires_ratio)) == 0 ) { | 
| 87 | 0 |  |  |  |  |  | return $self->cache->get($key.":earlyexp"); | 
| 88 |  |  |  |  |  |  | } | 
| 89 | 0 |  |  |  |  |  | my $result = $self->cache->get($key); | 
| 90 | 0 | 0 |  |  |  |  | $result = $self->cache->get($key.":earlyexp") if ! defined $result; | 
| 91 | 0 |  |  |  |  |  | $result; | 
| 92 |  |  |  |  |  |  | } | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | sub delete { | 
| 95 | 0 |  |  | 0 | 1 |  | my ($self, $key) = @_; | 
| 96 | 0 |  |  |  |  |  | $self->cache->delete($key.":earlyexp"); | 
| 97 | 0 |  |  |  |  |  | $self->cache->delete($key); | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | 1; | 
| 101 |  |  |  |  |  |  | __END__ |