Skip to content

Commit 55bc312

Browse files
vstinnercorona10methane
authored
gh-151722: Do not track the frozendict in the GC in _PyDict_FromKeys() (#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. Co-authored-by: Donghee Na <donghee.na@python.org> Co-authored-by: Inada Naoki <songofacandy@gmail.com>
1 parent bef5706 commit 55bc312

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);
@@ -3431,40 +3432,47 @@ dict_set_fromkeys(PyDictObject *mp, PyObject *iterable, PyObject *value)
34313432
PyObject *
34323433
_PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34333434
{
3434-
PyObject *it; /* iter(iterable) */
3435-
PyObject *key;
3435+
PyObject *it = NULL; /* iter(iterable) */
34363436
PyObject *d;
3437-
int status;
3437+
int need_copy = 0;
34383438

3439-
d = _PyObject_CallNoArgs(cls);
3439+
if (cls == (PyObject*)&PyFrozenDict_Type) {
3440+
// gh-151722: Create a frozendict which is not tracked by the GC.
3441+
d = frozendict_new_untracked(&PyFrozenDict_Type);
3442+
}
3443+
else {
3444+
// Dict subclass, or frozendict subclass which overrides
3445+
// the constructor.
3446+
d = _PyObject_CallNoArgs(cls);
3447+
}
34403448
if (d == NULL) {
34413449
return NULL;
34423450
}
34433451

3444-
// If cls is a dict or frozendict subclass with overridden constructor,
3445-
// copy the frozendict.
3446-
PyTypeObject *cls_type = _PyType_CAST(cls);
3447-
if (PyFrozenDict_Check(d) && cls_type->tp_new != frozendict_new) {
3448-
// Subclass-friendly copy
3449-
PyObject *copy;
3450-
if (PyObject_IsSubclass(cls, (PyObject*)&PyFrozenDict_Type)) {
3451-
copy = frozendict_new(cls_type, NULL, NULL);
3452-
}
3453-
else {
3454-
copy = dict_new(cls_type, NULL, NULL);
3455-
}
3452+
// gh-151722: If cls constructor returns a frozendict which is tracked by
3453+
// the GC, create a frozendict copy which is not tracked by the GC.
3454+
//
3455+
// At the function exit, return cls(fd) where fd is a frozendict.
3456+
//
3457+
// Untracking the frozendict requires tracking again the frozendict on
3458+
// error which is more complicated. It's easier to work on a copy.
3459+
if (PyFrozenDict_Check(d) && _PyObject_GC_IS_TRACKED(d)) {
3460+
need_copy = 1;
3461+
3462+
PyObject *copy = frozendict_new_untracked(&PyFrozenDict_Type);
34563463
if (copy == NULL) {
3457-
Py_DECREF(d);
3458-
return NULL;
3464+
goto Fail;
34593465
}
34603466
if (dict_merge(copy, d, 1, NULL) < 0) {
3461-
Py_DECREF(d);
34623467
Py_DECREF(copy);
3463-
return NULL;
3468+
goto Fail;
34643469
}
34653470
Py_SETREF(d, copy);
34663471
}
3467-
assert(!PyFrozenDict_Check(d) || can_modify_dict((PyDictObject*)d));
3472+
if (PyFrozenDict_Check(d)) {
3473+
assert(can_modify_dict((PyDictObject*)d));
3474+
assert(!_PyObject_GC_IS_TRACKED(d));
3475+
}
34683476

34693477
if (PyDict_CheckExact(d)) {
34703478
if (PyDict_CheckExact(iterable)) {
@@ -3473,23 +3481,23 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34733481
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
34743482
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
34753483
Py_END_CRITICAL_SECTION2();
3476-
return d;
3484+
goto Done;
34773485
}
34783486
else if (PyFrozenDict_CheckExact(iterable)) {
34793487
PyDictObject *mp = (PyDictObject *)d;
34803488

34813489
Py_BEGIN_CRITICAL_SECTION(d);
34823490
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
34833491
Py_END_CRITICAL_SECTION();
3484-
return d;
3492+
goto Done;
34853493
}
34863494
else if (PyAnySet_CheckExact(iterable)) {
34873495
PyDictObject *mp = (PyDictObject *)d;
34883496

34893497
Py_BEGIN_CRITICAL_SECTION2(d, iterable);
34903498
d = (PyObject *)dict_set_fromkeys(mp, iterable, value);
34913499
Py_END_CRITICAL_SECTION2();
3492-
return d;
3500+
goto Done;
34933501
}
34943502
}
34953503
else if (PyFrozenDict_CheckExact(d)) {
@@ -3499,71 +3507,113 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value)
34993507
Py_BEGIN_CRITICAL_SECTION(iterable);
35003508
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
35013509
Py_END_CRITICAL_SECTION();
3502-
return d;
3510+
goto Done;
35033511
}
35043512
else if (PyFrozenDict_CheckExact(iterable)) {
35053513
PyDictObject *mp = (PyDictObject *)d;
35063514
d = (PyObject *)dict_dict_fromkeys(mp, iterable, value);
3507-
return d;
3515+
goto Done;
35083516
}
35093517
else if (PyAnySet_CheckExact(iterable)) {
35103518
PyDictObject *mp = (PyDictObject *)d;
35113519

35123520
Py_BEGIN_CRITICAL_SECTION(iterable);
35133521
d = (PyObject *)dict_set_fromkeys(mp, iterable, value);
35143522
Py_END_CRITICAL_SECTION();
3515-
return d;
3523+
goto Done;
35163524
}
35173525
}
35183526

35193527
it = PyObject_GetIter(iterable);
35203528
if (it == NULL){
3521-
Py_DECREF(d);
3522-
return NULL;
3529+
goto Fail;
35233530
}
35243531

35253532
if (PyDict_CheckExact(d)) {
3533+
int status = 0;
3534+
35263535
Py_BEGIN_CRITICAL_SECTION(d);
3527-
while ((key = PyIter_Next(it)) != NULL) {
3536+
while (1) {
3537+
PyObject *key;
3538+
status = PyIter_NextItem(it, &key);
3539+
if (status <= 0) {
3540+
break;
3541+
}
3542+
35283543
status = setitem_lock_held((PyDictObject *)d, key, value);
35293544
Py_DECREF(key);
35303545
if (status < 0) {
3531-
assert(PyErr_Occurred());
3532-
goto dict_iter_exit;
3546+
break;
35333547
}
35343548
}
3535-
dict_iter_exit:;
35363549
Py_END_CRITICAL_SECTION();
3550+
3551+
if (status < 0) {
3552+
goto Fail;
3553+
}
35373554
}
35383555
else if (PyFrozenDict_Check(d)) {
3539-
while ((key = PyIter_Next(it)) != NULL) {
3556+
while (1) {
3557+
PyObject *key;
3558+
int status = PyIter_NextItem(it, &key);
3559+
if (status < 0) {
3560+
goto Fail;
3561+
}
3562+
if (status == 0) {
3563+
break;
3564+
}
3565+
35403566
// setitem_take2_lock_held consumes a reference to key
35413567
status = setitem_take2_lock_held((PyDictObject *)d,
35423568
key, Py_NewRef(value));
35433569
if (status < 0) {
3544-
assert(PyErr_Occurred());
35453570
goto Fail;
35463571
}
35473572
}
35483573
}
35493574
else {
3550-
while ((key = PyIter_Next(it)) != NULL) {
3575+
while (1) {
3576+
PyObject *key;
3577+
int status = PyIter_NextItem(it, &key);
3578+
if (status < 0) {
3579+
goto Fail;
3580+
}
3581+
if (status == 0) {
3582+
break;
3583+
}
3584+
35513585
status = PyObject_SetItem(d, key, value);
35523586
Py_DECREF(key);
3553-
if (status < 0)
3587+
if (status < 0) {
35543588
goto Fail;
3589+
}
35553590
}
3591+
35563592
}
35573593

3558-
if (PyErr_Occurred())
3559-
goto Fail;
3594+
assert(!PyErr_Occurred());
35603595
Py_DECREF(it);
3561-
return d;
3596+
goto Done;
35623597

35633598
Fail:
3564-
Py_DECREF(it);
3599+
assert(PyErr_Occurred());
3600+
Py_XDECREF(it);
35653601
Py_DECREF(d);
35663602
return NULL;
3603+
3604+
Done:
3605+
if (d == NULL) {
3606+
return NULL;
3607+
}
3608+
3609+
if (need_copy) {
3610+
PyObject *copy = _PyObject_CallOneArg(cls, d);
3611+
Py_SETREF(d, copy);
3612+
}
3613+
else if (!_PyObject_GC_IS_TRACKED(d)) {
3614+
_PyObject_GC_TRACK(d);
3615+
}
3616+
return d;
35673617
}
35683618

35693619
/* Methods */
@@ -4164,9 +4214,6 @@ dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override, PyObject **
41644214
set_keys(mp, keys);
41654215
STORE_USED(mp, other->ma_used);
41664216
ASSERT_CONSISTENT(mp);
4167-
if (PyDict_Check(mp)) {
4168-
assert(_PyObject_GC_IS_TRACKED(mp));
4169-
}
41704217
return 0;
41714218
}
41724219
}
@@ -4333,7 +4380,12 @@ dict_merge_api(PyObject *a, PyObject *b, int override, PyObject **dupkey)
43334380
}
43344381
return -1;
43354382
}
4336-
return dict_merge(a, b, override, dupkey);
4383+
4384+
int res = dict_merge(a, b, override, dupkey);
4385+
if (PyDict_Check(a)) {
4386+
assert(_PyObject_GC_IS_TRACKED(a));
4387+
}
4388+
return res;
43374389
}
43384390

43394391
int
@@ -4492,10 +4544,15 @@ copy_lock_held(PyObject *o, int as_frozendict)
44924544
}
44934545
if (copy == NULL)
44944546
return NULL;
4495-
if (dict_merge(copy, o, 1, NULL) == 0)
4496-
return copy;
4497-
Py_DECREF(copy);
4498-
return NULL;
4547+
if (dict_merge(copy, o, 1, NULL) < 0) {
4548+
Py_DECREF(copy);
4549+
return NULL;
4550+
}
4551+
4552+
if (PyDict_Check(copy)) {
4553+
assert(_PyObject_GC_IS_TRACKED(copy));
4554+
}
4555+
return copy;
44994556
}
45004557

45014558
PyObject *
@@ -5256,11 +5313,11 @@ static PyNumberMethods dict_as_number = {
52565313
.nb_inplace_or = _PyDict_IOr,
52575314
};
52585315

5259-
static PyObject *
5260-
dict_new_untracked(PyTypeObject *type)
5316+
static PyObject*
5317+
anydict_new_untracked(PyTypeObject *type)
52615318
{
52625319
assert(type != NULL);
5263-
// dict subclasses must implement the GC protocol
5320+
// dict and frozendict subclasses must implement the GC protocol
52645321
assert(_PyType_IS_GC(type));
52655322

52665323
PyObject *self = _PyType_AllocNoTrack(type, 0);
@@ -5279,6 +5336,14 @@ dict_new_untracked(PyTypeObject *type)
52795336
return self;
52805337
}
52815338

5339+
static PyObject*
5340+
dict_new_untracked(PyTypeObject *type)
5341+
{
5342+
assert(PyObject_IsSubclass((PyObject*)type, (PyObject*)&PyDict_Type));
5343+
5344+
return anydict_new_untracked(type);
5345+
}
5346+
52825347
static PyObject *
52835348
dict_new(PyTypeObject *type, PyObject *Py_UNUSED(args), PyObject *Py_UNUSED(kwds))
52845349
{
@@ -8394,7 +8459,9 @@ frozendict_hash(PyObject *op)
83948459
static PyObject *
83958460
frozendict_new_untracked(PyTypeObject *type)
83968461
{
8397-
PyObject *d = dict_new_untracked(type);
8462+
assert(PyObject_IsSubclass((PyObject*)type, (PyObject*)&PyFrozenDict_Type));
8463+
8464+
PyObject *d = anydict_new_untracked(type);
83988465
if (d == NULL) {
83998466
return NULL;
84008467
}

0 commit comments

Comments
 (0)