python 3.14t crash with a cdef extension module

32 views
Skip to first unread message

Peter Schay

unread,
Apr 21, 2026, 5:41:31 PM (13 days ago) Apr 21
to cython...@googlegroups.com
Hi,
Firstly, thanks for all the incredible work on Cython.  It's amazing.  

I'm trying to figure out a crash that seems to require both 3.14t and Cython.

I have not been able to boil this down to a standalone test case yet.  Hopefully, the following clues suggest a root cause:

- The crash occurs when calling traceback.TracebackException.from_exception()
- This crash occurs even when I run with PYTHON_GIL=1 (I verified that sys._is_gil_enabled() is True in this case)
- The crash does not occur when I rename module "t.pyx" to pure python "t.py"
- The crash does not occur when I build python without --disable-gil
- This behavior is consistent on both macOS and Linux
- Line 138 in linecache.py is "os.stat(fullname)".  If I intercept os.stat and just try to print the path name object, that causes the same crash.

Here is the output when the exception handling part of my app calls TracebackException.from_exception() ; naturally this is not the traceback I was looking for :-) 

hello from foo function
Fatal Python error: PyMutex_Unlock: unlocking mutex that is not locked
Python runtime state: initialized

Stack (most recent call first):
  File ".../local/lib/python3.14t/linecache.py", line 138 in updatecache
  File ".../local/lib/python3.14t/linecache.py", line 41 in getlines
  File ".../local/lib/python3.14t/linecache.py", line 26 in getline
  File ".../local/lib/python3.14t/traceback.py", line 353 in _set_lines
  File ".../local/lib/python3.14t/traceback.py", line 375 in line
  File ".../local/lib/python3.14t/traceback.py", line 506 in _extract_from_extended_frame_gen
  File ".../local/lib/python3.14t/traceback.py", line 1059 in __init__
  File ".../local/lib/python3.14t/traceback.py", line 1193 in from_exception
  File ".../source/npack/core/error.py", line 327 in save_traceback
  File ".../source/npack/core/error.py", line 442 in onto_exception
  File ".../source/npack/core/aio/task.py", line 260 in _completion_callback
  File ".../local/lib/python3.14t/asyncio/events.py", line 94 in _run
  File ".../local/lib/python3.14t/asyncio/base_events.py", line 2057 in _run_once
  File ".../local/lib/python3.14t/asyncio/base_events.py", line 677 in run_forever
  File ".../local/lib/python3.14t/asyncio/base_events.py", line 706 in run_until_complete
  File ".../local/lib/python3.14t/asyncio/runners.py", line 127 in run
  File ".../local/lib/python3.14t/asyncio/runners.py", line 204 in run
  File ".../source/npack/core/aio/engine.py", line 94 in wait
  File ".../source/npack/core/main.py", line 210 in main

This is the module that leads to the crash when it's cdef (named t.pyx and compiled to a .so), and there is no crash when this module named t.py.  

The error handling code elsewhere in the app tries to get a traceback from the exception raised here:


from npack.core import CoreTask, Spec
from npack.xfiles import Command
from npack.npack_app import command_guide


def foo():
    print(f'hello from foo function')
    raise ValueError('bad')


class Foo(CoreTask):
    async def run(self):
        foo()


class FooCommand(Command):
    spec = Spec(
        'foo', 'Test failure',
        example='{source}',
        allowed_opts=[])

    def run(self):
        return Foo()

command_guide.guide.add([FooCommand])


I am very excited to finally use free-threaded mode and it makes a big difference for many things in my app, so I would hate to revert to using the GIL.  

If you have any ideas I would greatly appreciate it!  If not, I will keep trying to create a small test case. 

Thanks!!
Pete

da-woods

unread,
Apr 22, 2026, 3:18:04 AM (13 days ago) Apr 22
to cython...@googlegroups.com

Hi Pete,

The root cause definitely isn't obvious. In that the filename should just be a string and typically they don't have any locking in because they're immutable.

If I were trying to investigate this I'd probably try to catch it in a C debugger (e.g. compile with `-O0 -g` and run inside gdb). I think that the debugger should stop at the fatal Python error. That might fill in the gap about what operation was it was trying to do between `os.stat` and unlocking the mutex.

What happens if you don't print the path name object, but try to print information about the path name object? (e.g. `type(fullname)`)? If that doesn't work, what happens if you print a fixed string like "hello"?

It seems reasonably likely that some internal state is getting corrupted somewhere which could be Cython's fault or might not be. So the problem might end up being really non-obvious and detached from the location.

David

--

---
You received this message because you are subscribed to the Google Groups "cython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cython-users...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/cython-users/CALKyFja%2Bjw2zkh%3DJZunVt-ACT8jB_We_9DLhYPqctvxVZdmGMA%40mail.gmail.com.

Peter Schay

unread,
Apr 22, 2026, 1:13:01 PM (12 days ago) Apr 22
to cython...@googlegroups.com
Hi David,
Thank you for the reply and suggestions.  I agree that the failure at the stat call seems to have been just a symptom.
I rebuilt Python with -O0 -g and no PGO, and also I managed to whittle my code down to a small test case causing the problem!

The following small "t3.pyx" program, when cythonized and imported, causes Python to crash when using the free-threading build.  I've attached output of the gdb run with a backtrace.
No crash when it's "t3.py".
python -VV: Python 3.14.4 free-threading build (main, Apr 22 2026, 08:10:58) [Clang 21.1.8 (++20251212115320+2078da43e25a-1~exp1~20251212115344.67)]
This run was ubuntu; however it behaves similarly on macOS.

What do you think?  Is this something that can be fixed in Cython, or Python, or both?  Can you recommend any workaround for me?  I think it also happens when the task is in pure python and the exception occurs in a simple Cython cdef class method or cdef function, but I'll have to check that.  

t3.pyx:

import asyncio
import traceback

async def foo():
    print('hello from foo')
    raise ValueError('bad')

def _completion_callback(task):
    if task.cancelled():
        return
    if not (e := task.exception()):
        return
    print(f'@@ capturing traceback')
    tbe = traceback.TracebackException.from_exception(e, compact=True)
    print(f'@@ getting stack of {task}')
    stack = task.get_stack()
    print(f'@@ got it')

async def amain():
    t = asyncio.Task(foo())
    t.add_done_callback(_completion_callback)
    print('hello')
    await t

asyncio.run(amain())

pete@flux:~/build_npack/source$ python -c 'import t3'
hello
hello from foo
@@ capturing traceback
@@ getting stack of <Task finished name='Task-2' coro=<<coroutine without __name__>()> exception=ValueError('bad')>

Fatal Python error: PyMutex_Unlock: unlocking mutex that is not locked
Python runtime state: initialized

Stack (most recent call first):
  File "/home/pete/build_npack/local/lib/python3.14t/asyncio/events.py", line 94 in _run
  File "/home/pete/build_npack/local/lib/python3.14t/asyncio/base_events.py", line 2057 in _run_once
  File "/home/pete/build_npack/local/lib/python3.14t/asyncio/base_events.py", line 677 in run_forever
  File "/home/pete/build_npack/local/lib/python3.14t/asyncio/base_events.py", line 706 in run_until_complete
  File "/home/pete/build_npack/local/lib/python3.14t/asyncio/runners.py", line 127 in run
  File "/home/pete/build_npack/local/lib/python3.14t/asyncio/runners.py", line 204 in run
  File "<frozen importlib._bootstrap>", line 491 in _call_with_frames_removed
  File "<frozen importlib._bootstrap_external>", line 1061 in exec_module
  File "<frozen importlib._bootstrap>", line 938 in _load_unlocked
  File "<frozen importlib._bootstrap>", line 1342 in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 1371 in _find_and_load
  File "<string>", line 1 in <module>

Extension modules: t3 (total: 1)
Exception ignored while flushing sys.stdout:
Traceback (most recent call last):
  File "/home/pete/build_npack/local/lib/python3.14t/asyncio/events.py", line 94, in _run
    self._context.run(self._callback, *self._args)
RuntimeError: reentrant call inside <_io.BufferedWriter name='<stdout>'>
Aborted (core dumped)



Thank you,
Pete


gdb.txt

da-woods

unread,
Apr 22, 2026, 2:36:53 PM (12 days ago) Apr 22
to cython...@googlegroups.com

That's a bug in the Cython coroutine code. My fault (I think). Thanks for the report.

Will be fixed in https://github.com/cython/cython/pull/7632

David

Peter Schay

unread,
Apr 22, 2026, 3:50:44 PM (12 days ago) Apr 22
to cython...@googlegroups.com
Great news!  Thank you.
I can keep on free threading now 


Reply all
Reply to author
Forward
0 new messages