Skip to content

Commit c48673b

Browse files
[3.15] gh-151722: Defer GC tracking in frozendict.copy() (GH-152230) (#152271)
gh-151722: Defer GC tracking in frozendict.copy() (GH-152230) Fix _PyDict_Or() and frozendict.copy(): only track the frozendict by the GC once the dictionary is fully initialized. Functions modifying frozendict now ensures that the object is not tracked by the GC (in debug mode). * can_modify_dict() checks that _PyObject_GC_IS_TRACKED() is false for frozendicts. * dict_merge_api() makes sure that the dictionary is tracked by the GC. (cherry picked from commit 05679f3) Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 62842c9 commit c48673b

1 file changed

Lines changed: 72 additions & 37 deletions

File tree

Objects/dictobject.c

Lines changed: 72 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -296,16 +296,20 @@ static inline int
296296
can_modify_dict(PyDictObject *mp)
297297
{
298298
if (PyFrozenDict_Check(mp)) {
299+
// gh-151722: A frozendict must not be tracked by the GC
300+
// when it's being modified.
301+
assert(!_PyObject_GC_IS_TRACKED(mp));
302+
299303
// No locking required to modify a newly created frozendict
300304
// since it's only accessible from the current thread.
301-
return PyUnstable_Object_IsUniquelyReferenced(_PyObject_CAST(mp));
305+
assert(PyUnstable_Object_IsUniquelyReferenced(_PyObject_CAST(mp)));
302306
}
303307
else {
304308
// Locking is only required if the dictionary is not
305309
// uniquely referenced.
306310
ASSERT_DICT_LOCKED(mp);
307-
return 1;
308311
}
312+
return 1;
309313
}
310314
#endif
311315

@@ -902,7 +906,7 @@ free_values(PyDictValues *values, bool use_qsbr)
902906
static inline PyObject *
903907
new_dict_impl(PyDictObject *mp, PyDictKeysObject *keys,
904908
PyDictValues *values, Py_ssize_t used,
905-
int free_values_on_failure, int frozendict)
909+
int free_values_on_failure, int frozendict, int gc_track)
906910
{
907911
assert(keys != NULL);
908912
if (mp == NULL) {
@@ -921,12 +925,14 @@ new_dict_impl(PyDictObject *mp, PyDictKeysObject *keys,
921925
((PyFrozenDictObject *)mp)->ma_hash = -1;
922926
}
923927
ASSERT_CONSISTENT(mp);
924-
_PyObject_GC_TRACK(mp);
928+
if (gc_track) {
929+
_PyObject_GC_TRACK(mp);
930+
}
925931
return (PyObject *)mp;
926932
}
927933

928934
/* Consumes a reference to the keys object */
929-
static PyObject *
935+
static PyObject*
930936
new_dict(PyDictKeysObject *keys, PyDictValues *values,
931937
Py_ssize_t used, int free_values_on_failure)
932938
{
@@ -936,16 +942,30 @@ new_dict(PyDictKeysObject *keys, PyDictValues *values,
936942
}
937943
assert(mp == NULL || Py_IS_TYPE(mp, &PyDict_Type));
938944

939-
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0);
945+
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0, 1);
940946
}
941947

942948
/* Consumes a reference to the keys object */
943-
static PyObject *
944-
new_frozendict(PyDictKeysObject *keys, PyDictValues *values,
945-
Py_ssize_t used, int free_values_on_failure)
949+
static PyObject*
950+
new_dict_untracked(PyDictKeysObject *keys, PyDictValues *values,
951+
Py_ssize_t used, int free_values_on_failure)
952+
{
953+
PyDictObject *mp = _Py_FREELIST_POP(PyDictObject, dicts);
954+
if (mp == NULL) {
955+
mp = PyObject_GC_New(PyDictObject, &PyDict_Type);
956+
}
957+
assert(mp == NULL || Py_IS_TYPE(mp, &PyDict_Type));
958+
959+
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 0, 0);
960+
}
961+
962+
/* Consumes a reference to the keys object */
963+
static PyObject*
964+
new_frozendict_untracked(PyDictKeysObject *keys, PyDictValues *values,
965+
Py_ssize_t used, int free_values_on_failure)
946966
{
947967
PyDictObject *mp = PyObject_GC_New(PyDictObject, &PyFrozenDict_Type);
948-
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 1);
968+
return new_dict_impl(mp, keys, values, used, free_values_on_failure, 1, 0);
949969
}
950970

951971
static PyObject *
@@ -3427,7 +3447,6 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34273447
}
34283448
if (PyFrozenDict_Check(d)) {
34293449
assert(can_modify_dict((PyDictObject*)d));
3430-
assert(!_PyObject_GC_IS_TRACKED(d));
34313450
}
34323451

34333452
if (PyDict_CheckExact(d)) {
@@ -4032,6 +4051,7 @@ merge_from_seq2_lock_held(PyObject *d, PyObject *seq2, int override)
40324051
assert(d != NULL);
40334052
assert(PyAnyDict_Check(d));
40344053
assert(seq2 != NULL);
4054+
assert(can_modify_dict((PyDictObject*)d));
40354055

40364056
it = PyObject_GetIter(seq2);
40374057
if (it == NULL)
@@ -4233,11 +4253,13 @@ dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey)
42334253
assert(0 <= override && override <= 2);
42344254

