Skip to content

Commit 948f1bf

Browse files
eevelweezeljacobtylerwalls
authored andcommitted
Fixed #36590 -- Made async cache methods use specialized sync versions if available.
Thanks Simon Charette, Sarah Boyce, and Jacob Walls for reviews.
1 parent 7a2f35b commit 948f1bf

2 files changed

Lines changed: 114 additions & 2 deletions

File tree

django/core/cache/backends/base.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ def get_many(self, keys, version=None):
208208

209209
async def aget_many(self, keys, version=None):
210210
"""See get_many()."""
211+
if self.get_many.__func__ is not BaseCache.get_many:
212+
return await sync_to_async(self.get_many, thread_sensitive=True)(
213+
keys, version=version
214+
)
211215
d = {}
212216
for k in keys:
213217
val = await self.aget(k, self._missing_key, version=version)
@@ -236,6 +240,10 @@ def get_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
236240

237241
async def aget_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
238242
"""See get_or_set()."""
243+
if self.get_or_set.__func__ is not BaseCache.get_or_set:
244+
return await sync_to_async(self.get_or_set, thread_sensitive=True)(
245+
key, default, timeout=timeout, version=version
246+
)
239247
val = await self.aget(key, self._missing_key, version=version)
240248
if val is self._missing_key:
241249
if callable(default):
@@ -255,6 +263,10 @@ def has_key(self, key, version=None):
255263
)
256264

257265
async def ahas_key(self, key, version=None):
266+
if self.has_key.__func__ is not BaseCache.has_key:
267+
return await sync_to_async(self.has_key, thread_sensitive=True)(
268+
key, version=version
269+
)
258270
return (
259271
await self.aget(key, self._missing_key, version=version)
260272
is not self._missing_key
@@ -274,6 +286,10 @@ def incr(self, key, delta=1, version=None):
274286

275287
async def aincr(self, key, delta=1, version=None):
276288
"""See incr()."""
289+
if self.incr.__func__ is not BaseCache.incr:
290+
return await sync_to_async(self.incr, thread_sensitive=True)(
291+
key, delta=delta, version=version
292+
)
277293
value = await self.aget(key, self._missing_key, version=version)
278294
if value is self._missing_key:
279295
raise ValueError("Key '%s' not found" % key)
@@ -317,6 +333,10 @@ def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
317333
return []
318334

319335
async def aset_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
336+
if self.set_many.__func__ is not BaseCache.set_many:
337+
return await sync_to_async(self.set_many, thread_sensitive=True)(
338+
data, timeout=timeout, version=version
339+
)
320340
for key, value in data.items():
321341
await self.aset(key, value, timeout=timeout, version=version)
322342
return []
@@ -331,6 +351,10 @@ def delete_many(self, keys, version=None):
331351
self.delete(key, version=version)
332352

333353
async def adelete_many(self, keys, version=None):
354+
if self.delete_many.__func__ is not BaseCache.delete_many:
355+
return await sync_to_async(self.delete_many, thread_sensitive=True)(
356+
keys, version=version
357+
)
334358
for key in keys:
335359
await self.adelete(key, version=version)
336360

@@ -361,6 +385,10 @@ def incr_version(self, key, delta=1, version=None):
361385

362386
async def aincr_version(self, key, delta=1, version=None):
363387
"""See incr_version()."""
388+
if self.incr_version.__func__ is not BaseCache.incr_version:
389+
return await sync_to_async(self.incr_version, thread_sensitive=True)(
390+
key, delta=delta, version=version
391+
)
364392
if version is None:
365393
version = self.version
366394

@@ -387,7 +415,7 @@ def close(self, **kwargs):
387415
pass
388416

389417
async def aclose(self, **kwargs):
390-
pass
418+
return await sync_to_async(self.close, thread_sensitive=True)(**kwargs)
391419

392420

393421
memcached_error_chars_re = _lazy_re_compile(r"[\x00-\x20\x7f]")

tests/cache/tests.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
cache,
2626
caches,
2727
)
28-
from django.core.cache.backends.base import InvalidCacheBackendError
28+
from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
2929
from django.core.cache.backends.redis import RedisCacheClient
3030
from django.core.cache.utils import make_template_fragment_key
3131
from django.db import close_old_connections, connection, connections
@@ -1165,6 +1165,90 @@ def test_get_or_set_racing(self):
11651165
cache_add.return_value = False
11661166
self.assertEqual(cache.get_or_set("key", "default"), "default")
11671167

