Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Crash on deallocator of type 'time.struct_time' during finalizing for free-threaded Python #122527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
XuehaiPan opened this issue Jul 31, 2024 · 4 comments
Assignees
Labels
3.13 bugs and security fixes 3.14 bugs and security fixes topic-free-threading type-crash A hard crash of the interpreter, possibly with a core dump

Comments

@XuehaiPan
Copy link
Contributor

XuehaiPan commented Jul 31, 2024

Crash report

What happened?

A simple standalone file:

# test1.py

import time

obj = time.struct_time(range(1, 10))
$ pyenv install 3.14t-dev --debug
$ pyenv local 3.14t-dev-debug
$ python3 -VV
Python 3.14.0a0 experimental free-threading build (heads/main:bd3d31f, Aug  1 2024, 01:22:33) [Clang 15.0.0 (clang-1500.3.9.4)]
$ python3 test1.py
Fatal Python error: _Py_Dealloc: Deallocator of type 'time.struct_time' raised an exception
Python runtime state: finalizing (tstate=0x00000001018ca548)
Exception ignored in the internal traceback machinery:
ImportError: sys.meta_path is None, Python is likely shutting down
TypeError: Missed attribute 'n_fields' of type time.struct_time

Current thread 0x00000001fff70c00 (most recent call first):
  Garbage-collecting
  <no Python frame>
[1]    89377 abort      python3 test1.py
$ python3 -c 'import test1'
Fatal Python error: _Py_Dealloc: Deallocator of type 'time.struct_time' raised an exception
Python runtime state: finalizing (tstate=0x00000001040a2548)
Exception ignored in the internal traceback machinery:
ImportError: sys.meta_path is None, Python is likely shutting down
TypeError: Missed attribute 'n_fields' of type time.struct_time

Current thread 0x00000001fff70c00 (most recent call first):
  Garbage-collecting
  <no Python frame>
[1]    89737 abort      python3 -c 'import test1'

If I remove the assignment for obj, the program can exit successfully.

# test2.py

import time

time.struct_time(range(1, 10))
$ python3 test2.py           # no output
$ python3 -c 'import test2'  # no output

CPython versions tested on:

CPython main branch, CPython 3.13t, CPython 3.14t

Operating systems tested on:

Linux, macOS

Output from running 'python -VV' on the command line:

Python 3.14.0a0 experimental free-threading build (heads/main:bd3d31f, Aug 1 2024, 01:22:33) [Clang 15.0.0 (clang-1500.3.9.4)]

Linked PRs

@XuehaiPan XuehaiPan added the type-crash A hard crash of the interpreter, possibly with a core dump label Jul 31, 2024
@Eclips4
Copy link
Member

Eclips4 commented Jul 31, 2024

Thanks for the report!
Confirmed on current main.

@colesbury colesbury self-assigned this Jul 31, 2024
@colesbury colesbury added 3.13 bugs and security fixes 3.14 bugs and security fixes labels Jul 31, 2024
@colesbury
Copy link
Contributor

@colesbury
Copy link
Contributor

The problem is in structseq_dealloc, the REAL_SIZE() call fails:

cpython/Objects/structseq.c

Lines 116 to 131 in 8844197

static void
structseq_dealloc(PyStructSequence *obj)
{
Py_ssize_t i, size;
PyObject_GC_UnTrack(obj);
PyTypeObject *tp = Py_TYPE(obj);
size = REAL_SIZE(obj);
for (i = 0; i < size; ++i) {
Py_XDECREF(obj->ob_item[i]);
}
PyObject_GC_Del(obj);
if (_PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) {
Py_DECREF(tp);
}
}

REAL_SIZE is a macro that expands to:

PyDict_GetItemWithError(_PyType_GetDict(Py_TYPE(self)), _Py_ID(n_fields))

The GC is clearing the type's dictionary (i.e., _PyType_GetDict) before the structseq instances is freed, so the REAL_SIZE call fails. We're also not handling that failure, which leads to a crash.

The default build works because the GC happens to free the structseq instance first before calling tp_clear() on the type's dictionary, but I don't think the GC guarantees any ordering of tp_clear() calls.

@colesbury
Copy link
Contributor

colesbury commented Jul 31, 2024

You can trigger a similar crash in the default build with some weirder code:

import time
obj = time.struct_time(range(1, 10))
type(obj).obj = obj

colesbury added a commit to colesbury/cpython that referenced this issue Aug 1, 2024
The `PyStructSequence` destructor would crash if it was deallocated after
its type's dictionary was cleared by the GC, because it couldn't compute
the "real size" of the instance. This could occur with relatively
straightforward code in the free-threaded build or with a reference
cycle involving the type in the default build, due to differing orders
in which `tp_clear()` was called.

Account for the non-sequence fields in `tp_basicsize` and use that,
along with `Py_SIZE()`, to compute the "real" size of a
`PyStructSequence` in the dealloc function. This avoids the accesses to
the type's dictionary during dealloc, which were unsafe.
encukou pushed a commit that referenced this issue Aug 2, 2024
The `PyStructSequence` destructor would crash if it was deallocated after
its type's dictionary was cleared by the GC, because it couldn't compute
the "real size" of the instance. This could occur with relatively
straightforward code in the free-threaded build or with a reference
cycle involving the type in the default build, due to differing orders
in which `tp_clear()` was called.

Account for the non-sequence fields in `tp_basicsize` and use that,
along with `Py_SIZE()`, to compute the "real" size of a
`PyStructSequence` in the dealloc function. This avoids the accesses to
the type's dictionary during dealloc, which were unsafe.
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Aug 2, 2024
…ythonGH-122577)

The `PyStructSequence` destructor would crash if it was deallocated after
its type's dictionary was cleared by the GC, because it couldn't compute
the "real size" of the instance. This could occur with relatively
straightforward code in the free-threaded build or with a reference
cycle involving the type in the default build, due to differing orders
in which `tp_clear()` was called.

Account for the non-sequence fields in `tp_basicsize` and use that,
along with `Py_SIZE()`, to compute the "real" size of a
`PyStructSequence` in the dealloc function. This avoids the accesses to
the type's dictionary during dealloc, which were unsafe.
(cherry picked from commit 4b63cd1)

Co-authored-by: Sam Gross <[email protected]>
brandtbucher pushed a commit to brandtbucher/cpython that referenced this issue Aug 7, 2024
…ythonGH-122577)

The `PyStructSequence` destructor would crash if it was deallocated after
its type's dictionary was cleared by the GC, because it couldn't compute
the "real size" of the instance. This could occur with relatively
straightforward code in the free-threaded build or with a reference
cycle involving the type in the default build, due to differing orders
in which `tp_clear()` was called.

Account for the non-sequence fields in `tp_basicsize` and use that,
along with `Py_SIZE()`, to compute the "real" size of a
`PyStructSequence` in the dealloc function. This avoids the accesses to
the type's dictionary during dealloc, which were unsafe.
blhsing pushed a commit to blhsing/cpython that referenced this issue Aug 22, 2024
…ythonGH-122577)

The `PyStructSequence` destructor would crash if it was deallocated after
its type's dictionary was cleared by the GC, because it couldn't compute
the "real size" of the instance. This could occur with relatively
straightforward code in the free-threaded build or with a reference
cycle involving the type in the default build, due to differing orders
in which `tp_clear()` was called.

Account for the non-sequence fields in `tp_basicsize` and use that,
along with `Py_SIZE()`, to compute the "real" size of a
`PyStructSequence` in the dealloc function. This avoids the accesses to
the type's dictionary during dealloc, which were unsafe.
Yhg1s pushed a commit that referenced this issue Sep 2, 2024
…H-122577) (#122625)

gh-122527: Fix a crash on deallocation of `PyStructSequence` (GH-122577)

The `PyStructSequence` destructor would crash if it was deallocated after
its type's dictionary was cleared by the GC, because it couldn't compute
the "real size" of the instance. This could occur with relatively
straightforward code in the free-threaded build or with a reference
cycle involving the type in the default build, due to differing orders
in which `tp_clear()` was called.

Account for the non-sequence fields in `tp_basicsize` and use that,
along with `Py_SIZE()`, to compute the "real" size of a
`PyStructSequence` in the dealloc function. This avoids the accesses to
the type's dictionary during dealloc, which were unsafe.
(cherry picked from commit 4b63cd1)

Co-authored-by: Sam Gross <[email protected]>
ambv pushed a commit that referenced this issue Sep 6, 2024
…H-122577) (#122626)

The `PyStructSequence` destructor would crash if it was deallocated after
its type's dictionary was cleared by the GC, because it couldn't compute
the "real size" of the instance. This could occur with relatively
straightforward code in the free-threaded build or with a reference
cycle involving the type in the default build, due to differing orders
in which `tp_clear()` was called.

Account for the non-sequence fields in `tp_basicsize` and use that,
along with `Py_SIZE()`, to compute the "real" size of a
`PyStructSequence` in the dealloc function. This avoids the accesses to
the type's dictionary during dealloc, which were unsafe.
(cherry picked from commit 4b63cd1)
@ambv ambv closed this as completed Sep 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.13 bugs and security fixes 3.14 bugs and security fixes topic-free-threading type-crash A hard crash of the interpreter, possibly with a core dump
Projects
None yet
Development

No branches or pull requests

4 participants