42354255
PyDictObject *mp = _PyAnyDict_CAST(a);
4256+
42364257
int res = 0;
42374258
if (PyAnyDict_Check(b) && (Py_TYPE(b)->tp_iter == dict_iter)) {
42384259
PyDictObject *other = (PyDictObject*)b;
42394260
int res;
42404261
Py_BEGIN_CRITICAL_SECTION2(a, b);
4262+
assert(can_modify_dict(mp));
42414263
res = dict_dict_merge((PyDictObject *)a, other, override, dupkey);
42424264
ASSERT_CONSISTENT(a);
42434265
Py_END_CRITICAL_SECTION2();
@@ -4246,6 +4268,8 @@ dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey)
42464268
else {
42474269
/* Do it the generic, slower way */
42484270
Py_BEGIN_CRITICAL_SECTION(a);
4271+
assert(can_modify_dict(mp));
4272+
42494273
PyObject *keys = PyMapping_Keys(b);
42504274
PyObject *iter;
42514275
PyObject *key, *value;
@@ -4338,9 +4362,7 @@ dict_merge_api(PyObject *a, PyObject *b, int override, PyObject **dupkey)
43384362
}
43394363

43404364
int res = dict_merge(a, b, override, dupkey);
4341-
if (PyDict_Check(a)) {
4342-
assert(_PyObject_GC_IS_TRACKED(a));
4343-
}
4365+
assert(_PyObject_GC_IS_TRACKED(a));
43444366
return res;
43454367
}
43464368

@@ -4398,7 +4420,7 @@ copy_values(PyDictValues *values)
43984420
}
43994421

44004422
static PyObject *
4401-
copy_lock_held(PyObject *o, int as_frozendict)
4423+
copy_lock_held_untracked(PyObject *o, int as_frozendict)
44024424
{
44034425
PyObject *copy;
44044426
PyDictObject *mp;
@@ -4411,12 +4433,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
44114433
mp = (PyDictObject *)o;
44124434
if (mp->ma_used == 0) {
44134435
/* The dict is empty; just return a new dict. */
4436+
PyObject *d;
44144437
if (as_frozendict) {
4415-
return PyFrozenDict_New(NULL);
4438+
d = frozendict_new_untracked(&PyFrozenDict_Type);
44164439
}
44174440
else {
4418-
return PyDict_New();
4441+
d = dict_new_untracked(&PyDict_Type);
44194442
}
4443+
assert(!_PyObject_GC_IS_TRACKED(d));
4444+
return d;
44204445
}
44214446

44224447
if (_PyDict_HasSplitTable(mp)) {
@@ -4448,7 +4473,7 @@ copy_lock_held(PyObject *o, int as_frozendict)
44484473
PyFrozenDictObject *frozen = (PyFrozenDictObject *)split_copy;
44494474
frozen->ma_hash = -1;
44504475
}
4451-
_PyObject_GC_TRACK(split_copy);
4476+
assert(!_PyObject_GC_IS_TRACKED(split_copy));
44524477
return (PyObject *)split_copy;
44534478
}
44544479