1168+
async def test_get_many_async_uses_specialized_implementation(self):
1169+
if (
1170+
cache.get_many.__func__ is not BaseCache.get_many
1171+
and cache.aget_many.__func__ is BaseCache.aget_many
1172+
):
1173+
with mock.patch.object(cache, "get_many") as mocked:
1174+
mocked.__func__ = lambda x: x
1175+
await cache.aget_many({})
1176+
mocked.assert_called_once()
1177+
1178+
async def test_set_many_async_uses_specialized_implementation(self):
1179+
if (
1180+
cache.set_many.__func__ is not BaseCache.set_many
1181+
and cache.aset_many.__func__ is BaseCache.aset_many
1182+
):
1183+
with mock.patch.object(cache, "set_many") as mocked:
1184+
mocked.__func__ = lambda x: x
1185+
await cache.aset_many({})
1186+
mocked.assert_called_once()
1187+
1188+
async def test_delete_many_async_uses_specialized_implementation(self):
1189+
if (
1190+
cache.delete_many.__func__ is not BaseCache.delete_many
1191+
and cache.adelete_many.__func__ is BaseCache.adelete_many
1192+
):
1193+
with mock.patch.object(cache, "delete_many") as mocked:
1194+
mocked.__func__ = lambda x: x
1195+
await cache.adelete_many({})
1196+
mocked.assert_called_once()
1197+
1198+
async def test_get_or_set_async_uses_specialized_implementation(self):
1199+
await cache.aset("key", "value")
1200+
if (
1201+
cache.get_or_set.__func__ is not BaseCache.get_or_set
1202+
and cache.aget_or_set.__func__ is BaseCache.aget_or_set
1203+
):
1204+
with mock.patch.object(cache, "get_or_set") as mocked:
1205+
mocked.__func__ = lambda x: x
1206+
await cache.aget_or_set("key")
1207+
mocked.assert_called_once()
1208+
1209+
async def test_has_key_async_uses_specialized_implementation(self):
1210+
await cache.aset("key", "value")
1211+
if (
1212+
cache.has_key.__func__ is not BaseCache.has_key
1213+
and cache.ahas_key.__func__ is BaseCache.ahas_key
1214+
):
1215+
with mock.patch.object(cache, "has_key") as mocked:
1216+
mocked.__func__ = lambda x: x
1217+
await cache.ahas_key("key")
1218+
mocked.assert_called_once()
1219+
1220+
async def test_incr_async_uses_specialized_implementation(self):
1221+
await cache.aset("key", 1)
1222+
if (
1223+
cache.incr.__func__ is not BaseCache.incr
1224+
and cache.aincr.__func__ is BaseCache.aincr
1225+
):
1226+
with mock.patch.object(cache, "incr") as mocked:
1227+
mocked.__func__ = lambda x: x
1228+
await cache.aincr("key")
1229+
mocked.assert_called_once()
1230+
1231+
async def test_incr_version_async_uses_specialized_implementation(self):
1232+
await cache.aset("key", "value", version=1)
1233+
if (
1234+
cache.incr_version.__func__ is not BaseCache.incr_version
1235+
and cache.aincr_version.__func__ is BaseCache.aincr_version
1236+
):
1237+
with mock.patch.object(cache, "incr_version") as mocked:
1238+
mocked.__func__ = lambda x: x
1239+
await cache.aincr_version("key")
1240+
mocked.assert_called_once()
1241+
1242+
async def test_close_async_uses_specialized_implementation(self):
1243+
if (
1244+
cache.close.__func__ is not BaseCache.close
1245+
and cache.aclose.__func__ is BaseCache.aclose
1246+
):
1247+
with mock.patch.object(cache, "close") as mocked:
1248+
mocked.__func__ = lambda x: x
1249+
await cache.aclose()
1250+
mocked.assert_called_once()
1251+
11681252

11691253
@override_settings(
11701254
CACHES=caches_setting_for_tests(

0 commit comments

Comments
 (0)