Skip to content

Commit 36e8f04

Browse files
authored
Merge branch '3.13' into fix-name-passed-to-task-factory
2 parents f9786ce + e1cc789 commit 36e8f04

File tree

5 files changed

+85
-9
lines changed

5 files changed

+85
-9
lines changed

Doc/c-api/unicode.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,16 @@ APIs:
621621
decref'ing the returned objects.
622622
623623
624+
.. c:function:: PyObject* PyUnicode_BuildEncodingMap(PyObject* string)
625+
626+
Return a mapping suitable for decoding a custom single-byte encoding.
627+
Given a Unicode string *string* of up to 256 characters representing an encoding
628+
table, returns either a compact internal mapping object or a dictionary
629+
mapping character ordinals to byte values. Raises a :exc:`TypeError` and
630+
return ``NULL`` on invalid input.
631+
.. versionadded:: 3.2
632+
633+
624634
.. c:function:: const char* PyUnicode_GetDefaultEncoding(void)
625635
626636
Return the name of the default string encoding, ``"utf-8"``.

Doc/data/refcounts.dat

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2759,6 +2759,9 @@ PyUnicode_FromFormatV:va_list:args::
27592759
PyUnicode_FromOrdinal:PyObject*::+1:
27602760
PyUnicode_FromOrdinal:int:ordinal::
27612761

2762+
PyUnicode_BuildEncodingMap:PyObject*::+1:
2763+
PyUnicode_BuildEncodingMap:PyObject*:string:::
2764+
27622765
PyUnicode_GetDefaultEncoding:const char*:::
27632766
PyUnicode_GetDefaultEncoding::void::
27642767

Lib/test/test_free_threading/test_dict.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from ast import Or
77
from functools import partial
8-
from threading import Thread
8+
from threading import Thread, Barrier
99
from unittest import TestCase
1010

1111
try:
@@ -142,6 +142,25 @@ def writer_func(l):
142142
for ref in thread_list:
143143
self.assertIsNone(ref())
144144

145+
def test_getattr_setattr(self):
146+
NUM_THREADS = 10
147+
b = Barrier(NUM_THREADS)
148+
149+
def closure(b, c):
150+
b.wait()
151+
for i in range(10):
152+
getattr(c, f'attr_{i}', None)
153+
setattr(c, f'attr_{i}', 99)
154+
155+
class MyClass:
156+
pass
157+
158+
o = MyClass()
159+
threads = [Thread(target=closure, args=(b, o))
160+
for _ in range(NUM_THREADS)]
161+
with threading_helper.start_threads(threads):
162+
pass
163+
145164
@unittest.skipIf(_testcapi is None, 'need _testcapi module')
146165
def test_dict_version(self):
147166
dict_version = _testcapi.dict_version
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash in the :term:`free threading` build when accessing an object
2+
attribute that may be concurrently inserted or deleted.

Objects/dictobject.c

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,6 +1185,37 @@ dictkeys_generic_lookup(PyDictObject *mp, PyDictKeysObject* dk, PyObject *key, P
11851185
return do_lookup(mp, dk, key, hash, compare_generic);
11861186
}
11871187

1188+
#ifdef Py_GIL_DISABLED
1189+
1190+
static Py_ssize_t
1191+
unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key,
1192+
Py_hash_t hash);
1193+
1194+
#endif
1195+
1196+
static Py_ssize_t
1197+
unicodekeys_lookup_split(PyDictKeysObject* dk, PyObject *key, Py_hash_t hash)
1198+
{
1199+
Py_ssize_t ix;
1200+
assert(dk->dk_kind == DICT_KEYS_SPLIT);
1201+
assert(PyUnicode_CheckExact(key));
1202+
1203+
#ifdef Py_GIL_DISABLED
1204+
// A split dictionaries keys can be mutated by other dictionaries
1205+
// but if we have a unicode key we can avoid locking the shared
1206+
// keys.
1207+
ix = unicodekeys_lookup_unicode_threadsafe(dk, key, hash);
1208+
if (ix == DKIX_KEY_CHANGED) {
1209+
LOCK_KEYS(dk);
1210+
ix = unicodekeys_lookup_unicode(dk, key, hash);
1211+
UNLOCK_KEYS(dk);
1212+
}
1213+
#else
1214+
ix = unicodekeys_lookup_unicode(dk, key, hash);
1215+
#endif
1216+
return ix;
1217+
}
1218+
11881219
/* Lookup a string in a (all unicode) dict keys.
11891220
* Returns DKIX_ERROR if key is not a string,
11901221
* or if the dict keys is not all strings.
@@ -1209,13 +1240,24 @@ _PyDictKeys_StringLookup(PyDictKeysObject* dk, PyObject *key)
12091240
return unicodekeys_lookup_unicode(dk, key, hash);
12101241
}
12111242

1212-
#ifdef Py_GIL_DISABLED
1213-
1214-
static Py_ssize_t
1215-
unicodekeys_lookup_unicode_threadsafe(PyDictKeysObject* dk, PyObject *key,
1216-
Py_hash_t hash);
1217-
1218-
#endif
1243+
/* Like _PyDictKeys_StringLookup() but only works on split keys. Note
1244+
* that in free-threaded builds this locks the keys object as required.
1245+
*/
1246+
Py_ssize_t
1247+
_PyDictKeys_StringLookupSplit(PyDictKeysObject* dk, PyObject *key)
1248+
{
1249+
assert(dk->dk_kind == DICT_KEYS_SPLIT);
1250+
assert(PyUnicode_CheckExact(key));
1251+
Py_hash_t hash = unicode_get_hash(key);
1252+
if (hash == -1) {
1253+
hash = PyUnicode_Type.tp_hash(key);
1254+
if (hash == -1) {
1255+
PyErr_Clear();
1256+
return DKIX_ERROR;
1257+
}
1258+
}
1259+
return unicodekeys_lookup_split(dk, key, hash);
1260+
}
12191261

12201262
/*
12211263
The basic lookup function used by all operations.
@@ -6976,7 +7018,7 @@ _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name, PyObject **attr
69767018

69777019
PyDictKeysObject *keys = CACHED_KEYS(Py_TYPE(obj));
69787020
assert(keys != NULL);
6979-
Py_ssize_t ix = _PyDictKeys_StringLookup(keys, name);
7021+
Py_ssize_t ix = _PyDictKeys_StringLookupSplit(keys, name);
69807022
if (ix == DKIX_EMPTY) {
69817023
*attr = NULL;
69827024
return true;

0 commit comments

Comments
 (0)