@@ -4476,10 +4501,10 @@ copy_lock_held(PyObject *o, int as_frozendict)
44764501
}
44774502
PyDictObject *new;
44784503
if (as_frozendict) {
4479-
new = (PyDictObject *)new_frozendict(keys, NULL, 0, 0);
4504+
new = (PyDictObject *)new_frozendict_untracked(keys, NULL, 0, 0);
44804505
}
44814506
else {
4482-
new = (PyDictObject *)new_dict(keys, NULL, 0, 0);
4507+
new = (PyDictObject *)new_dict_untracked(keys, NULL, 0, 0);
44834508
}
44844509
if (new == NULL) {
44854510
/* In case of an error, new_dict()/new_frozendict() takes care of
@@ -4489,14 +4514,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
44894514

44904515
new->ma_used = mp->ma_used;
44914516
ASSERT_CONSISTENT(new);
4517+
assert(!_PyObject_GC_IS_TRACKED(new));
44924518
return (PyObject *)new;
44934519
}
44944520

44954521
if (as_frozendict) {
4496-
copy = PyFrozenDict_New(NULL);
4522+
copy = frozendict_new_untracked(&PyFrozenDict_Type);
44974523
}
44984524
else {
4499-
copy = PyDict_New();
4525+
copy = dict_new_untracked(&PyDict_Type);
45004526
}
45014527
if (copy == NULL)
45024528
return NULL;
@@ -4505,9 +4531,7 @@ copy_lock_held(PyObject *o, int as_frozendict)
45054531
return NULL;
45064532
}
45074533

4508-
if (PyDict_Check(copy)) {
4509-
assert(_PyObject_GC_IS_TRACKED(copy));
4510-
}
4534+
assert(!_PyObject_GC_IS_TRACKED(copy));
45114535
return copy;
45124536
}
45134537

@@ -4521,25 +4545,28 @@ PyDict_Copy(PyObject *o)
45214545

45224546
PyObject *res;
45234547
Py_BEGIN_CRITICAL_SECTION(o);
4524-
res = copy_lock_held(o, 0);
4548+
res = copy_lock_held_untracked(o, 0);
45254549
Py_END_CRITICAL_SECTION();
4550+
if (res != NULL) {
4551+
_PyObject_GC_TRACK(res);
4552+
}
45264553
return res;
45274554
}
45284555

45294556
// Similar to PyDict_Copy(), but return a frozendict if the argument
45304557
// is a frozendict.
45314558
static PyObject *
4532-
anydict_copy(PyObject *o)
4559+
anydict_copy_untracked(PyObject *o)
45334560
{
45344561
assert(PyAnyDict_Check(o));
45354562

45364563
PyObject *res;
45374564
if (PyFrozenDict_Check(o)) {
4538-
res = copy_lock_held(o, 1);
4565+
res = copy_lock_held_untracked(o, 1);
45394566
}
45404567
else {
45414568
Py_BEGIN_CRITICAL_SECTION(o);
4542-
res = copy_lock_held(o, 0);
4569+
res = copy_lock_held_untracked(o, 0);
45434570
Py_END_CRITICAL_SECTION();
45444571
}
45454572
return res;
@@ -4554,13 +4581,16 @@ _PyDict_CopyAsDict(PyObject *o)
45544581

45554582
PyObject *res;
45564583
if (PyFrozenDict_Check(o)) {
4557-
res = copy_lock_held(o, 0);
4584+
res = copy_lock_held_untracked(o, 0);
45584585
}
45594586
else {
45604587
Py_BEGIN_CRITICAL_SECTION(o);
4561-
res = copy_lock_held(o, 0);
4588+
res = copy_lock_held_untracked(o, 0);
45624589
Py_END_CRITICAL_SECTION();
45634590
}
4591+
if (res != NULL) {
4592+
_PyObject_GC_TRACK(res);
4593+
}
45644594
return res;
45654595
}
45664596

@@ -5113,14 +5143,15 @@ _PyDict_Or(PyObject *self, PyObject *other)
51135143
if (!PyAnyDict_Check(self) || !PyAnyDict_Check(other)) {
51145144
Py_RETURN_NOTIMPLEMENTED;
51155145
}
5116-
PyObject *new = anydict_copy(self);
5146+
PyObject *new = anydict_copy_untracked(self);
51175147
if (new == NULL) {
51185148
return NULL;
51195149
}
51205150
if (dict_update_arg(new, other)) {
51215151
Py_DECREF(new);
51225152
return NULL;
51235153
}
5154+
_PyObject_GC_TRACK(new);
51245155
return new;
51255156
}
51265157

@@ -5308,7 +5339,6 @@ dict_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds
53085339
if (self == NULL) {
53095340
return NULL;
53105341
}
5311-
assert(!_PyObject_GC_IS_TRACKED(self));
53125342
_PyObject_GC_TRACK(self);
53135343
return self;
53145344
}
@@ -5390,7 +5420,7 @@ frozendict_vectorcall(PyObject *type, PyObject * const*args,
53905420
}
53915421
}
53925422
}
5393-
assert(!_PyObject_GC_IS_TRACKED(self));
5423+
53945424
_PyObject_GC_TRACK(self);
53955425
return self;
53965426
}
@@ -6709,10 +6739,12 @@ dictitems_xor_lock_held(PyObject *d1, PyObject *d2)
67096739
ASSERT_DICT_LOCKED(d1);
67106740
ASSERT_DICT_LOCKED(d2);
67116741

6712-
PyObject *temp_dict = copy_lock_held(d1, 0);
6742+
PyObject *temp_dict = copy_lock_held_untracked(d1, 0);
67136743
if (temp_dict == NULL) {
67146744
return NULL;
67156745
}
6746+
_PyObject_GC_TRACK(temp_dict);
6747+
67166748
PyObject *result_set = PySet_New(NULL);
67176749
if (result_set == NULL) {
67186750
Py_CLEAR(temp_dict);
@@ -8417,7 +8449,6 @@ frozendict_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
84178449
assert(kwds == NULL);
84188450
}
84198451

8420-
assert(!_PyObject_GC_IS_TRACKED(d));
84218452
_PyObject_GC_TRACK(d);
84228453
return d;
84238454
}
@@ -8462,7 +8493,11 @@ frozendict_copy_impl(PyFrozenDictObject *self)
84628493
return Py_NewRef(self);
84638494
}
84648495

8465-
return anydict_copy((PyObject*)self);
8496+
PyObject *copy = anydict_copy_untracked((PyObject*)self);
8497+
if (copy != NULL) {
8498+
_PyObject_GC_TRACK(copy);
8499+
}
8500+
return copy;
84668501
}
84678502

84688503

0 commit comments

Comments
 (0)