Skip to content

threadpool_info() before os.fork() causes SIGABRT on child exit (Python 3.13+) #225

Description

@bornexmachina

threadpool_info() before os.fork() causes SIGABRT on child exit (Python 3.13+)

Versions: threadpoolctl 3.6.0, Python 3.14.5, libffi 3.4.x (Linux)

Hi all,
I have updated my code library from Python3.9 to 3.14. I have noticed that stopping my Python service produced core dumps. My service has a parent which forks multiple children which use scikit-learn.

I could reproduce a minimal running code with a coredump. Sorry, I am not sure, whether it is threadpoolctl or CPython issue. Thus, I open the issue with CPython as well.

Below you will find the structured output summarized by our glorious AI overlords.

Thanks!

Problem

Calling threadpoolctl.threadpool_info() in a parent process before
os.fork(), then letting the forked child exit via sys.exit(), reliably
crashes the child with SIGABRT inside libffi's allocator:

abort
dlfree.cold (libffi.so.8)
CThunkObject_dealloc (_ctypes)
... (Py_Finalize / module teardown) ...

Cause

threadpoolctl imports ctypes.util.find_library. On Python 3.13+,
ctypes.util implements Linux lookups via dl_iterate_phdr() using a
module-level ctypes.CFUNCTYPE closure (ctypes.util._info_callback) that
lives for the process lifetime. If this closure is created in a parent
before fork(), the forked child inherits it via copy-on-write. libffi's
private closure allocator keeps global free-list state that diverges
between parent and child after fork; when the child later frees the
inherited closure at interpreter shutdown, the allocator aborts on
corrupted metadata.

Minimal repro

import os, sys, signal, time
import threadpoolctl

threadpoolctl.threadpool_info()  # creates ctypes.util._info_callback

def child():
    signal.signal(signal.SIGTERM, lambda s, f: sys.exit(0))
    threadpoolctl.threadpool_info()
    time.sleep(30)

pid = os.fork()
if pid == 0:
    child()
else:
    time.sleep(3)
    os.kill(pid, signal.SIGTERM)
    os.waitpid(pid, 0)

python3.14 repro.pyAborted (core dumped).

Does not crash if threadpoolctl.threadpool_info() is only called
after the fork (i.e., not in the parent).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions