Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
D
Django-Redis-Cache
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Shared
Django-Redis-Cache
Commits
94eab537
Commit
94eab537
authored
Nov 22, 2018
by
Sean Bleier
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add docstring and test for get_or_set.
parent
18a58d06
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
84 additions
and
4 deletions
+84
-4
base.py
redis_cache/backends/base.py
+23
-4
base_tests.py
tests/testapp/tests/base_tests.py
+61
-0
No files found.
redis_cache/backends/base.py
View file @
94eab537
...
...
@@ -406,13 +406,28 @@ class BaseRedisCache(BaseCache):
)
@
get_client
(
write
=
True
)
def
get_or_set
(
self
,
client
,
key
,
func
,
timeout
=
DEFAULT_TIMEOUT
,
lock_timeout
=
None
,
stale_cache_timeout
=
0
):
def
get_or_set
(
self
,
client
,
key
,
func
,
timeout
=
DEFAULT_TIMEOUT
,
lock_timeout
=
None
,
stale_cache_timeout
=
None
):
"""Get a value from the cache or call `func` to set it and return it.
This implementation is slightly more advanced that Django's. It provides thundering herd
protection
that
prevents multiple threads/processes from calling the value-generating
protection
, which
prevents multiple threads/processes from calling the value-generating
function too much.
There are three timeouts you can specify:
`timeout`: Time in seconds that value at `key` is considered fresh.
`lock_timeout`: Time in seconds that the lock will stay active and prevent other threads or
processes from acquiring the lock.
`stale_cache_timeout`: Time in seconds that the stale cache will remain after the key has
expired. If `None` is specified, the stale value will remain indefinitely.
"""
if
not
callable
(
func
):
raise
Exception
(
"Must pass in a callable"
)
...
...
@@ -422,6 +437,7 @@ class BaseRedisCache(BaseCache):
is_fresh
=
self
.
_get
(
client
,
fresh_key
)
value
=
self
.
_get
(
client
,
key
)
if
is_fresh
:
return
value
...
...
@@ -436,9 +452,12 @@ class BaseRedisCache(BaseCache):
except
Exception
:
raise
else
:
key_timeout
=
(
None
if
stale_cache_timeout
is
None
else
timeout
+
stale_cache_timeout
)
pipeline
=
client
.
pipeline
()
pipeline
.
set
(
key
,
self
.
prep_value
(
value
),
timeout
+
stale_cache
_timeout
)
pipeline
.
set
(
fresh_key
,
self
.
prep_value
(
1
)
,
timeout
)
pipeline
.
set
(
key
,
self
.
prep_value
(
value
),
key
_timeout
)
pipeline
.
set
(
fresh_key
,
1
,
timeout
)
pipeline
.
execute
()
finally
:
lock
.
release
()
...
...
tests/testapp/tests/base_tests.py
View file @
94eab537
...
...
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
from
hashlib
import
sha1
import
os
import
subprocess
import
threading
import
time
...
...
@@ -510,6 +511,66 @@ class BaseRedisTestCase(SetupMixin):
self
.
assertEqual
(
expensive_function
.
num_calls
,
2
)
self
.
assertEqual
(
value
,
42
)
def
test_get_or_set_serving_from_stale_value
(
self
):
def
expensive_function
(
x
):
time
.
sleep
(
.5
)
expensive_function
.
num_calls
+=
1
return
x
expensive_function
.
num_calls
=
0
self
.
assertEqual
(
expensive_function
.
num_calls
,
0
)
results
=
{}
def
thread_worker
(
thread_id
,
return_value
,
timeout
,
lock_timeout
,
stale_cache_timeout
):
value
=
self
.
cache
.
get_or_set
(
'key'
,
lambda
:
expensive_function
(
return_value
),
timeout
,
lock_timeout
,
stale_cache_timeout
)
results
[
thread_id
]
=
value
thread_0
=
threading
.
Thread
(
target
=
thread_worker
,
args
=
(
0
,
'a'
,
1
,
None
,
1
))
thread_1
=
threading
.
Thread
(
target
=
thread_worker
,
args
=
(
1
,
'b'
,
1
,
None
,
1
))
thread_2
=
threading
.
Thread
(
target
=
thread_worker
,
args
=
(
2
,
'c'
,
1
,
None
,
1
))
thread_3
=
threading
.
Thread
(
target
=
thread_worker
,
args
=
(
3
,
'd'
,
1
,
None
,
1
))
thread_4
=
threading
.
Thread
(
target
=
thread_worker
,
args
=
(
4
,
'e'
,
1
,
None
,
1
))
# First thread should complete and return its value
thread_0
.
start
()
# t = 0, valid from t = .5 - 1.5, stale from t = 1.5 - 2.5
# Second thread will start while the first thread is still working and return None.
time
.
sleep
(
.25
)
# t = .25
thread_1
.
start
()
# Third thread will start after the first value is computed, but before it expires.
# its value.
time
.
sleep
(
.5
)
# t = .75
thread_2
.
start
()
# Fourth thread will start after the first value has expired and will re-compute its value.
# valid from t = 2.25 - 3.25, stale from t = 3.75 - 4.75.
time
.
sleep
(
1
)
# t = 1.75
thread_3
.
start
()
# Fifth thread will start after the fourth thread has started to compute its value, but
# before the first thread's stale cache has expired.
time
.
sleep
(
.25
)
# t = 2
thread_4
.
start
()
thread_0
.
join
()
thread_1
.
join
()
thread_2
.
join
()
thread_3
.
join
()
thread_4
.
join
()
self
.
assertEqual
(
results
,
{
0
:
'a'
,
1
:
None
,
2
:
'a'
,
3
:
'd'
,
4
:
'a'
})
def
assertMaxConnection
(
self
,
cache
,
max_num
):
for
client
in
cache
.
clients
.
values
():
self
.
assertLessEqual
(
client
.
connection_pool
.
_created_connections
,
max_num
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment