-
-
Notifications
You must be signed in to change notification settings - Fork 30.7k
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
Debug pymalloc crash when using os.fork() [regression] #86706
Comments
A python equivalent of the classical daemon() call started throwing an error from 3.8 when the debug hooks are active for pymalloc. If the tracing is also active it segfaults. This simple example triggers it: import os
def daemon():
pid = os.fork()
if pid != 0:
os._exit(0)
daemon() The error given is: Debug memory block at address p=0xf013d0: API '1' Enable tracemalloc to get the memory block allocation traceback Fatal Python error: bad ID: Allocated using API '1', verified using API 'r' Tested on Fedora, Ubuntu and RHEL with the same behaviour everwhere. Everything up to 3.8 works fine. 3.8 and 3.9 both exhibit the issue. Since this is a very standard way of starting a daemon it should affect quite a few users. At the very least it makes it annoying to use development mode to catch other issues. |
Reproducible on master, but doesn't occur with a debug build. My configure:
Crash comes out of the child process, I modified example to invoke lldb for child:
Attaching to the child gives this backtrace: (lldb) bt
The call stack is trying to free an interpreter mutex:
PyThread_free_lock(interp->id_mutex); |
Something seems to be: breaking alignment, changing the allocator used, or trashing that memory. In my case, the address of the mutex is: 0x5603a3666630 (*1) below 0x5603a3666610: n\0\0\03\0\0\0\0\0\0\0(*2)\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd\xdd1\0\0\0\0\0\0\0 The fun thing is that the nbytes field lines up at (*2) which is full of DEADBYTEs, and thus tries to find the tail of the allocation at p + 15987178197214944733 (which happily segfaults) |
So, I'm not an allocator/init/teardown expert, but it looks like: When you fork, PyRuntimeState creates a new mutex, explicitly using the default allocator (without the debug allocator active).. #ifdef HAVE_FORK
/* This function is called from PyOS_AfterFork_Child to ensure that
newly created child processes do not share locks with the parent. */
PyStatus
_PyRuntimeState_ReInitThreads(_PyRuntimeState *runtime)
{
// This was initially set in _PyRuntimeState_Init().
runtime->main_thread = PyThread_get_thread_ident();
int reinit_interp = _PyThread_at_fork_reinit(&runtime->interpreters.mutex);
int reinit_main_id = _PyThread_at_fork_reinit(&runtime->interpreters.main->id_mutex);
int reinit_xidregistry = _PyThread_at_fork_reinit(&runtime->xidregistry.mutex);
PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc);
}
return _PyStatus_OK();
}
#endif But the PyInterpreterState_Delete function does not do this: if (interp->id_mutex != NULL) {
PyThread_free_lock(interp->id_mutex);
} Which causes it to try to use the debug allocator to free, and hence crash.. |
Thanks, Sam! ✨ 🍰 ✨ |
Do you think that it would be worth it to dump the memory allocation when a Fatal Python error related to a memory error is triggered? |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: