From d60c12cf18bbebe5e98c938dd1ba0adee2644aa9 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Wed, 26 Mar 2025 06:52:20 -0400 Subject: [PATCH 01/17] gh-131757: allow lru_cache functions to execute concurrently --- Modules/_functoolsmodule.c | 82 ++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 25 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index f91fa9a7327a3d..86e5053c451495 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1195,14 +1195,13 @@ infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd Py_DECREF(key); return NULL; } - result = _PyDict_GetItem_KnownHash(self->cache, key, hash); - if (result) { - Py_INCREF(result); + int res = _PyDict_GetItemRef_KnownHash((PyDictObject *)self->cache, key, hash, &result); + if (res > 0) { self->hits++; Py_DECREF(key); return result; } - if (PyErr_Occurred()) { + if (res < 0) { Py_DECREF(key); return NULL; } @@ -1281,37 +1280,45 @@ lru_cache_prepend_link(lru_cache_object *self, lru_list_elem *link) so that we know the cache is a consistent state. */ -static PyObject * -bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds) +static int +bounded_lru_cache_wrapper_pre_call_lock_held(lru_cache_object *self, PyObject *args, PyObject *kwds, + PyObject **result, PyObject **key, Py_hash_t *hash) { lru_list_elem *link; - PyObject *key, *result, *testresult; - Py_hash_t hash; - key = lru_cache_make_key(self->kwd_mark, args, kwds, self->typed); - if (!key) - return NULL; - hash = PyObject_Hash(key); - if (hash == -1) { - Py_DECREF(key); - return NULL; + PyObject *_key = *key = lru_cache_make_key(self->kwd_mark, args, kwds, self->typed); + if (!_key) + return -1; + Py_hash_t _hash = *hash = PyObject_Hash(_key); + if (_hash == -1) { + Py_DECREF(_key); + return -1; } - link = (lru_list_elem *)_PyDict_GetItem_KnownHash(self->cache, key, hash); + link = (lru_list_elem *)_PyDict_GetItem_KnownHash(self->cache, _key, _hash); if (link != NULL) { lru_cache_extract_link(link); lru_cache_append_link(self, link); - result = link->result; + *result = link->result; self->hits++; - Py_INCREF(result); - Py_DECREF(key); - return result; + Py_INCREF(link->result); + Py_DECREF(_key); + return 1; } if (PyErr_Occurred()) { - Py_DECREF(key); - return NULL; + Py_DECREF(_key); + return -1; } self->misses++; - result = PyObject_Call(self->func, args, kwds); + return 0; +} + +PyObject * +bounded_lru_cache_wrapper_post_call_lock_held(lru_cache_object *self, + PyObject *result, PyObject *key, Py_hash_t hash) +{ + lru_list_elem *link; + PyObject *testresult; + if (!result) { Py_DECREF(key); return NULL; @@ -1447,6 +1454,33 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds return result; } +static PyObject * +bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds) +{ + PyObject *key, *result; + Py_hash_t hash; + int res; + + Py_BEGIN_CRITICAL_SECTION(self); + res = bounded_lru_cache_wrapper_pre_call_lock_held(self, args, kwds, &result, &key, &hash); + Py_END_CRITICAL_SECTION(); + + if (res < 0) { + return NULL; + } + if (res > 0) { + return result; + } + + result = PyObject_Call(self->func, args, kwds); + + Py_BEGIN_CRITICAL_SECTION(self); + result = bounded_lru_cache_wrapper_post_call_lock_held(self, result, key, hash); + Py_END_CRITICAL_SECTION(); + + return result; +} + static PyObject * lru_cache_new(PyTypeObject *type, PyObject *args, PyObject *kw) { @@ -1579,9 +1613,7 @@ lru_cache_call(PyObject *op, PyObject *args, PyObject *kwds) { lru_cache_object *self = lru_cache_object_CAST(op); PyObject *result; - Py_BEGIN_CRITICAL_SECTION(self); result = self->wrapper(self, args, kwds); - Py_END_CRITICAL_SECTION(); return result; } From 2dfcea3dbe2cc44e862dcf42146218fcdc2391f1 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:56:24 +0000 Subject: [PATCH 02/17] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst diff --git a/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst b/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst new file mode 100644 index 00000000000000..f4e8647961dc48 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst @@ -0,0 +1 @@ +Make ``functools.lru_cache`` call the cached function unlocked to allow concurrency. From c700d1ed6a7c28c6c13c10e40b2fb1a70586c84f Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Wed, 26 Mar 2025 07:01:58 -0400 Subject: [PATCH 03/17] fix smelly symbols --- Modules/_functoolsmodule.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 86e5053c451495..b78cf53a656aa9 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1286,26 +1286,26 @@ bounded_lru_cache_wrapper_pre_call_lock_held(lru_cache_object *self, PyObject *a { lru_list_elem *link; - PyObject *_key = *key = lru_cache_make_key(self->kwd_mark, args, kwds, self->typed); - if (!_key) + PyObject *key_ = *key = lru_cache_make_key(self->kwd_mark, args, kwds, self->typed); + if (!key_) return -1; - Py_hash_t _hash = *hash = PyObject_Hash(_key); - if (_hash == -1) { - Py_DECREF(_key); + Py_hash_t hash_ = *hash = PyObject_Hash(key_); + if (hash_ == -1) { + Py_DECREF(key_); return -1; } - link = (lru_list_elem *)_PyDict_GetItem_KnownHash(self->cache, _key, _hash); + link = (lru_list_elem *)_PyDict_GetItem_KnownHash(self->cache, key_, hash_); if (link != NULL) { lru_cache_extract_link(link); lru_cache_append_link(self, link); *result = link->result; self->hits++; Py_INCREF(link->result); - Py_DECREF(_key); + Py_DECREF(key_); return 1; } if (PyErr_Occurred()) { - Py_DECREF(_key); + Py_DECREF(key_); return -1; } self->misses++; From 24d4f7a8beecf544cdadbbc3ffcc45e377de9b19 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Wed, 26 Mar 2025 07:10:56 -0400 Subject: [PATCH 04/17] fix smelly symbols for real this time --- Modules/_functoolsmodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index b78cf53a656aa9..d7d2d6912b1bae 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1312,7 +1312,7 @@ bounded_lru_cache_wrapper_pre_call_lock_held(lru_cache_object *self, PyObject *a return 0; } -PyObject * +static PyObject * bounded_lru_cache_wrapper_post_call_lock_held(lru_cache_object *self, PyObject *result, PyObject *key, Py_hash_t hash) { From 7ead4eec0aff7bc20611a47e8ca541ca1fee8489 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Wed, 26 Mar 2025 13:31:21 -0400 Subject: [PATCH 05/17] requested changes --- Modules/_functoolsmodule.c | 42 ++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index d7d2d6912b1bae..f5972902b047d7 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -4,6 +4,7 @@ #include "pycore_long.h" // _PyLong_GetZero() #include "pycore_moduleobject.h" // _PyModule_GetState() #include "pycore_object.h" // _PyObject_GC_TRACK +#include "pycore_pyatomic_ft_wrappers.h" #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_tuple.h" // _PyTuple_ITEMS() @@ -1175,7 +1176,11 @@ uncached_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd { PyObject *result; +#ifdef Py_GIL_DISABLED + _Py_atomic_add_ssize(&self->misses, 1); +#else self->misses++; +#endif result = PyObject_Call(self->func, args, kwds); if (!result) return NULL; @@ -1197,7 +1202,11 @@ infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd } int res = _PyDict_GetItemRef_KnownHash((PyDictObject *)self->cache, key, hash, &result); if (res > 0) { +#ifdef Py_GIL_DISABLED + _Py_atomic_add_ssize(&self->hits, 1); +#else self->hits++; +#endif Py_DECREF(key); return result; } @@ -1205,7 +1214,11 @@ infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd Py_DECREF(key); return NULL; } +#ifdef Py_GIL_DISABLED + _Py_atomic_add_ssize(&self->misses, 1); +#else self->misses++; +#endif result = PyObject_Call(self->func, args, kwds); if (!result) { Py_DECREF(key); @@ -1281,8 +1294,8 @@ lru_cache_prepend_link(lru_cache_object *self, lru_list_elem *link) */ static int -bounded_lru_cache_wrapper_pre_call_lock_held(lru_cache_object *self, PyObject *args, PyObject *kwds, - PyObject **result, PyObject **key, Py_hash_t *hash) +bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject *kwds, + PyObject **result, PyObject **key, Py_hash_t *hash) { lru_list_elem *link; @@ -1299,7 +1312,11 @@ bounded_lru_cache_wrapper_pre_call_lock_held(lru_cache_object *self, PyObject *a lru_cache_extract_link(link); lru_cache_append_link(self, link); *result = link->result; +#ifdef Py_GIL_DISABLED + _Py_atomic_add_ssize(&self->hits, 1); +#else self->hits++; +#endif Py_INCREF(link->result); Py_DECREF(key_); return 1; @@ -1308,12 +1325,16 @@ bounded_lru_cache_wrapper_pre_call_lock_held(lru_cache_object *self, PyObject *a Py_DECREF(key_); return -1; } +#ifdef Py_GIL_DISABLED + _Py_atomic_add_ssize(&self->misses, 1); +#else self->misses++; +#endif return 0; } static PyObject * -bounded_lru_cache_wrapper_post_call_lock_held(lru_cache_object *self, +bounded_lru_cache_update_lock_held(lru_cache_object *self, PyObject *result, PyObject *key, Py_hash_t hash) { lru_list_elem *link; @@ -1462,7 +1483,7 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds int res; Py_BEGIN_CRITICAL_SECTION(self); - res = bounded_lru_cache_wrapper_pre_call_lock_held(self, args, kwds, &result, &key, &hash); + res = bounded_lru_cache_get_lock_held(self, args, kwds, &result, &key, &hash); Py_END_CRITICAL_SECTION(); if (res < 0) { @@ -1475,7 +1496,7 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds result = PyObject_Call(self->func, args, kwds); Py_BEGIN_CRITICAL_SECTION(self); - result = bounded_lru_cache_wrapper_post_call_lock_held(self, result, key, hash); + result = bounded_lru_cache_update_lock_held(self, result, key, hash); Py_END_CRITICAL_SECTION(); return result; @@ -1640,11 +1661,15 @@ _functools__lru_cache_wrapper_cache_info_impl(PyObject *self) lru_cache_object *_self = (lru_cache_object *) self; if (_self->maxsize == -1) { return PyObject_CallFunction(_self->cache_info_type, "nnOn", - _self->hits, _self->misses, Py_None, + FT_ATOMIC_LOAD_SSIZE_RELAXED(_self->hits), + FT_ATOMIC_LOAD_SSIZE_RELAXED(_self->misses), + Py_None, PyDict_GET_SIZE(_self->cache)); } return PyObject_CallFunction(_self->cache_info_type, "nnnn", - _self->hits, _self->misses, _self->maxsize, + FT_ATOMIC_LOAD_SSIZE_RELAXED(_self->hits), + FT_ATOMIC_LOAD_SSIZE_RELAXED(_self->misses), + _self->maxsize, PyDict_GET_SIZE(_self->cache)); } @@ -1661,7 +1686,8 @@ _functools__lru_cache_wrapper_cache_clear_impl(PyObject *self) { lru_cache_object *_self = (lru_cache_object *) self; lru_list_elem *list = lru_cache_unlink_list(_self); - _self->hits = _self->misses = 0; + FT_ATOMIC_STORE_SSIZE_RELAXED(_self->hits, 0); + FT_ATOMIC_STORE_SSIZE_RELAXED(_self->misses, 0); PyDict_Clear(_self->cache); lru_cache_clear_list(list); Py_RETURN_NONE; From 68d14eaee615ae8927d4cb82d65ed31535952005 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Wed, 26 Mar 2025 13:43:50 -0400 Subject: [PATCH 06/17] remove unnecessary atomics --- Modules/_functoolsmodule.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index f5972902b047d7..e909924cb9f34e 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1312,11 +1312,7 @@ bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject lru_cache_extract_link(link); lru_cache_append_link(self, link); *result = link->result; -#ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->hits, 1); -#else self->hits++; -#endif Py_INCREF(link->result); Py_DECREF(key_); return 1; @@ -1325,11 +1321,7 @@ bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject Py_DECREF(key_); return -1; } -#ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->misses, 1); -#else self->misses++; -#endif return 0; } From 028f18b69b4cb4bcf989df6cb6fc8ac3520937dc Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Wed, 26 Mar 2025 13:55:22 -0400 Subject: [PATCH 07/17] add FT_ATOMIC_ADD_SSIZE just in this file --- Modules/_functoolsmodule.c | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index e909924cb9f34e..1bb5044a37c852 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -41,6 +41,12 @@ get_functools_state(PyObject *module) return (_functools_state *)state; } +#ifdef Py_GIL_DISABLED +#define FT_ATOMIC_ADD_SSIZE(value, new_value) \ + _Py_atomic_add_ssize(&value, new_value) +#else +#define FT_ATOMIC_ADD_SSIZE(value, new_value) value += new_value +#endif /* partial object **********************************************************/ @@ -1176,11 +1182,7 @@ uncached_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd { PyObject *result; -#ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->misses, 1); -#else - self->misses++; -#endif + FT_ATOMIC_ADD_SSIZE(self->misses, 1); result = PyObject_Call(self->func, args, kwds); if (!result) return NULL; @@ -1202,11 +1204,7 @@ infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd } int res = _PyDict_GetItemRef_KnownHash((PyDictObject *)self->cache, key, hash, &result); if (res > 0) { -#ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->hits, 1); -#else - self->hits++; -#endif + FT_ATOMIC_ADD_SSIZE(self->hits, 1); Py_DECREF(key); return result; } @@ -1214,11 +1212,7 @@ infinite_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwd Py_DECREF(key); return NULL; } -#ifdef Py_GIL_DISABLED - _Py_atomic_add_ssize(&self->misses, 1); -#else - self->misses++; -#endif + FT_ATOMIC_ADD_SSIZE(self->misses, 1); result = PyObject_Call(self->func, args, kwds); if (!result) { Py_DECREF(key); From 6c74b262cd6484093f4f3e6aa1344cf4a9a60430 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Wed, 26 Mar 2025 14:20:11 -0400 Subject: [PATCH 08/17] _PyDict_GetItemRef_KnownHash_LockHeld() --- Modules/_functoolsmodule.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 1bb5044a37c852..679001b1fbe277 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1301,17 +1301,19 @@ bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject Py_DECREF(key_); return -1; } - link = (lru_list_elem *)_PyDict_GetItem_KnownHash(self->cache, key_, hash_); - if (link != NULL) { + int res = _PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)self->cache, key_, hash_, + (PyObject **)&link); + if (res > 0) { lru_cache_extract_link(link); lru_cache_append_link(self, link); *result = link->result; self->hits++; Py_INCREF(link->result); + Py_DECREF(link); Py_DECREF(key_); return 1; } - if (PyErr_Occurred()) { + if (res < 0) { Py_DECREF(key_); return -1; } @@ -1325,20 +1327,23 @@ bounded_lru_cache_update_lock_held(lru_cache_object *self, { lru_list_elem *link; PyObject *testresult; + int res; if (!result) { Py_DECREF(key); return NULL; } - testresult = _PyDict_GetItem_KnownHash(self->cache, key, hash); - if (testresult != NULL) { + res = _PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)self->cache, key, hash, + &testresult); + if (res > 0) { /* Getting here means that this same key was added to the cache during the PyObject_Call(). Since the link update is already done, we need only return the computed result. */ + Py_DECREF(testresult); Py_DECREF(key); return result; } - if (PyErr_Occurred()) { + if (res < 0) { /* This is an unusual case since this same lookup did not previously trigger an error during lookup. Treat it the same as an error in user function @@ -1402,7 +1407,7 @@ bounded_lru_cache_update_lock_held(lru_cache_object *self, The cache dict holds one reference to the link. We created one other reference when the link was created. The linked list only has borrowed references. */ - int res = _PyDict_Pop_KnownHash((PyDictObject*)self->cache, link->key, + res = _PyDict_Pop_KnownHash((PyDictObject*)self->cache, link->key, link->hash, &popresult); if (res < 0) { /* An error arose while trying to remove the oldest key (the one From 2910429b6c3bf64be211b2851be8b386d29c0429 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Thu, 27 Mar 2025 08:49:08 -0400 Subject: [PATCH 09/17] requested changes --- .../2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst | 2 +- Modules/_functoolsmodule.c | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst b/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst index f4e8647961dc48..440239160cbe2a 100644 --- a/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst +++ b/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst @@ -1 +1 @@ -Make ``functools.lru_cache`` call the cached function unlocked to allow concurrency. +Make :func:``functools.lru_cache`` call the cached function unlocked to allow concurrency. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 679001b1fbe277..7f3b3bda8a3d94 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1291,6 +1291,7 @@ static int bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject *kwds, PyObject **result, PyObject **key, Py_hash_t *hash) { + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); lru_list_elem *link; PyObject *key_ = *key = lru_cache_make_key(self->kwd_mark, args, kwds, self->typed); @@ -1298,7 +1299,7 @@ bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject return -1; Py_hash_t hash_ = *hash = PyObject_Hash(key_); if (hash_ == -1) { - Py_DECREF(key_); + Py_DECREF(key_); /* dead reference left in *key, is not used */ return -1; } int res = _PyDict_GetItemRef_KnownHash_LockHeld((PyDictObject *)self->cache, key_, hash_, @@ -1323,8 +1324,9 @@ bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject static PyObject * bounded_lru_cache_update_lock_held(lru_cache_object *self, - PyObject *result, PyObject *key, Py_hash_t hash) + PyObject *result, PyObject *key, Py_hash_t hash) { + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); lru_list_elem *link; PyObject *testresult; int res; @@ -1487,6 +1489,9 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds result = PyObject_Call(self->func, args, kwds); Py_BEGIN_CRITICAL_SECTION(self); + /* Note: key will be released in the below function and + result may be relased on error, or returned as a passthrough + or have its reference count increased if is added to cache. */ result = bounded_lru_cache_update_lock_held(self, result, key, hash); Py_END_CRITICAL_SECTION(); From 12823257ae1152954f1c76ae0903c336a0764113 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Thu, 27 Mar 2025 08:50:50 -0400 Subject: [PATCH 10/17] stupid news --- .../next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst b/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst index 440239160cbe2a..89f71ca113aa3d 100644 --- a/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst +++ b/Misc/NEWS.d/next/Library/2025-03-26-10-56-22.gh-issue-131757.pFRdmN.rst @@ -1 +1 @@ -Make :func:``functools.lru_cache`` call the cached function unlocked to allow concurrency. +Make :func:`functools.lru_cache` call the cached function unlocked to allow concurrency. From 34f7fe7d3e46506b4d670b8712573b9c20c3999b Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Thu, 27 Mar 2025 09:04:34 -0400 Subject: [PATCH 11/17] requested changes --- Modules/_functoolsmodule.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 7f3b3bda8a3d94..285960fbe6c735 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1489,9 +1489,10 @@ bounded_lru_cache_wrapper(lru_cache_object *self, PyObject *args, PyObject *kwds result = PyObject_Call(self->func, args, kwds); Py_BEGIN_CRITICAL_SECTION(self); - /* Note: key will be released in the below function and - result may be relased on error, or returned as a passthrough - or have its reference count increased if is added to cache. */ + /* Note: key will be stolen in the below function, and + result may be stolen or sometimes re-returned as a passthrough. + Treat both as being stolen. + */ result = bounded_lru_cache_update_lock_held(self, result, key, hash); Py_END_CRITICAL_SECTION(); From 60a475dbe481b8bc125b0a21714c48beb76cf5df Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Fri, 28 Mar 2025 12:05:54 -0400 Subject: [PATCH 12/17] FT_ATOMIC_ADD_SSIZE into pyatomic_ft_wrappers.h --- Include/internal/pycore_pyatomic_ft_wrappers.h | 3 +++ Modules/_functoolsmodule.c | 7 ------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h index d755d03a5fa190..c99085fcdd2fb0 100644 --- a/Include/internal/pycore_pyatomic_ft_wrappers.h +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -109,6 +109,8 @@ extern "C" { _Py_atomic_store_ullong_relaxed(&value, new_value) #define FT_ATOMIC_LOAD_ULLONG_RELAXED(value) \ _Py_atomic_load_ullong_relaxed(&value) +#define FT_ATOMIC_ADD_SSIZE(value, new_value) \ + _Py_atomic_add_ssize(&value, new_value) #else #define FT_ATOMIC_LOAD_PTR(value) value @@ -156,6 +158,7 @@ extern "C" { #define FT_ATOMIC_STORE_LLONG_RELAXED(value, new_value) value = new_value #define FT_ATOMIC_LOAD_ULLONG_RELAXED(value) value #define FT_ATOMIC_STORE_ULLONG_RELAXED(value, new_value) value = new_value +#define FT_ATOMIC_ADD_SSIZE(value, new_value) (value += new_value) - new_value #endif diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 285960fbe6c735..c91d0c7a422eee 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -41,13 +41,6 @@ get_functools_state(PyObject *module) return (_functools_state *)state; } -#ifdef Py_GIL_DISABLED -#define FT_ATOMIC_ADD_SSIZE(value, new_value) \ - _Py_atomic_add_ssize(&value, new_value) -#else -#define FT_ATOMIC_ADD_SSIZE(value, new_value) value += new_value -#endif - /* partial object **********************************************************/ From 4200582082eee25a442d7ba2ac000d6be0607035 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Fri, 28 Mar 2025 12:18:47 -0400 Subject: [PATCH 13/17] stupid warnings --- Include/internal/pycore_pyatomic_ft_wrappers.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h index c99085fcdd2fb0..c5ee1b6f79330d 100644 --- a/Include/internal/pycore_pyatomic_ft_wrappers.h +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -158,7 +158,14 @@ extern "C" { #define FT_ATOMIC_STORE_LLONG_RELAXED(value, new_value) value = new_value #define FT_ATOMIC_LOAD_ULLONG_RELAXED(value) value #define FT_ATOMIC_STORE_ULLONG_RELAXED(value, new_value) value = new_value -#define FT_ATOMIC_ADD_SSIZE(value, new_value) (value += new_value) - new_value + +static inline _FT_ATOMIC_ADD_SSIZE(Py_ssize_t *value, Py_ssize_t new_value) +{ + Py_ssize_t old_value = *value; + *value = old_value + new_value; + return old_value; +} +#define FT_ATOMIC_ADD_SSIZE(value, new_value) _FT_ATOMIC_ADD_SSIZE(&value, new_value) #endif From aed9b452fb0383c5bc37673468f0f4d01eff7873 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Fri, 28 Mar 2025 12:21:36 -0400 Subject: [PATCH 14/17] return type --- Include/internal/pycore_pyatomic_ft_wrappers.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h index c5ee1b6f79330d..99466dd810c7a5 100644 --- a/Include/internal/pycore_pyatomic_ft_wrappers.h +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -159,7 +159,8 @@ extern "C" { #define FT_ATOMIC_LOAD_ULLONG_RELAXED(value) value #define FT_ATOMIC_STORE_ULLONG_RELAXED(value, new_value) value = new_value -static inline _FT_ATOMIC_ADD_SSIZE(Py_ssize_t *value, Py_ssize_t new_value) +static inline Py_ssize_t +_FT_ATOMIC_ADD_SSIZE(Py_ssize_t *value, Py_ssize_t new_value) { Py_ssize_t old_value = *value; *value = old_value + new_value; From 47e47d58640b9b53e1ed46db453cd29732180a78 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Fri, 28 Mar 2025 12:27:59 -0400 Subject: [PATCH 15/17] FT_ATOMIC_ADD_SSIZE no return value --- Include/internal/pycore_pyatomic_ft_wrappers.h | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h b/Include/internal/pycore_pyatomic_ft_wrappers.h index 99466dd810c7a5..3e41e2fd1569ca 100644 --- a/Include/internal/pycore_pyatomic_ft_wrappers.h +++ b/Include/internal/pycore_pyatomic_ft_wrappers.h @@ -110,7 +110,7 @@ extern "C" { #define FT_ATOMIC_LOAD_ULLONG_RELAXED(value) \ _Py_atomic_load_ullong_relaxed(&value) #define FT_ATOMIC_ADD_SSIZE(value, new_value) \ - _Py_atomic_add_ssize(&value, new_value) + (void)_Py_atomic_add_ssize(&value, new_value) #else #define FT_ATOMIC_LOAD_PTR(value) value @@ -158,15 +158,7 @@ extern "C" { #define FT_ATOMIC_STORE_LLONG_RELAXED(value, new_value) value = new_value #define FT_ATOMIC_LOAD_ULLONG_RELAXED(value) value #define FT_ATOMIC_STORE_ULLONG_RELAXED(value, new_value) value = new_value - -static inline Py_ssize_t -_FT_ATOMIC_ADD_SSIZE(Py_ssize_t *value, Py_ssize_t new_value) -{ - Py_ssize_t old_value = *value; - *value = old_value + new_value; - return old_value; -} -#define FT_ATOMIC_ADD_SSIZE(value, new_value) _FT_ATOMIC_ADD_SSIZE(&value, new_value) +#define FT_ATOMIC_ADD_SSIZE(value, new_value) (void)(value += new_value) #endif From 11eab6d046633128f46e550465e42f301958dd67 Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Mon, 14 Apr 2025 08:39:51 -0400 Subject: [PATCH 16/17] bounded_lru_cache_get_lock_held atomic writes so tsan doesn't complain --- Modules/_functoolsmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 634539ea713497..9603344f868cc4 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1299,7 +1299,7 @@ bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject lru_cache_extract_link(link); lru_cache_append_link(self, link); *result = link->result; - self->hits++; + FT_ATOMIC_ADD_SSIZE(self->hits, 1); Py_INCREF(link->result); Py_DECREF(link); Py_DECREF(key_); @@ -1309,7 +1309,7 @@ bounded_lru_cache_get_lock_held(lru_cache_object *self, PyObject *args, PyObject Py_DECREF(key_); return -1; } - self->misses++; + FT_ATOMIC_ADD_SSIZE(self->misses, 1); return 0; } From 5d1bf312dd62baf1a91ef8eed7b80fc86f50380f Mon Sep 17 00:00:00 2001 From: Tomasz Pytel Date: Mon, 14 Apr 2025 11:07:59 -0400 Subject: [PATCH 17/17] fix indentation --- Modules/_functoolsmodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 9603344f868cc4..e6c454faf4b16f 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1016,7 +1016,7 @@ _functools_reduce_impl(PyObject *module, PyObject *func, PyObject *seq, if (result == NULL) PyErr_SetString(PyExc_TypeError, - "reduce() of empty iterable with no initial value"); + "reduce() of empty iterable with no initial value"); Py_DECREF(it); return result; @@ -1401,7 +1401,7 @@ bounded_lru_cache_update_lock_held(lru_cache_object *self, We created one other reference when the link was created. The linked list only has borrowed references. */ res = _PyDict_Pop_KnownHash((PyDictObject*)self->cache, link->key, - link->hash, &popresult); + link->hash, &popresult); if (res < 0) { /* An error arose while trying to remove the oldest key (the one being evicted) from the cache. We restore the link to its