line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojo::Redis::Cache; |
2
|
18
|
|
|
18
|
|
145
|
use Mojo::Base -base; |
|
18
|
|
|
|
|
45
|
|
|
18
|
|
|
|
|
195
|
|
3
|
|
|
|
|
|
|
|
4
|
18
|
|
|
18
|
|
3440
|
use Mojo::JSON; |
|
18
|
|
|
|
|
43
|
|
|
18
|
|
|
|
|
787
|
|
5
|
18
|
|
|
18
|
|
103
|
use Scalar::Util 'blessed'; |
|
18
|
|
|
|
|
44
|
|
|
18
|
|
|
|
|
776
|
|
6
|
18
|
|
|
18
|
|
13605
|
use Storable (); |
|
18
|
|
|
|
|
64408
|
|
|
18
|
|
|
|
|
523
|
|
7
|
18
|
|
|
18
|
|
149
|
use Time::HiRes (); |
|
18
|
|
|
|
|
39
|
|
|
18
|
|
|
|
|
562
|
|
8
|
|
|
|
|
|
|
|
9
|
18
|
|
|
18
|
|
115
|
use constant OFFLINE => $ENV{MOJO_REDIS_CACHE_OFFLINE}; |
|
18
|
|
|
|
|
41
|
|
|
18
|
|
|
|
|
24404
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
has connection => sub { |
12
|
|
|
|
|
|
|
OFFLINE ? shift->_offline_connection : shift->redis->_dequeue->encoding(undef); |
13
|
|
|
|
|
|
|
}; |
14
|
|
|
|
|
|
|
has deserialize => sub { \&Storable::thaw }; |
15
|
|
|
|
|
|
|
has default_expire => 600; |
16
|
|
|
|
|
|
|
has namespace => 'cache:mojo:redis'; |
17
|
|
|
|
|
|
|
has refresh => 0; |
18
|
|
|
|
|
|
|
has redis => sub { Carp::confess('redis is required in constructor') }; |
19
|
|
|
|
|
|
|
has serialize => sub { \&Storable::freeze }; |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
sub compute_p { |
22
|
5
|
|
|
5
|
1
|
1430
|
my $compute = pop; |
23
|
5
|
|
|
|
|
6
|
my $self = shift; |
24
|
5
|
|
|
|
|
13
|
my $key = join ':', $self->namespace, shift; |
25
|
5
|
|
66
|
|
|
42
|
my $expire = shift || $self->default_expire; |
26
|
|
|
|
|
|
|
|
27
|
5
|
100
|
|
|
|
17
|
my $p = $self->refresh ? Mojo::Promise->new->resolve : $self->connection->write_p(GET => $key); |
28
|
|
|
|
|
|
|
return $p->then(sub { |
29
|
5
|
100
|
|
5
|
|
2198
|
my $data = $_[0] ? $self->deserialize->(shift) : undef; |
30
|
5
|
50
|
|
|
|
42
|
return $self->_maybe_compute_p($key, $expire, $compute, $data) if $expire < 0; |
31
|
5
|
100
|
|
|
|
18
|
return $self->_compute_p($key, $expire, $compute) unless $data; |
32
|
1
|
|
|
|
|
4
|
return $data->[0]; |
33
|
5
|
|
|
|
|
747
|
}); |
34
|
|
|
|
|
|
|
} |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
sub memoize_p { |
37
|
4
|
|
|
4
|
1
|
5297
|
my ($self, $obj, $method) = (shift, shift, shift); |
38
|
4
|
50
|
|
|
|
16
|
my $args = ref $_[0] eq 'ARRAY' ? shift : []; |
39
|
4
|
|
33
|
|
|
22
|
my $expire = shift || $self->default_expire; |
40
|
4
|
|
33
|
|
|
51
|
my $key = join ':', '@M' => (ref($obj) || $obj), $method, Mojo::JSON::encode_json($args); |
41
|
|
|
|
|
|
|
|
42
|
4
|
|
|
3
|
|
292
|
return $self->compute_p($key, $expire, sub { $obj->$method(@$args) }); |
|
3
|
|
|
|
|
17
|
|
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub _compute_p { |
46
|
4
|
|
|
4
|
|
10
|
my ($self, $key, $expire, $compute) = @_; |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
my $set = sub { |
49
|
3
|
|
|
3
|
|
6
|
my $data = shift; |
50
|
|
|
|
|
|
|
my @set |
51
|
3
|
50
|
|
|
|
14
|
= $expire < 0 |
52
|
|
|
|
|
|
|
? $self->serialize->([$data, _time() + -$expire]) |
53
|
|
|
|
|
|
|
: ($self->serialize->([$data]), PX => 1000 * $expire); |
54
|
3
|
|
|
|
|
256
|
$self->connection->write_p(SET => $key => @set)->then(sub {$data}); |
|
3
|
|
|
|
|
939
|
|
55
|
4
|
|
|
|
|
18
|
}; |
56
|
|
|
|
|
|
|
|
57
|
4
|
|
|
|
|
10
|
my $data = $compute->(); |
58
|
3
|
50
|
33
|
0
|
|
24
|
return (blessed $data and $data->can('then')) ? $data->then(sub { $set->(@_) }) : $set->($data); |
|
0
|
|
|
|
|
0
|
|
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
sub _maybe_compute_p { |
62
|
0
|
|
|
0
|
|
0
|
my ($self, $key, $expire, $compute, $data) = @_; |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# Nothing in cache |
65
|
0
|
0
|
|
0
|
|
0
|
return $self->_compute_p($key => $expire, $compute)->then(sub { ($_[0], {computed => 1}) }) unless $data; |
|
0
|
|
|
|
|
0
|
|
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# No need to refresh cache |
68
|
0
|
0
|
0
|
|
|
0
|
return ($data->[0], {expired => 0}) if $data->[1] and _time() < $data->[1]; |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# Try to refresh, but use old data on error |
71
|
0
|
|
|
|
|
0
|
my $p = Mojo::Promise->new; |
72
|
|
|
|
|
|
|
eval { |
73
|
|
|
|
|
|
|
$self->_compute_p($key => $expire, $compute)->then( |
74
|
0
|
|
|
0
|
|
0
|
sub { $p->resolve(shift, {computed => 1, expired => 1}) }, |
75
|
0
|
|
|
0
|
|
0
|
sub { $p->resolve($data->[0], {error => $_[0], expired => 1}) }, |
76
|
0
|
|
|
|
|
0
|
); |
77
|
0
|
0
|
|
|
|
0
|
} or do { |
78
|
0
|
|
|
|
|
0
|
$p->resolve($data->[0], {error => $@, expired => 1}); |
79
|
|
|
|
|
|
|
}; |
80
|
|
|
|
|
|
|
|
81
|
0
|
|
|
|
|
0
|
return $p; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub _offline_connection { |
85
|
1
|
50
|
100
|
1
|
|
9
|
state $c = eval <<'HERE' or die $@; |
|
1
|
50
|
100
|
1
|
|
2
|
|
|
1
|
100
|
|
6
|
|
6
|
|
|
1
|
100
|
|
|
|
113
|
|
|
6
|
100
|
|
|
|
588
|
|
|
6
|
|
|
|
|
19
|
|
|
3
|
|
|
|
|
11
|
|
|
3
|
|
|
|
|
591
|
|
|
3
|
|
|
|
|
14
|
|
|
3
|
|
|
|
|
968
|
|
|
3
|
|
|
|
|
32
|
|
|
3
|
|
|
|
|
192
|
|
86
|
|
|
|
|
|
|
package Mojo::Redis::Connection::Offline; |
87
|
|
|
|
|
|
|
use Mojo::Base 'Mojo::Redis::Connection'; |
88
|
|
|
|
|
|
|
our $STORE = {}; # Meant for internal use only |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub write_p { |
91
|
|
|
|
|
|
|
my ($conn, $op, $key) = (shift, shift, shift); |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
if ($op eq 'SET') { |
94
|
|
|
|
|
|
|
$STORE->{$conn->url}{$key} = [$_[0], defined $_[2] ? $_[2] + Mojo::Redis::Cache::_time() * 1000 : undef]; |
95
|
|
|
|
|
|
|
return Mojo::Promise->new->resolve('OK'); |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
else { |
98
|
|
|
|
|
|
|
my $val = $STORE->{$conn->url}{$key} || []; |
99
|
|
|
|
|
|
|
my $expired = $val->[1] && $val->[1] < Mojo::Redis::Cache::_time() * 1000; |
100
|
|
|
|
|
|
|
delete $STORE->{$conn->url}{$key} if $expired; |
101
|
|
|
|
|
|
|
return Mojo::Promise->new->resolve($expired ? undef : $val->[0]); |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
'Mojo::Redis::Connection::Offline'; |
106
|
|
|
|
|
|
|
HERE |
107
|
1
|
|
|
|
|
10
|
my $redis = shift->redis; |
108
|
1
|
|
|
|
|
25
|
return $c->new(url => $redis->url); |
109
|
|
|
|
|
|
|
} |
110
|
|
|
|
|
|
|
|
111
|
3
|
|
|
3
|
|
38
|
sub _time { Time::HiRes::time() } |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
1; |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=encoding utf8 |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head1 NAME |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Mojo::Redis::Cache - Simple cache interface using Redis |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head1 SYNOPSIS |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
use Mojo::Redis; |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
my $redis = Mojo::Redis->new; |
126
|
|
|
|
|
|
|
my $cache = $redis->cache; |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# Cache and expire the data after 60.7 seconds |
129
|
|
|
|
|
|
|
$cache->compute_p("some:key", 60.7, sub { |
130
|
|
|
|
|
|
|
my $p = Mojo::Promise->new; |
131
|
|
|
|
|
|
|
Mojo::IOLoop->timer(0.1 => sub { $p->resolve("some data") }); |
132
|
|
|
|
|
|
|
return $p; |
133
|
|
|
|
|
|
|
})->then(sub { |
134
|
|
|
|
|
|
|
my $some_key = shift; |
135
|
|
|
|
|
|
|
}); |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Cache and expire the data after default_expire() seconds |
138
|
|
|
|
|
|
|
$cache->compute_p("some:key", sub { |
139
|
|
|
|
|
|
|
return {some => "data"}; |
140
|
|
|
|
|
|
|
})->then(sub { |
141
|
|
|
|
|
|
|
my $some_key = shift; |
142
|
|
|
|
|
|
|
}); |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# Call $obj->get_some_slow_data() and cache the return value |
145
|
|
|
|
|
|
|
$cache->memoize_p($obj, "get_some_slow_data")->then(sub { |
146
|
|
|
|
|
|
|
my $data = shift; |
147
|
|
|
|
|
|
|
}); |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# Call $obj->get_some_data_by_id({id => 42}) and cache the return value |
150
|
|
|
|
|
|
|
$cache->memoize_p($obj, "get_some_data_by_id", [{id => 42}])->then(sub { |
151
|
|
|
|
|
|
|
my $data = shift; |
152
|
|
|
|
|
|
|
}); |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
See L |
155
|
|
|
|
|
|
|
for example L application. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=head1 DESCRIPTION |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
L provides a simple interface for caching data in the |
160
|
|
|
|
|
|
|
Redis database. There is no "check if exists", "get" or "set" methods in this |
161
|
|
|
|
|
|
|
class. Instead, both L and L will fetch the value |
162
|
|
|
|
|
|
|
from Redis, if the given compute function / method has been called once, and |
163
|
|
|
|
|
|
|
the cached data is not expired. |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
If you need to check if the value exists, then you can manually look up the |
166
|
|
|
|
|
|
|
the key using L. |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
=head1 ENVIRONMENT VARIABLES |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=head2 MOJO_REDIS_CACHE_OFFLINE |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Set C to 1 if you want to use this cache without a |
173
|
|
|
|
|
|
|
real Redis backend. This can be useful in unit tests. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=head2 connection |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
$conn = $cache->connection; |
180
|
|
|
|
|
|
|
$cache = $cache->connection(Mojo::Redis::Connection->new); |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Holds a L object. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head2 default_expire |
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
$num = $cache->default_expire; |
187
|
|
|
|
|
|
|
$cache = $cache->default_expire(600); |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
Holds the default expire time for cached data. |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
=head2 deserialize |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
$cb = $cache->deserialize; |
194
|
|
|
|
|
|
|
$cache = $cache->deserialize(\&Mojo::JSON::decode_json); |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
Holds a callback used to deserialize data from Redis. |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
=head2 namespace |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
$str = $cache->namespace; |
201
|
|
|
|
|
|
|
$cache = $cache->namespace("cache:mojo:redis"); |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
Prefix for the cache key. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=head2 redis |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
$conn = $cache->redis; |
208
|
|
|
|
|
|
|
$cache = $cache->redis(Mojo::Redis->new); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
Holds a L object used to create the connection to talk with Redis. |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head2 refresh |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
$bool = $cache->refresh; |
215
|
|
|
|
|
|
|
$cache = $cache->refresh(1); |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
Will force the cache to be computed again if set to a true value. |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head2 serialize |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
$cb = $cache->serialize; |
222
|
|
|
|
|
|
|
$cache = $cache->serialize(\&Mojo::JSON::encode_json); |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
Holds a callback used to serialize before storing the data in Redis. |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
=head1 METHODS |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=head2 compute_p |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
$promise = $cache->compute_p($key => $expire => $compute_function); |
231
|
|
|
|
|
|
|
$promise = $cache->compute_p($key => $expire => sub { return "data" }); |
232
|
|
|
|
|
|
|
$promise = $cache->compute_p($key => $expire => sub { return Mojo::Promise->new }); |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
This method will store the return value from the C<$compute_function> the |
235
|
|
|
|
|
|
|
first time it is called and pass the same value to L. |
236
|
|
|
|
|
|
|
C<$compute_function> will not be called the next time, if the C<$key> is |
237
|
|
|
|
|
|
|
still present in Redis, but instead the cached value will be passed on to |
238
|
|
|
|
|
|
|
L. |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
C<$key> will be prefixed by L resulting in "namespace:some-key". |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
C<$expire> is the number of seconds before the cache should expire, and will |
243
|
|
|
|
|
|
|
default to L unless passed in. The last argument is a |
244
|
|
|
|
|
|
|
callback used to calculate cached value. |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
C<$expire> can also be a negative number. This will result in serving old cache |
247
|
|
|
|
|
|
|
in the case where the C<$compute_function> fails. An example usecase would be |
248
|
|
|
|
|
|
|
if you are fetching Twitter updates for your website, but instead of throwing |
249
|
|
|
|
|
|
|
an exception if Twitter is down, you will serve old data instead. Note that the |
250
|
|
|
|
|
|
|
fulfilled promise will get two variables passed in: |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
$promise->then(sub { my ($data, $info) = @_ }); |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
C<$info> is a hash and can have these keys: |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
=over 2 |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
=item * computed |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
Will be true if the C<$compute_function> was called successfully and C<$data> |
261
|
|
|
|
|
|
|
is fresh. |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
=item * expired |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
Will be true if C<$data> is expired. If this key is present and false, it will |
266
|
|
|
|
|
|
|
indicate that the C<$data> is within the expiration period. The C key |
267
|
|
|
|
|
|
|
can be found together with both L and L. |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=item * error |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
Will hold a string if the C<$compute_function> failed. |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=back |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
Negative C<$expire> is currently EXPERIMENTAL, but unlikely to go away. |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
=head2 memoize_p |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
$promise = $cache->memoize_p($obj, $method_name, \@args, $expire); |
280
|
|
|
|
|
|
|
$promise = $cache->memoize_p($class, $method_name, \@args, $expire); |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
L behaves the same way as L, but has a convenient |
283
|
|
|
|
|
|
|
interface for calling methods on an object. One of the benefits is that you |
284
|
|
|
|
|
|
|
do not have to come up with your own cache key. This method is pretty much |
285
|
|
|
|
|
|
|
the same as: |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
$promise = $cache->compute_p( |
288
|
|
|
|
|
|
|
join(":", $cache->namespace, "@M", ref($obj), $method_name, serialize(\@args)), |
289
|
|
|
|
|
|
|
$expire, |
290
|
|
|
|
|
|
|
sub { return $obj->$method_name(@args) } |
291
|
|
|
|
|
|
|
); |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
See L regarding C<$expire>. |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=head1 SEE ALSO |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
L |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=cut |