mirror of
https://github.com/python/cpython.git
synced 2025-09-18 14:40:43 +00:00
Issue #28969: Fixed race condition in C implementation of functools.lru_cache.
KeyError could be raised when cached function with full cache was simultaneously called from differen threads with the same uncached arguments.
This commit is contained in:
parent
9b8dcc6b1c
commit
67796521dd
5 changed files with 78 additions and 29 deletions
|
@ -102,6 +102,7 @@ PyAPI_FUNC(int) _PyDict_HasOnlyStringKeys(PyObject *mp);
|
||||||
Py_ssize_t _PyDict_KeysSize(PyDictKeysObject *keys);
|
Py_ssize_t _PyDict_KeysSize(PyDictKeysObject *keys);
|
||||||
Py_ssize_t _PyDict_SizeOf(PyDictObject *);
|
Py_ssize_t _PyDict_SizeOf(PyDictObject *);
|
||||||
PyObject *_PyDict_Pop(PyDictObject *, PyObject *, PyObject *);
|
PyObject *_PyDict_Pop(PyDictObject *, PyObject *, PyObject *);
|
||||||
|
PyObject *_PyDict_Pop_KnownHash(PyDictObject *, PyObject *, Py_hash_t, PyObject *);
|
||||||
PyObject *_PyDict_FromKeys(PyObject *, PyObject *, PyObject *);
|
PyObject *_PyDict_FromKeys(PyObject *, PyObject *, PyObject *);
|
||||||
#define _PyDict_HasSplitTable(d) ((d)->ma_values != NULL)
|
#define _PyDict_HasSplitTable(d) ((d)->ma_values != NULL)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import pickle
|
||||||
from random import choice
|
from random import choice
|
||||||
import sys
|
import sys
|
||||||
from test import support
|
from test import support
|
||||||
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
from weakref import proxy
|
from weakref import proxy
|
||||||
try:
|
try:
|
||||||
|
@ -1364,6 +1365,20 @@ class TestLRU:
|
||||||
pause.reset()
|
pause.reset()
|
||||||
self.assertEqual(f.cache_info(), (0, (i+1)*n, m*n, i+1))
|
self.assertEqual(f.cache_info(), (0, (i+1)*n, m*n, i+1))
|
||||||
|
|
||||||
|
@unittest.skipUnless(threading, 'This test requires threading.')
|
||||||
|
def test_lru_cache_threaded3(self):
|
||||||
|
@self.module.lru_cache(maxsize=2)
|
||||||
|
def f(x):
|
||||||
|
time.sleep(.01)
|
||||||
|
return 3 * x
|
||||||
|
def test(i, x):
|
||||||
|
with self.subTest(thread=i):
|
||||||
|
self.assertEqual(f(x), 3 * x, i)
|
||||||
|
threads = [threading.Thread(target=test, args=(i, v))
|
||||||
|
for i, v in enumerate([1, 2, 2, 3, 2])]
|
||||||
|
with support.start_threads(threads):
|
||||||
|
pass
|
||||||
|
|
||||||
def test_need_for_rlock(self):
|
def test_need_for_rlock(self):
|
||||||
# This will deadlock on an LRU cache that uses a regular lock
|
# This will deadlock on an LRU cache that uses a regular lock
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #28969: Fixed race condition in C implementation of functools.lru_cache.
|
||||||
|
KeyError could be raised when cached function with full cache was
|
||||||
|
simultaneously called from differen threads with the same uncached arguments.
|
||||||
|
|
||||||
- Issue #29142: In urllib.request, suffixes in no_proxy environment variable with
|
- Issue #29142: In urllib.request, suffixes in no_proxy environment variable with
|
||||||
leading dots could match related hostnames again (e.g. .b.c matches a.b.c).
|
leading dots could match related hostnames again (e.g. .b.c matches a.b.c).
|
||||||
Patch by Milan Oberkirch.
|
Patch by Milan Oberkirch.
|
||||||
|
|
|
@ -864,20 +864,33 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
|
||||||
}
|
}
|
||||||
if (self->full && self->root.next != &self->root) {
|
if (self->full && self->root.next != &self->root) {
|
||||||
/* Use the oldest item to store the new key and result. */
|
/* Use the oldest item to store the new key and result. */
|
||||||
PyObject *oldkey, *oldresult;
|
PyObject *oldkey, *oldresult, *popresult;
|
||||||
/* Extricate the oldest item. */
|
/* Extricate the oldest item. */
|
||||||
link = self->root.next;
|
link = self->root.next;
|
||||||
lru_cache_extricate_link(link);
|
lru_cache_extricate_link(link);
|
||||||
/* Remove it from the cache.
|
/* Remove it from the cache.
|
||||||
The cache dict holds one reference to the link,
|
The cache dict holds one reference to the link,
|
||||||
and the linked list holds yet one reference to it. */
|
and the linked list holds yet one reference to it. */
|
||||||
if (_PyDict_DelItem_KnownHash(self->cache, link->key,
|
popresult = _PyDict_Pop_KnownHash((PyDictObject *)self->cache,
|
||||||
link->hash) < 0) {
|
link->key, link->hash,
|
||||||
|
Py_None);
|
||||||
|
if (popresult == Py_None) {
|
||||||
|
/* Getting here means that this same key was added to the
|
||||||
|
cache while the lock was released. Since the link
|
||||||
|
update is already done, we need only return the
|
||||||
|
computed result and update the count of misses. */
|
||||||
|
Py_DECREF(popresult);
|
||||||
|
Py_DECREF(link);
|
||||||
|
Py_DECREF(key);
|
||||||
|
}
|
||||||
|
else if (popresult == NULL) {
|
||||||
lru_cache_append_link(self, link);
|
lru_cache_append_link(self, link);
|
||||||
Py_DECREF(key);
|
Py_DECREF(key);
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
Py_DECREF(popresult);
|
||||||
/* Keep a reference to the old key and old result to
|
/* Keep a reference to the old key and old result to
|
||||||
prevent their ref counts from going to zero during the
|
prevent their ref counts from going to zero during the
|
||||||
update. That will prevent potentially arbitrary object
|
update. That will prevent potentially arbitrary object
|
||||||
|
@ -900,6 +913,7 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds
|
||||||
Py_INCREF(result); /* for return */
|
Py_INCREF(result); /* for return */
|
||||||
Py_DECREF(oldkey);
|
Py_DECREF(oldkey);
|
||||||
Py_DECREF(oldresult);
|
Py_DECREF(oldresult);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Put result in a new link at the front of the queue. */
|
/* Put result in a new link at the front of the queue. */
|
||||||
link = (lru_list_elem *)PyObject_GC_New(lru_list_elem,
|
link = (lru_list_elem *)PyObject_GC_New(lru_list_elem,
|
||||||
|
|
|
@ -1475,9 +1475,8 @@ _PyDict_Next(PyObject *op, Py_ssize_t *ppos, PyObject **pkey,
|
||||||
|
|
||||||
/* Internal version of dict.pop(). */
|
/* Internal version of dict.pop(). */
|
||||||
PyObject *
|
PyObject *
|
||||||
_PyDict_Pop(PyDictObject *mp, PyObject *key, PyObject *deflt)
|
_PyDict_Pop_KnownHash(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *deflt)
|
||||||
{
|
{
|
||||||
Py_hash_t hash;
|
|
||||||
PyObject *old_value, *old_key;
|
PyObject *old_value, *old_key;
|
||||||
PyDictKeyEntry *ep;
|
PyDictKeyEntry *ep;
|
||||||
PyObject **value_addr;
|
PyObject **value_addr;
|
||||||
|
@ -1490,12 +1489,6 @@ _PyDict_Pop(PyDictObject *mp, PyObject *key, PyObject *deflt)
|
||||||
_PyErr_SetKeyError(key);
|
_PyErr_SetKeyError(key);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (!PyUnicode_CheckExact(key) ||
|
|
||||||
(hash = ((PyASCIIObject *) key)->hash) == -1) {
|
|
||||||
hash = PyObject_Hash(key);
|
|
||||||
if (hash == -1)
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
ep = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr);
|
ep = (mp->ma_keys->dk_lookup)(mp, key, hash, &value_addr);
|
||||||
if (ep == NULL)
|
if (ep == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -1520,6 +1513,28 @@ _PyDict_Pop(PyDictObject *mp, PyObject *key, PyObject *deflt)
|
||||||
return old_value;
|
return old_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
_PyDict_Pop(PyDictObject *mp, PyObject *key, PyObject *deflt)
|
||||||
|
{
|
||||||
|
Py_hash_t hash;
|
||||||
|
|
||||||
|
if (mp->ma_used == 0) {
|
||||||
|
if (deflt) {
|
||||||
|
Py_INCREF(deflt);
|
||||||
|
return deflt;
|
||||||
|
}
|
||||||
|
_PyErr_SetKeyError(key);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!PyUnicode_CheckExact(key) ||
|
||||||
|
(hash = ((PyASCIIObject *) key)->hash) == -1) {
|
||||||
|
hash = PyObject_Hash(key);
|
||||||
|
if (hash == -1)
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return _PyDict_Pop_KnownHash(mp, key, hash, deflt);
|
||||||
|
}
|
||||||
|
|
||||||
/* Internal version of dict.from_keys(). It is subclass-friendly. */
|
/* Internal version of dict.from_keys(). It is subclass-friendly. */
|
||||||
PyObject *
|
PyObject *
|
||||||
_PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
|
_PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue