Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(time): make struct_time type immutable
  • Loading branch information
ashm-dev committed Oct 6, 2026
commit a5a15b2755e08369b4e4a5b40330e63e2f561b91
4 changes: 0 additions & 4 deletions Doc/c-api/tuple.rst
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,6 @@ type.
Creates an instance of *type*, which must have been created with
:c:func:`PyStructSequence_NewType`.

The returned instance is not tracked by the garbage collector. If the
instance can contain objects that participate in reference cycles, call
:c:func:`PyObject_GC_Track` after populating its fields.

Return ``NULL`` with an exception set on failure.


Expand Down
16 changes: 7 additions & 9 deletions Lib/test/test_structseq.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,9 @@ def test_reference_cycle(self):
# was cleared first, the structseq instance would crash in the
# destructor.
script_helper.assert_python_ok("-c", textwrap.dedent(r"""
import time
t = time.gmtime()
type(t).refcyle = t
import os
t = os.stat('.')
type(t).refcycle = t
"""))

def test_replace_gc_tracked(self):
Expand All @@ -365,12 +365,10 @@ def test_replace_gc_tracked(self):

self.assertTrue(gc.is_tracked(replaced_struct))

def test_gc_tracked(self):
# Instances created via Python or stdlib callers that may participate
# in cycles are GC-tracked; simple instances like os.stat_result are not.
self.assertTrue(gc.is_tracked(time.gmtime()))
self.assertTrue(gc.is_tracked(time.struct_time((2020, 1, 1, 0, 0, 0, 0, 1, 0))))
self.assertFalse(gc.is_tracked(os.stat(__file__)))
def test_struct_time_type_immutable(self):
t = time.gmtime()
with self.assertRaisesRegex(TypeError, "immutable type"):
Comment thread
ashm-dev marked this conversation as resolved.
Outdated
type(t).refcycle = t

if __name__ == "__main__":
unittest.main()
1 change: 0 additions & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1969,7 +1969,6 @@ def test_asyncgen_hooks(self):

sys.set_asyncgen_hooks(firstiter=firstiter)
hooks = sys.get_asyncgen_hooks()
self.assertTrue(gc.is_tracked(hooks))
self.assertIs(hooks.firstiter, firstiter)
self.assertIs(hooks[0], firstiter)
self.assertIs(hooks.finalizer, None)
Expand Down

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in Library.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! I didn't pay attention to the section of the Changelog entry. Sadly, I saw your comment after the PR was merged.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote #158983 to move it.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
Track :func:`time.struct_time` and :func:`sys.get_asyncgen_hooks` objects in the garbage collector to prevent reference cycle leaks. Patch by Shamil Abdulaev.
Make the :class:`time.struct_time` type immutable to prevent reference cycles between the type and its instances. Patch by Shamil Abdulaev.
5 changes: 3 additions & 2 deletions Modules/timemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "pycore_moduleobject.h" // _PyModule_GetState()
#include "pycore_namespace.h" // _PyNamespace_New()
#include "pycore_runtime.h" // _Py_ID()
#include "pycore_structseq.h" // _PyStructSequence_NewType()
#include "pycore_time.h" // _PyTimeFraction

#include <time.h> // clock()
Expand Down Expand Up @@ -497,7 +498,6 @@ tmtotuple(time_module_state *state, struct tm *p
#undef SET
#undef SET_ITEM

PyObject_GC_Track(v);
return v;
}

Expand Down Expand Up @@ -2111,7 +2111,8 @@ time_exec(PyObject *module)
}

// struct_time type
state->struct_time_type = PyStructSequence_NewType(&struct_time_type_desc);
state->struct_time_type = _PyStructSequence_NewType(
&struct_time_type_desc, Py_TPFLAGS_IMMUTABLETYPE);
if (state->struct_time_type == NULL) {
return -1;
}
Expand Down
1 change: 0 additions & 1 deletion Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1531,7 +1531,6 @@ sys_get_asyncgen_hooks_impl(PyObject *module)
PyStructSequence_SET_ITEM(res, 0, Py_NewRef(firstiter));
PyStructSequence_SET_ITEM(res, 1, Py_NewRef(finalizer));

PyObject_GC_Track(res);
return res;
}

Expand Down
Loading