Skip to content

Commit 30f9c8a

Browse files
miss-islingtonvstinnercorona10methane
authored
[3.15] gh-151722: Do not track the frozendict in the GC in _PyDict_FromKeys() (GH-152067) (#152225)
gh-151722: Do not track the frozendict in the GC in _PyDict_FromKeys() (GH-152067) _PyDict_FromKeys() now creates a frozendict copy which is not tracked by the GC. dict_merge() no longer requires the dictionary to be tracked by the GC. (cherry picked from commit 55bc312) Co-authored-by: Victor Stinner <vstinner@python.org> Co-authored-by: Donghee Na <donghee.na@python.org> Co-authored-by: Inada Naoki <songofacandy@gmail.com>
1 parent 59e54f5 commit 30f9c8a

4 files changed

Lines changed: 149 additions & 65 deletions

File tree

Doc/library/stdtypes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5757,6 +5757,13 @@ Frozen dictionaries
57575757
Like dictionaries, frozendicts are :ref:`generic <generics>` over two types,
57585758
signifying (respectively) the types of the frozendict's keys and values.
57595759

5760+
.. classmethod:: fromkeys(iterable, value=None, /)
5761+
5762+
Similar to :meth:`dict.fromkeys`, but call again the type constructor
5763+
with an initialized :class:`frozendict` if the type is a
5764+
:class:`frozendict` subclass or if the constructor returned a
5765+
:class:`frozendict`.
5766+
57605767
.. versionadded:: 3.15
57615768

57625769

Lib/test/test_dict.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,8 +1939,11 @@ def test_fromkeys(self):
19391939
# Subclass which overrides the constructor
19401940
created = frozendict(x=1)
19411941
class FrozenDictSubclass(frozendict):
1942-
def __new__(self):
1943-
return created
1942+
def __new__(cls, *args, **kwargs):
1943+
if args or kwargs:
1944+
return super().__new__(cls, *args, **kwargs)
1945+
else:
1946+
return created
19441947

19451948
fd = FrozenDictSubclass.fromkeys("abc")
19461949
self.assertEqual(fd, frozendict(x=1, a=None, b=None, c=None))
@@ -1952,6 +1955,20 @@ def __new__(self):
19521955
self.assertEqual(type(fd), FrozenDictSubclass)
19531956
self.assertEqual(created, frozendict(x=1))
19541957

1958+
# Dict subclass with a constructor which returns a frozendict
1959+
# by default
1960+
class DictSubclass(dict):
1961+
def __new__(cls, *args, **kwargs):
1962+
if args or kwargs:
1963+
return super().__new__(cls, *args, **kwargs)
1964+
else:
1965+
return created
1966+
1967+
fd = DictSubclass.fromkeys("abc")
1968+
self.assertEqual(fd, frozendict(x=1, a=None, b=None, c=None))
1969+
self.assertEqual(type(fd), DictSubclass)
1970+
self.assertEqual(created, frozendict(x=1))
1971+
19551972
# Subclass which doesn't override the constructor
19561973
class FrozenDictSubclass2(frozendict):
19571974
pass
@@ -1960,16 +1977,6 @@ class FrozenDictSubclass2(frozendict):
19601977
self.assertEqual(fd, frozendict(a=None, b=None, c=None))
19611978
self.assertEqual(type(fd), FrozenDictSubclass2)
19621979

1963-
# Dict subclass which overrides the constructor
1964-
class DictSubclass(dict):
1965-
def __new__(self):
1966-
return created
1967-
1968-
fd = DictSubclass.fromkeys("abc")
1969-
self.assertEqual(fd, frozendict(x=1, a=None, b=None, c=None))
1970-
self.assertEqual(type(fd), DictSubclass)
1971-
self.assertEqual(created, frozendict(x=1))
1972-
19731980
def test_pickle(self):
19741981
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
19751982
for fd in (
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:meth:`!frozendict.fromkeys` now only tracks the :class:`frozendict` in the
2+
garbage collector once the dictionary is fully initialized. Patch by Donghee Na
3+
and Victor Stinner.

Objects/dictobject.c

Lines changed: 120 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ static PyObject* frozendict_new(PyTypeObject *type, PyObject *args,
140140
PyObject *kwds);
141141
static PyObject* frozendict_new_untracked(PyTypeObject *type);
142142
static PyObject* dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds);
143+
static PyObject* dict_new_untracked(PyTypeObject *type);
143144
static int dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey);
144145
static int dict_contains(PyObject *op, PyObject *key);
145146
static int dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override);
@@ -3387,40 +3388,47 @@ dict_set_fromkeys(PyDictObject *mp, PyObject *iterable, PyObject *value)
33873388
PyObject *
33883389
_PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
33893390
{
3390-
PyObject *it; /* iter(iterable) */
3391-
PyObject *key;
3391+
PyObject *it = NULL; /* iter(iterable) */
33923392
PyObject *d;
3393-
int status;
3393+
int need_copy = 0;
33943394

3395-
d = _PyObject_CallNoArgs(cls);
3395+
if (cls == (PyObject*)&PyFrozenDict_Type) {
3396+
// gh-151722: Create a frozendict which is not tracked by the GC.
3397+
d = frozendict_new_untracked(&PyFrozenDict_Type);
3398+
}
3399+
else {
3400+
// Dict subclass, or frozendict subclass which overrides
3401+
// the constructor.
3402+
d = _PyObject_CallNoArgs(cls);
3403+
}
33963404
if (d == NULL) {
33973405
return NULL;
33983406
}
33993407

3400-
// If cls is a dict or frozendict subclass with overridden constructor,
3401-
// copy the frozendict.
3402-
PyTypeObject *cls_type = _PyType_CAST(cls);
3403-
if (PyFrozenDict_Check(d) && cls_type->tp_new != frozendict_new) {
3404-
// Subclass-friendly copy
3405-
PyObject *copy;
3406-
if (PyObject_IsSubclass(cls, (PyObject*)&PyFrozenDict_Type)) {
3407-
copy = frozendict_new(cls_type, NULL, NULL);
3408-
}
3409-
else {
3410-
copy = dict_new(cls_type, NULL, NULL);
3411-
}
3408+
// gh-151722: If cls constructor returns a frozendict which is tracked by
3409+
// the GC, create a frozendict copy which is not tracked by the GC.
3410+
//
3411+
// At the function exit, return cls(fd) where fd is a frozendict.
3412+
//
3413+
// Untracking the frozendict requires tracking again the frozendict on
3414+
// error which is more complicated. It's easier to work on a copy.
3415+
if (PyFrozenDict_Check(d) && _PyObject_GC_IS_TRACKED(d)) {
3416+
need_copy = 1;
3417+
3418+
PyObject *copy = frozendict_new_untracked(&PyFrozenDict_Type);
34123419
if (copy == NULL) {
3413-
Py_DECREF(d);
3414-
return NULL;
3420+
goto Fail;
34153421
}
34163422
if (dict_merge(copy, d, 1, NULL) < 0) {
3417-
Py_DECREF(d);
34183423
Py_DECREF(copy);
3419-
return NULL;
3424+
goto Fail;
34203425
}
34213426
Py_SETREF(d, copy);
34223427
}
3423-
assert(!PyFrozenDict_Check(d) || can_modify_dict((PyDictObject*)d));
3428+
if (PyFrozenDict_Check(d)) {
3429+
assert(can_modify_dict((PyDictObject*)d));
3430+
assert(!_PyObject_GC_IS_TRACKED(d));
3431+
}
34243432

34253433
if (PyDict_CheckExact(d)) {
34263434
if (PyDict_CheckExact(iterable)) {
@@ -3429,23 +3437,23 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34293437
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
34303438
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
34313439
Py_END_CRITICAL_SECTION2();
3432-
return d;
3440+
goto Done;
34333441
}
34343442
else if (PyFrozenDict_CheckExact(iterable)) {
34353443
PyDictObject *mp = (PyDictObject *)d;
34363444

34373445
Py_BEGIN_CRITICAL_SECTION(d);
34383446
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
34393447
Py_END_CRITICAL_SECTION();
3440-
return d;
3448+
goto Done;
34413449
}
34423450
else if (PyAnySet_CheckExact(iterable)) {
34433451
PyDictObject *mp = (PyDictObject *)d;
34443452

34453453
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
34463454
d = (PyObject *)dict_set_fromkeys(mp, iterable, value);
34473455
Py_END_CRITICAL_SECTION2();
3448-
return d;
3456+
goto Done;
34493457
}
34503458
}
34513459
else if (PyFrozenDict_CheckExact(d)) {
@@ -3455,71 +3463,113 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34553463
Py_BEGIN_CRITICAL_SECTION(iterable);
34563464
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
34573465
Py_END_CRITICAL_SECTION();
3458-
return d;
3466+
goto Done;
34593467
}
34603468
else if (PyFrozenDict_CheckExact(iterable)) {
34613469
PyDictObject *mp = (PyDictObject *)d;
34623470
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
3463-
return d;
3471+
goto Done;
34643472
}
34653473
else if (PyAnySet_CheckExact(iterable)) {
34663474
PyDictObject *mp = (PyDictObject *)d;
34673475

34683476
Py_BEGIN_CRITICAL_SECTION(iterable);
34693477
d = (PyObject *)dict_set_fromkeys(mp, iterable, value);
34703478
Py_END_CRITICAL_SECTION();
3471-
return d;
3479+
goto Done;
34723480
}
34733481
}
34743482

34753483
it = PyObject_GetIter(iterable);
34763484
if (it == NULL){
3477-
Py_DECREF(d);
3478-
return NULL;
3485+
goto Fail;
34793486
}
34803487

34813488
if (PyDict_CheckExact(d)) {
3489+
int status = 0;
3490+
34823491
Py_BEGIN_CRITICAL_SECTION(d);
3483-
while ((key = PyIter_Next(it)) != NULL) {
3492+
while (1) {
3493+
PyObject *key;
3494+
status = PyIter_NextItem(it, &key);
3495+
if (status <= 0) {
3496+
break;
3497+
}
3498+
34843499
status = setitem_lock_held((PyDictObject *)d, key, value);
34853500
Py_DECREF(key);
34863501
if (status < 0) {
3487-
assert(PyErr_Occurred());
3488-
goto dict_iter_exit;
3502+
break;
34893503
}
34903504
}
3491-
dict_iter_exit:;
34923505
Py_END_CRITICAL_SECTION();
3506+
3507+
if (status < 0) {
3508+
goto Fail;
3509+
}
34933510
}
34943511
else if (PyFrozenDict_Check(d)) {
3495-
while ((key = PyIter_Next(it)) != NULL) {
3512+
while (1) {
3513+
PyObject *key;
3514+
int status = PyIter_NextItem(it, &key);
3515+
if (status < 0) {
3516+
goto Fail;
3517+
}
3518+
if (status == 0) {
3519+
break;
3520+
}
3521+
34963522
// setitem_take2_lock_held consumes a reference to key
34973523
status = setitem_take2_lock_held((PyDictObject *)d,
34983524
key, Py_NewRef(value));
34993525
if (status < 0) {
3500-
assert(PyErr_Occurred());
35013526
goto Fail;
35023527
}
35033528
}
35043529
}
35053530
else {
3506-
while ((key = PyIter_Next(it)) != NULL) {
3531+
while (1) {
3532+
PyObject *key;
3533+
int status = PyIter_NextItem(it, &key);
3534+
if (status < 0) {
3535+
goto Fail;
3536+
}
3537+
if (status == 0) {
3538+
break;
3539+
}
3540+
35073541
status = PyObject_SetItem(d, key, value);
35083542
Py_DECREF(key);
3509-
if (status < 0)
3543+
if (status < 0) {
35103544
goto Fail;
3545+
}
35113546
}
3547+
35123548
}
35133549

3514-
if (PyErr_Occurred())
3515-
goto Fail;
3550+
assert(!PyErr_Occurred());
35163551
Py_DECREF(it);
3517-
return d;
3552+
goto Done;
35183553

35193554
Fail:
3520-
Py_DECREF(it);
3555+
assert(PyErr_Occurred());
3556+
Py_XDECREF(it);
35213557
Py_DECREF(d);
35223558
return NULL;
3559+
3560+
Done:
3561+
if (d == NULL) {
3562+
return NULL;
3563+
}
3564+
3565+
if (need_copy) {
3566+
PyObject *copy = _PyObject_CallOneArg(cls, d);
3567+
Py_SETREF(d, copy);
3568+
}
3569+
else if (!_PyObject_GC_IS_TRACKED(d)) {
3570+
_PyObject_GC_TRACK(d);
3571+
}
3572+
return d;
35233573
}
35243574

35253575
/* Methods */
@@ -4120,9 +4170,6 @@ dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override, PyObject **
41204170
set_keys(mp, keys);
41214171
STORE_USED(mp, other->ma_used);
41224172
ASSERT_CONSISTENT(mp);
4123-
if (PyDict_Check(mp)) {
4124-
assert(_PyObject_GC_IS_TRACKED(mp));
4125-
}
41264173
return 0;
41274174
}
41284175
}
@@ -4289,7 +4336,12 @@ dict_merge_api(PyObject *a, PyObject *b, int override, PyObject **dupkey)
42894336
}
42904337
return -1;
42914338
}
4292-
return dict_merge(a, b, override, dupkey);
4339+
4340+
int res = dict_merge(a, b, override, dupkey);
4341+
if (PyDict_Check(a)) {
4342+
assert(_PyObject_GC_IS_TRACKED(a));
4343+
}
4344+
return res;
42934345
}
42944346

