1package CHI::Driver;
2$CHI::Driver::VERSION = '0.60';
3use Carp;
4use CHI::CacheObject;
5use CHI::Constants qw(CHI_Max_Time);
6use CHI::Driver::Metacache;
7use CHI::Driver::Role::HasSubcaches;
8use CHI::Driver::Role::IsSizeAware;
9use CHI::Driver::Role::IsSubcache;
10use CHI::Driver::Role::Universal;
11use CHI::Serializer::Storable;
12use CHI::Serializer::JSON;
13use CHI::Util qw(parse_duration);
14use CHI::Types qw(:all);
15use Digest::MD5;
16use Encode;
17use Hash::MoreUtils qw(slice_grep);
18use Log::Any qw($log);
19use Moo;
20use MooX::Types::MooseLike::Base qw(:all);
21use Scalar::Util qw(blessed);
22use Time::Duration;
23use Time::HiRes qw(gettimeofday);
24use strict;
25use warnings;
26
27my $default_serializer     = CHI::Serializer::Storable->new();
28my $default_key_serializer = CHI::Serializer::JSON->new();
29my $default_key_digester   = Digest::MD5->new();
30
31my @common_params;
32{
33    my %attr = (
34        chi_root_class => {
35            is => 'ro',
36        },
37        compress_threshold => {
38            is  => 'ro',
39            isa => Int,
40        },
41        constructor_params => {
42            is       => 'ro',
43            init_arg => undef,
44        },
45        driver_class => {
46            is => 'ro',
47        },
48        expires_at => {
49            is      => 'rw',
50            default => sub { CHI_Max_Time },
51        },
52        expires_in => {
53            is     => 'rw',
54            isa    => Duration,
55            coerce => \&to_Duration,
56        },
57        expires_on_backend => {
58            is      => 'ro',
59            isa     => Num,
60            default => sub { 0 },
61        },
62        expires_variance => {
63            is      => 'rw',
64            isa     => Num,
65            default => sub { 0 },
66        },
67        has_subcaches => {
68            is       => 'lazy',
69            isa      => Bool,
70            init_arg => undef,
71        },
72        is_size_aware => {
73            is  => 'ro',
74            isa => Bool,
75        },
76        is_subcache => {
77            is  => 'ro',
78            isa => Bool,
79        },
80        key_digester => {
81            is      => 'ro',
82            isa     => Digester,
83            coerce  => \&to_Digester,
84            default => sub { $default_key_digester },
85        },
86        key_serializer => {
87            is      => 'ro',
88            isa     => Serializer,
89            coerce  => \&to_Serializer,
90            default => sub { $default_key_serializer },
91        },
92        label => {
93            is        => 'rw',
94            lazy      => 1,
95            builder   => 1,
96            clearer   => 1,
97            predicate => 1,
98        },
99        max_build_depth => {
100            is      => 'ro',
101            default => sub { 8 },
102        },
103        max_key_length => {
104            is      => 'ro',
105            isa     => Int,
106            default => sub { 1 << 31 },
107        },
108        metacache => {
109            is        => 'lazy',
110            clearer   => 1,
111            predicate => 1,
112        },
113        namespace => {
114            is      => 'ro',
115            isa     => Str,
116            default => sub { 'Default' },
117        },
118        on_get_error => {
119            is      => 'rw',
120            isa     => OnError,
121            default => sub { 'log' },
122        },
123        on_set_error => {
124            is      => 'rw',
125            isa     => OnError,
126            default => sub { 'log' },
127        },
128        serializer => {
129            is      => 'ro',
130            isa     => Serializer,
131            coerce  => \&to_Serializer,
132            default => sub { $default_serializer },
133        },
134        short_driver_name => {
135            is        => 'lazy',
136            clearer   => 1,
137            predicate => 1,
138        },
139        storage => {
140            is => 'ro',
141        },
142    );
143    push @common_params, keys %attr;
144    for my $attr ( keys %attr ) {
145        has $attr => %{ $attr{$attr} };
146    }
147}
148
149sub _build_has_subcaches { undef }
150
151# These methods must be implemented by subclass
152foreach my $method (qw(fetch store remove get_keys get_namespaces)) {
153    no strict 'refs';
154    *{$method} = sub { die "method '$method' must be implemented by subclass" };
155}
156
157# Given a hash of params, return the subset that are not in CHI's common parameters.
158#
159push @common_params, qw(
160  discard_policy
161  discard_timeout
162  l1_cache
163  max_size
164  max_size_reduction_factor
165  mirror_cache
166  parent_cache
167  subcache_type
168  subcaches
169);
170my %common_params = map { ( $_, 1 ) } @common_params;
171
172sub non_common_constructor_params {
173    my ( $class, $params ) = @_;
174
175    return {
176        map { ( $_, $params->{$_} ) }
177        grep { !$common_params{$_} } keys(%$params)
178    };
179}
180
181sub declare_unsupported_methods {
182    my ( $class, @methods ) = @_;
183
184    foreach my $method (@methods) {
185        no strict 'refs';
186        *{"$class\::$method"} =
187          sub { croak "method '$method' not supported by '$class'" };
188    }
189}
190
191sub cache_object_class { 'CHI::CacheObject' }
192
193# To override time() for testing - must be writable in a dynamically scoped way from tests
194our $Test_Time;    ## no critic (ProhibitPackageVars)
195our $Build_Depth = 0;    ## no critic (ProhibitPackageVars)
196
197sub valid_get_options { qw(expire_if busy_lock) }
198sub valid_set_options { qw(expires_at expires_in expires_variance) }
199
200sub BUILD {
201    my ( $self, $params ) = @_;
202
203    # Ward off infinite build recursion, e.g. from circular subcache configuration.
204    #
205    local $Build_Depth = $Build_Depth + 1;
206    die "$Build_Depth levels of CHI cache creation; infinite recursion?"
207      if ( $Build_Depth > $self->max_build_depth );
208
209    # Save off constructor params. Used to create metacache, for
210    # example. Hopefully this will not cause circular references...
211    #
212    $self->{constructor_params} = {%$params};
213    foreach my $param (qw(l1_cache mirror_cache parent_cache)) {
214        delete( $self->{constructor_params}->{$param} );
215    }
216
217    # If stats enabled, add ns_stats slot for keeping track of stats
218    #
219    my $stats = $self->chi_root_class->stats;
220    if ( $stats->enabled ) {
221        $self->{ns_stats} = $stats->stats_for_driver($self);
222    }
223
224    # Call BUILD_roles on any of the roles that need initialization.
225    #
226    $self->BUILD_roles($params);
227}
228
229sub BUILD_roles {
230
231    # Will be modified by roles that need it
232}
233
234sub _build_short_driver_name {
235    my ($self) = @_;
236
237    ( my $name = $self->driver_class ) =~ s/^CHI::Driver:://;
238
239    return $name;
240}
241
242sub _build_label {
243    my ($self) = @_;
244
245    return $self->short_driver_name;
246}
247
248sub _build_metacache {
249    my $self = shift;
250
251    return CHI::Driver::Metacache->new( owner_cache => $self );
252}
253
254sub get {
255    my ( $self, $key, %params ) = @_;
256
257    croak "must specify key" unless defined($key);
258    my $ns_stats     = $self->{ns_stats};
259    my $log_is_debug = $log->is_debug;
260    my $measure_time = defined($ns_stats) || $log_is_debug;
261    my ( $start_time, $elapsed_time );
262
263    # Fetch cache object
264    #
265    $start_time = gettimeofday() if $measure_time;
266    my $obj = eval { $params{obj} || $self->get_object($key) };
267    $elapsed_time = ( gettimeofday() - $start_time ) * 1000 if $measure_time;
268    if ( my $error = $@ ) {
269        $ns_stats->{'get_errors'}++ if defined($ns_stats);
270        $self->_handle_get_error( $error, $key );
271        return undef;
272    }
273    if ( !defined $obj ) {
274        $self->_record_get_stats( 'absent_misses', $elapsed_time )
275          if defined($ns_stats);
276        $self->_log_get_result( $log, "MISS (not in cache)",
277            $key, $elapsed_time )
278          if $log_is_debug;
279        return undef;
280    }
281    if ( defined( my $obj_ref = $params{obj_ref} ) ) {
282        $$obj_ref = $obj;
283    }
284
285    # Check if expired
286    #
287    my $is_expired = $obj->is_expired()
288      || ( defined( $params{expire_if} )
289        && $params{expire_if}->( $obj, $self ) );
290    if ($is_expired) {
291        $self->_record_get_stats( 'expired_misses', $elapsed_time )
292          if defined($ns_stats);
293        $self->_log_get_result( $log, "MISS (expired)", $key, $elapsed_time )
294          if $log_is_debug;
295
296        # If busy_lock value provided, set a new "temporary" expiration time that many
297        # seconds forward before returning undef
298        #
299        if ( defined( my $busy_lock = $params{busy_lock} ) ) {
300            my $time = $Test_Time || time();
301            my $busy_lock_time = $time + parse_duration($busy_lock);
302            $obj->set_early_expires_at($busy_lock_time);
303            $obj->set_expires_at($busy_lock_time);
304            $self->set_object( $key, $obj );
305        }
306
307        return undef;
308    }
309
310    $self->_record_get_stats( 'hits', $elapsed_time ) if defined($ns_stats);
311    $self->_log_get_result( $log, "HIT", $key, $elapsed_time ) if $log_is_debug;
312    return $obj->value;
313}
314
315sub _record_get_stats {
316    my ( $self, $stat, $elapsed_time ) = @_;
317    $self->{ns_stats}->{$stat}++;
318    $self->{ns_stats}->{'get_time_ms'} += $elapsed_time;
319}
320
321sub unpack_from_data {
322    my ( $self, $key, $data ) = @_;
323
324    return $self->cache_object_class->unpack_from_data( $key, $data,
325        $self->serializer );
326}
327
328sub get_object {
329    my ( $self, $key ) = @_;
330
331    croak "must specify key" unless defined($key);
332    $key = $self->transform_key($key);
333
334    my $data = $self->fetch($key) or return undef;
335    my $obj = $self->unpack_from_data( $key, $data );
336    return $obj;
337}
338
339sub get_expires_at {
340    my ( $self, $key ) = @_;
341    croak "must specify key" unless defined($key);
342
343    if ( my $obj = $self->get_object($key) ) {
344        return $obj->expires_at;
345    }
346    else {
347        return;
348    }
349}
350
351sub exists_and_is_expired {
352    my ( $self, $key ) = @_;
353    croak "must specify key" unless defined($key);
354
355    if ( my $obj = $self->get_object($key) ) {
356        return $obj->is_expired;
357    }
358    else {
359        return;
360    }
361}
362
363sub is_valid {
364    my ( $self, $key ) = @_;
365    croak "must specify key" unless defined($key);
366
367    if ( my $obj = $self->get_object($key) ) {
368        return !$obj->is_expired;
369    }
370    else {
371        return;
372    }
373}
374
375sub _default_set_options {
376    my $self = shift;
377
378    return { map { $_ => $self->$_() }
379          qw( expires_at expires_in expires_variance ) };
380}
381
382sub set {
383    my $self = shift;
384    my ( $key, $value, $options ) = @_;
385
386    croak "must specify key" unless defined($key);
387    $key = $self->transform_key($key);
388    return unless defined($value);
389
390    # Fill in $options if not passed, copy if passed, and apply defaults.
391    #
392    if ( !defined($options) ) {
393        $options = $self->_default_set_options;
394    }
395    else {
396        if ( !ref($options) ) {
397            if ( $options eq 'never' ) {
398                $options = { expires_at => CHI_Max_Time };
399            }
400            elsif ( $options eq 'now' ) {
401                $options = { expires_in => 0 };
402            }
403            else {
404                $options = { expires_in => $options };
405            }
406        }
407
408        # Disregard default expires_at and expires_in if either are provided
409        #
410        if (   exists( $options->{expires_at} )
411            || exists( $options->{expires_in} ) )
412        {
413            $options =
414              { expires_variance => $self->expires_variance, %$options };
415        }
416        else {
417            $options = { %{ $self->_default_set_options }, %$options };
418        }
419    }
420
421    $self->set_with_options( $key, $value, $options );
422}
423
424sub set_with_options {
425    my ( $self, $key, $value, $options ) = @_;
426    my $ns_stats     = $self->{ns_stats};
427    my $log_is_debug = $log->is_debug;
428    my $measure_time = defined($ns_stats) || $log_is_debug;
429    my ( $start_time, $elapsed_time );
430
431    # Determine early and final expiration times
432    #
433    my $time = $Test_Time || time();
434    my $created_at = $time;
435    my $expires_at =
436      ( defined( $options->{expires_in} ) )
437      ? $time + parse_duration( $options->{expires_in} )
438      : $options->{expires_at};
439    my $early_expires_at =
440        defined( $options->{early_expires_at} ) ? $options->{early_expires_at}
441      : ( $expires_at == CHI_Max_Time )         ? CHI_Max_Time
442      : $expires_at -
443      ( ( $expires_at - $time ) * $options->{expires_variance} );
444
445    # Pack into data, and store
446    #
447    my $obj =
448      $self->cache_object_class->new( $key, $value, $created_at,
449        $early_expires_at, $expires_at, $self->serializer,
450        $self->compress_threshold );
451    if ( defined( my $obj_ref = $options->{obj_ref} ) ) {
452        $$obj_ref = $obj;
453    }
454    $start_time = gettimeofday() if $measure_time;
455    if ( $self->set_object( $key, $obj ) ) {
456        $elapsed_time = ( gettimeofday() - $start_time ) * 1000
457          if $measure_time;
458
459        # Log the set
460        #
461        if ( defined($ns_stats) ) {
462            $ns_stats->{'sets'}++;
463            $ns_stats->{'set_key_size'}   += length( $obj->key );
464            $ns_stats->{'set_value_size'} += $obj->size;
465            $ns_stats->{'set_time_ms'}    += $elapsed_time;
466        }
467        if ($log_is_debug) {
468            $self->_log_set_result( $log, $obj, $elapsed_time );
469        }
470    }
471
472    return $value;
473}
474
475sub set_object {
476    my ( $self, $key, $obj ) = @_;
477
478    my $data               = $obj->pack_to_data();
479    my $expires_on_backend = $self->expires_on_backend;
480    my @expires_in         = (
481        $expires_on_backend && $obj->expires_at < CHI_Max_Time
482        ? ( ( $obj->expires_at - time ) * $expires_on_backend )
483        : ()
484    );
485    eval { $self->store( $key, $data, @expires_in ) };
486    if ( my $error = $@ ) {
487        $self->{ns_stats}->{'set_errors'}++ if defined( $self->{ns_stats} );
488        $self->_handle_set_error( $error, $obj );
489        return 0;
490    }
491    return 1;
492}
493
494sub get_keys_iterator {
495    my ($self) = @_;
496
497    my @keys = $self->get_keys();
498    return sub { shift(@keys) };
499}
500
501sub clear {
502    my $self = shift;
503    die "clear takes no arguments" if @_;
504
505    $self->remove_multi( [ $self->get_keys() ] );
506}
507
508sub expire {
509    my ( $self, $key ) = @_;
510    croak "must specify key" unless defined($key);
511
512    my $time = $Test_Time || time();
513    if ( defined( my $obj = $self->get_object($key) ) ) {
514        my $expires_at = $time - 1;
515        $obj->set_early_expires_at($expires_at);
516        $obj->set_expires_at($expires_at);
517        $self->set_object( $key, $obj );
518    }
519}
520
521sub compute {
522    my $self      = shift;
523    my $key       = shift;
524    my $wantarray = wantarray();
525
526    # Allow these in either order for backward compatibility
527    my ( $code, $options ) =
528      ( ref( $_[0] ) eq 'CODE' ) ? ( $_[0], $_[1] ) : ( $_[1], $_[0] );
529
530    croak "must specify key and code" unless defined($key) && defined($code);
531
532    my %get_options =
533      ( ref($options) eq 'HASH' )
534      ? slice_grep { /(?:expire_if|busy_lock)/ } $options
535      : ();
536    my $set_options =
537        ( ref($options) eq 'HASH' )
538      ? { slice_grep { !/(?:expire_if|busy_lock)/ } $options }
539      : $options;
540
541    my $value = $self->get( $key, %get_options );
542    if ( !defined $value ) {
543        my ( $start_time, $elapsed_time );
544        my $ns_stats = $self->{ns_stats};
545        $start_time = gettimeofday if defined($ns_stats);
546        $value = $wantarray ? [ $code->() ] : $code->();
547        $elapsed_time = ( gettimeofday() - $start_time ) * 1000
548          if defined($ns_stats);
549        $self->set( $key, $value, $set_options );
550        if ( defined($ns_stats) ) {
551            $ns_stats->{'computes'}++;
552            $ns_stats->{'compute_time_ms'} += $elapsed_time;
553        }
554    }
555    return $wantarray ? @$value : $value;
556}
557
558sub purge {
559    my ($self) = @_;
560
561    foreach my $key ( $self->get_keys() ) {
562        if ( my $obj = $self->get_object($key) ) {
563            if ( $obj->is_expired() ) {
564                $self->remove($key);
565            }
566        }
567    }
568}
569
570sub dump_as_hash {
571    my ($self) = @_;
572
573    my %hash;
574    foreach my $key ( $self->get_keys() ) {
575        if ( defined( my $value = $self->get($key) ) ) {
576            $hash{$key} = $value;
577        }
578    }
579    return \%hash;
580}
581
582sub is_empty {
583    my ($self) = @_;
584
585    return !$self->get_keys();
586}
587
588#
589# (SEMI-) ATOMIC OPERATIONS
590#
591
592sub add {
593    my $self = shift;
594    my $key  = shift;
595
596    if ( !$self->is_valid($key) ) {
597        $self->set( $key, @_ );
598    }
599}
600
601sub append {
602    my ( $self, $key, $new ) = @_;
603
604    my $current = $self->fetch($key) or return undef;
605    $self->store( $key, $current . $new );
606    return 1;
607}
608
609sub replace {
610    my $self = shift;
611    my $key  = shift;
612
613    if ( $self->is_valid($key) ) {
614        $self->set( $key, @_ );
615    }
616}
617
618#
619# MULTI KEY OPERATIONS
620#
621
622sub fetch_multi_hashref {
623    my ( $self, $keys ) = @_;
624
625    return { map { ( $_, $self->fetch($_) ) } @$keys };
626}
627
628sub get_multi_hashref_objects {
629    my ( $self, $keys ) = @_;
630    my $key_data = $self->fetch_multi_hashref($keys);
631    return {
632        map {
633            my $data = $key_data->{$_};
634            defined($data)
635              ? ( $_, $self->unpack_from_data( $_, $data ) )
636              : ( $_, undef )
637        } keys(%$key_data)
638    };
639}
640
641sub get_multi_arrayref {
642    my ( $self, $keys ) = @_;
643    croak "must specify keys" unless defined($keys);
644    my $transformed_keys = [ map { $self->transform_key($_) } @$keys ];
645
646    my $key_count = scalar(@$keys);
647    my $keyobjs   = $self->get_multi_hashref_objects($transformed_keys);
648    return [
649        map {
650            my $key = $transformed_keys->[$_];
651            my $obj = $keyobjs->{$key};
652            $obj ? $self->get( $key, obj => $obj ) : undef
653        } ( 0 .. $key_count - 1 )
654    ];
655}
656
657sub get_multi_hashref {
658    my ( $self, $keys ) = @_;
659    croak "must specify keys" unless defined($keys);
660
661    my $key_count = scalar(@$keys);
662    my $values    = $self->get_multi_arrayref($keys);
663    return { map { ( $keys->[$_], $values->[$_] ) } ( 0 .. $key_count - 1 ) };
664}
665
666sub set_multi {
667    my $self = shift;
668    $self->store_multi(@_);
669}
670
671sub store_multi {
672    my ( $self, $key_values, $set_options ) = @_;
673    croak "must specify key_values" unless defined($key_values);
674
675    while ( my ( $key, $value ) = each(%$key_values) ) {
676        $self->set( $key, $value, $set_options );
677    }
678}
679
680sub remove_multi {
681    my ( $self, $keys ) = @_;
682    croak "must specify keys" unless defined($keys);
683
684    foreach my $key (@$keys) {
685        $self->remove($key);
686    }
687}
688
689#
690# KEY TRANSFORMATION
691#
692
693my %escapes;
694for ( 0 .. 255 ) {
695    $escapes{ chr($_) } = sprintf( "+%02x", $_ );
696}
697my $_fail_hi = sub {
698    croak( sprintf "Can't escape multibyte character \\x{%04X}", ord( $_[0] ) );
699};
700
701sub transform_key {
702    my ( $self, $key ) = @_;
703
704    if ( ref($key) ) {
705        $key = $self->key_serializer->serialize($key);
706    }
707    elsif ( Encode::is_utf8($key) && $key =~ /[^\x00-\xFF]/ ) {
708        $key = $self->encode_key($key);
709    }
710    if ( length($key) > $self->max_key_length ) {
711        $key = $self->digest_key($key);
712    }
713
714    return $key;
715}
716
717sub digest_key {
718    my ( $self, $key ) = @_;
719
720    return $self->key_digester->add($key)->hexdigest;
721}
722
723sub encode_key {
724    my ( $self, $key ) = @_;
725
726    return Encode::encode( utf8 => $key );
727}
728
729# These will be called by drivers if necessary, and in testing. By default
730# no escaping/unescaping is necessary.
731#
732sub escape_key   { $_[1] }
733sub unescape_key { $_[1] }
734
735# May be used by drivers to implement escape_key/unescape_key.
736#
737sub escape_for_filename {
738    my ( $self, $key ) = @_;
739
740    $key =~ s/([^A-Za-z0-9_\=\-\~])/$escapes{$1} || $_fail_hi->($1)/ge;
741    return $key;
742}
743
744sub unescape_for_filename {
745    my ( $self, $key ) = @_;
746
747    $key =~ s/\+([0-9A-Fa-f]{2})/chr(hex($1))/eg if defined $key;
748    return $key;
749}
750
751sub is_escaped_for_filename {
752    my ( $self, $key ) = @_;
753
754    return $self->escape_for_filename( $self->unescape_for_filename($key) ) eq
755      $key;
756}
757
758#
759# LOGGING AND ERROR HANDLING
760#
761
762sub _log_get_result {
763    my $self = shift;
764    my $log  = shift;
765    my $msg  = shift;
766    $log->debug( sprintf( "%s: %s", $self->_describe_cache_get(@_), $msg ) );
767}
768
769sub _log_set_result {
770    my $self = shift;
771    my $log  = shift;
772    $log->debug( $self->_describe_cache_set(@_) );
773}
774
775sub _handle_get_error {
776    my $self  = shift;
777    my $error = shift;
778    my $key   = $_[0];
779
780    my $msg =
781      sprintf( "error during %s: %s", $self->_describe_cache_get(@_), $error );
782    $self->_dispatch_error_msg( $msg, $error, $self->on_get_error(), $key );
783}
784
785sub _handle_set_error {
786    my ( $self, $error, $obj ) = @_;
787
788    my $msg =
789      sprintf( "error during %s: %s", $self->_describe_cache_set($obj),
790        $error );
791    $self->_dispatch_error_msg( $msg, $error, $self->on_set_error(),
792        $obj->key );
793}
794
795sub _dispatch_error_msg {
796    my ( $self, $msg, $error, $on_error, $key ) = @_;
797
798    for ($on_error) {
799        ( ref($_) eq 'CODE' ) && do { $_->( $msg, $key, $error ) };
800        /^log$/
801          && do { $log->error($msg) };
802        /^ignore$/ && do { };
803        /^warn$/   && do { carp $msg };
804        /^die$/    && do { croak $msg };
805    }
806}
807
808sub _describe_cache_get {
809    my ( $self, $key, $elapsed_time ) = @_;
810
811    return
812      sprintf( "cache get for namespace='%s', key='%s', cache='%s'"
813          . ( defined($elapsed_time) ? ", time='%dms'" : "" ),
814        $self->namespace, $key, $self->label,
815        defined($elapsed_time) ? int($elapsed_time) : () );
816}
817
818sub _describe_cache_set {
819    my ( $self, $obj, $elapsed_time ) = @_;
820
821    my $expires_str = (
822        ( $obj->expires_at == CHI_Max_Time )
823        ? 'never'
824        : Time::Duration::concise(
825            Time::Duration::duration_exact(
826                $obj->expires_at - $obj->created_at
827            )
828        )
829    );
830
831    return
832      sprintf(
833        "cache set for namespace='%s', key='%s', size=%d, expires='%s', cache='%s'"
834          . ( defined($elapsed_time) ? ", time='%dms'" : "" ),
835        $self->namespace, $obj->key, $obj->size, $expires_str, $self->label,
836        defined($elapsed_time) ? int($elapsed_time) : () );
837
838}
839
8401;
841
842__END__
843
844=pod
845
846=head1 NAME
847
848CHI::Driver - Base class for all CHI drivers
849
850=head1 VERSION
851
852version 0.60
853
854=head1 DESCRIPTION
855
856This is the base class that all CHI drivers inherit from. It provides the
857methods that one calls on $cache handles, such as get() and set().
858
859See L<CHI/METHODS> for documentation on $cache methods, and
860L<CHI::Driver::Development|CHI::Driver::Development> for documentation on
861creating new subclasses of CHI::Driver.
862
863=head1 SEE ALSO
864
865L<CHI|CHI>
866
867=head1 AUTHOR
868
869Jonathan Swartz <swartz@pobox.com>
870
871=head1 COPYRIGHT AND LICENSE
872
873This software is copyright (c) 2012 by Jonathan Swartz.
874
875This is free software; you can redistribute it and/or modify it under
876the same terms as the Perl 5 programming language system itself.
877
878=cut
879