42954347
int
@@ -4448,10 +4500,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
44484500
}
44494501
if (copy == NULL)
44504502
return NULL;
4451-
if (dict_merge(copy, o, 1, NULL) == 0)
4452-
return copy;
4453-
Py_DECREF(copy);
4454-
return NULL;
4503+
if (dict_merge(copy, o, 1, NULL) < 0) {
4504+
Py_DECREF(copy);
4505+
return NULL;
4506+
}
4507+
4508+
if (PyDict_Check(copy)) {
4509+
assert(_PyObject_GC_IS_TRACKED(copy));
4510+
}
4511+
return copy;
44554512
}
44564513

44574514
PyObject *
@@ -5212,11 +5269,11 @@ static PyNumberMethods dict_as_number = {
52125269
.nb_inplace_or = _PyDict_IOr,
52135270
};
52145271

5215-
static PyObject *
5216-
dict_new_untracked(PyTypeObject *type)
5272+
static PyObject*
5273+
anydict_new_untracked(PyTypeObject *type)
52175274
{
52185275
assert(type != NULL);
5219-
// dict subclasses must implement the GC protocol
5276+
// dict and frozendict subclasses must implement the GC protocol
52205277
assert(_PyType_IS_GC(type));
52215278

52225279
PyObject *self = _PyType_AllocNoTrack(type, 0);
@@ -5235,6 +5292,14 @@ dict_new_untracked(PyTypeObject *type)
52355292
return self;
52365293
}
52375294

5295+
static PyObject*
5296+
dict_new_untracked(PyTypeObject *type)
5297+
{
5298+
assert(PyObject_IsSubclass((PyObject*)type, (PyObject*)&PyDict_Type));
5299+
5300+
return anydict_new_untracked(type);
5301+
}
5302+
52385303
static PyObject *
52395304
dict_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds))
52405305
{
@@ -8323,7 +8388,9 @@ frozendict_hash(PyObject *op)
83238388
static PyObject *
83248389
frozendict_new_untracked(PyTypeObject *type)
83258390
{
8326-
PyObject *d = dict_new_untracked(type);
8391+
assert(PyObject_IsSubclass((PyObject*)type, (PyObject*)&PyFrozenDict_Type));
8392+
8393+
PyObject *d = anydict_new_untracked(type);
83278394
if (d == NULL) {
83288395
return NULL;
83298396
}

0 commit comments

Comments
 (0)