Repository navigation
Py_NewInterpreterFromConfig end with hard crash #123488
Description
Activity
- addedtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
on Aug 30, 2024 the docs say also…
Note that the PyGILState_* functions assume there is only one global interpreter (created automatically by Py_Initialize()). Python supports the creation of additional interpreters (using Py_NewInterpreter()), but mixing multiple interpreters and the PyGILState_* API is unsupported.
Right, mixing them is unsupported. You still need to have Python initialized, and then you can create subinterpreters with the main interpreter's GIL -- once the sub interpreter is initialized, it will have its own GIL.
First success … If I use a "release" build, (non debug python) than
valgrindreport an error==22661== Thread 2: ==22661== Invalid free() / delete / delete[] / realloc() ==22661== at 0x483B18B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==22661== by 0x4A7C51A: _PyMem_RawFree (obmalloc.c:73) ==22661== by 0x4A7C51A: PyMem_RawFree (obmalloc.c:685) ==22661== by 0x4A7C51A: _PyObject_Free (obmalloc.c:1853) ==22661== by 0x4A7C51A: PyObject_Free (obmalloc.c:830) ==22661== by 0x4A729F7: dictresize (dictobject.c:1569) ==22661== by 0x4A721EA: insertion_resize (dictobject.c:1194) ==22661== by 0x4A721EA: insertdict (dictobject.c:1261) ==22661== by 0x4AFC275: add_subclass (typeobject.c:7618) ==22661== by 0x4AFA491: type_ready_add_subclasses (typeobject.c:7352) ==22661== by 0x4AFA491: type_ready (typeobject.c:7515) ==22661== by 0x4AFD1B9: type_new_impl (typeobject.c:3795) ==22661== by 0x4AFD1B9: type_new (typeobject.c:3929) ==22661== by 0x4A838BD: type_call (typeobject.c:1664) ==22661== by 0x4A575BA: _PyObject_MakeTpCall (call.c:240) ==22661== by 0x4A57882: _PyObject_FastCallDictTstate (call.c:133) ==22661== by 0x4A57882: PyObject_VectorcallDict (call.c:157) ==22661== by 0x4B21EE4: builtin___build_class__ (bltinmodule.c:208) ==22661== by 0x4A783AE: cfunction_vectorcall_FASTCALL_KEYWORDS (methodobject.c:438) ==22661== by 0x4A57D2E: _PyObject_VectorcallTstate (pycore_call.h:92) ==22661== by 0x4A57D2E: PyObject_Vectorcall (call.c:325) ==22661== by 0x49D573B: _PyEval_EvalFrameDefault.cold (bytecodes.c:2714) ==22661== by 0x4B236B1: PyEval_EvalCode (ceval.c:578) ==22661== by 0x4B430C6: run_eval_code_obj (pythonrun.c:1722) ==22661== by 0x4B4303A: run_mod (pythonrun.c:1743) ==22661== by 0x4B437C1: pyrun_file (pythonrun.c:1643) ==22661== by 0x4B433AE: _PyRun_SimpleFileObject (pythonrun.c:433) ==22661== by 0x4A00EE2: PyRun_SimpleFileExFlags (pythonrun.c:466) ==22661== by 0x58BD664: py_mqmsgque_sFactoryCTor (MqFactoryC_py.c:195) ==22661== by 0x593D9EF: MqFactoryInvoke_RT (MqFactoryS_mq.c:289) ==22661== by 0x595CE22: MqSysServerThreadMain (sys_mq.c:269) ==22661== by 0x595CE22: MqSysServerThreadMain (sys_mq.c:219) ==22661== by 0x595D6F8: sSysServerThreadInit (sys_mq.c:324) ==22661== by 0x4ECA6E9: start_thread (in /lib64/libpthread-2.31.so) ==22661== by 0x515358E: clone (in /lib64/libc-2.31.so) ==22661== Address 0x601f7d0 is in a rw- anonymous segmentI have an initialized main python interpreter which loads my library and in the library a thread is created etc.. this is the reason why it is workc but only for 4 instances :-(
Task → my current research is to make the type itself thread-local I mean the new type I define in my extension but now the thread startup does not understand the type template (because the original type is from the main-thread and this type has no meaning in the new thread. :-(
> Nhi1Exec -r=uds MyServer.py --thread exec[#6] -> 'NHI1_EXT/debug/bin/python3' 'NHI1_HOME/example/py/MyServer.py' '--thread' '--uds' '--file' '/tmp/test.uds' X> {MqFactoryC_py.c :pid(32901):tid(0x7f975a3e3700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: MQ_FACTORY_NEW_THREAD -> thread<0x7f9759b99950> X> {MkErrorC :pid(32901):tid(0x7f975a3e3700):X:dlv(0):ctxId( 0):rc(1):ctx(0x2421b10 ):MkErrorPrintln_RT }: BACKGROUND ERROR >>> X> (MkErrorC) [WrongTypeOfArgError] for argument 'tmpl'...expect type 'pymqmsgque.MqContextC'...but got 'MyServer' X> (MkErrorC) | 211 | mk_misc_check_lng.h | py_mkkernel_CheckClass X> (MkErrorC) | 3568 | MqContextC_py.c | MqContextC MqContextC.new(?tmpl:MqContextC=None?) X> (MkErrorC) | 258 | MqFactoryC_py.c | py_mqmsgque_sFactoryCTor X> (MkErrorC) | 327 | MqFactoryS_mq.c | MqFactoryInvoke_RT BACKGROUND ERROR <<<The "MyServer" is the initial server from the main thread
import sys from pymqmsgque import * #import gc #gc.set_debug(gc.DEBUG_LEAK) # package-item class MyServer(MqContextC): # factory startup def __init__(self, tmpl=None): self.ConfigSetServerSetup(self.ServerSetup) super().__init__(tmpl) # service to serve all incoming requests for token "HLWO" def MyFirstService(self): self.SendSTART() self.SendSTR(self.ReadSTR() + " World") self.SendRETURN() # define a service as link between the token "HLWO" and the callback "MyFirstService" def ServerSetup(self): self.ServiceCreate("HLWO",self.MyFirstService) # package-main if __name__ == "__main__": # create the "MyServer" factory… and the object srv = MqFactoryC.Add(MyServer).New() try: srv.LinkCreate(sys.argv) srv.ProcessEvent(MqWaitOnEventE.FOREVER) except Exception as ex: srv.ErrorCatch(ex) finally: srv.Exit()Yeah, this looks like it's probably a duplicate of #123134. I haven't reproduced this on Linux yet, but on Windows, there seems to be a thread safety issue inside the allocator when using a per-interpreter GIL. I'll investigate this further later today. In the meantime, try adding some synchronization between your threads to prevent them from calling
Py_NewInterpreterFromConfigconcurrently.even if I do a one thread per step my python is failing after the fourth thread was created (and closed) → there is no "concurrency" right now
You'll have to share more of the C code, but that's probably just a side-effect of a prior memory error.
question : do you use a TYPE per THREAD or a TYPE per process , I mean the C-Python-Type definition
I add the macro
OT_CLASS_TYPE_REFthis is__thread PyTypeObjector justPyTypeObject
→ I test this setup.OT_CLASS_TYPE_REF NS(MqContextCR) = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "pymqmsgque.MqContextC", .tp_basicsize = sizeof(MqContextC_Obj), .tp_itemsize = 0, .tp_new = (newfunc) NS(MqContextC_new), .tp_doc = MqContextC_Doc, .tp_methods = NS(MqContextC_Methods), .tp_str = (reprfunc) NS(sMqContextC_Str), .tp_repr = (reprfunc) NS(sMqContextC_Repr), .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, };new test case with parallel access . looks like the "race" thing
exec[#6] -> 'NHI1_EXT/debug/bin/python3' 'NHI1_HOME/example/py/perfserver.py' '--thread' '--uds' '--file' '/tmp/test.uds' X> {MqFactoryC_py.c :pid(72897):tid(0x7f85a1251700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: MQ_FACTORY_NEW_THREAD -> thread<0x7f85a0a07950> X> {MqFactoryC_py.c :pid(72897):tid(0x7f859fc63700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: MQ_FACTORY_NEW_THREAD -> thread<0x7f859daf2950> X> {MqFactoryC_py.c :pid(72897):tid(0x7f859f462700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: MQ_FACTORY_NEW_THREAD -> thread<0x7f859d8d4950> X> {MqFactoryC_py.c :pid(72897):tid(0x7f859e385700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: MQ_FACTORY_NEW_THREAD -> thread<0x7f859da94950> X> {MqFactoryC_py.c :pid(72897):tid(0x7f859ebcf700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: MQ_FACTORY_NEW_THREAD -> thread<0x7f859d554950> X> {MqFactoryC_py.c :pid(72897):tid(0x7f85a0464700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: MQ_FACTORY_NEW_THREAD -> thread<0x7f859d714950> X> {MqContextC_py.c :pid(72897):tid(0x7f859fc63700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sThreadExit }: thread EXIT, current state<0x7f859daf2950>, rt state<0x7f859daf2950> X> {MqFactoryC_py.c :pid(72897):tid(0x7f859c19a700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: MQ_FACTORY_NEW_THREAD -> thread<0x180c3b0> Debug memory block at address p=0x7f85a16431b0: API '' 18302063728033398269 bytes originally requested The 7 pad bytes at p-7 are Debug memory block at address p=0x7f85a16431b0: API '' not all FORBIDDENBYTE (0xfd): at p-7: 0x00 18302063728033398269 bytes originally requested *** OUCH The 7 pad bytes at p-7 are at p-6: 0x00not all FORBIDDENBYTE (0xfd): at p-7: 0x00 *** OUCH at p-5: 0x00 *** OUCH *** OUCH at p-6: 0x00 at p-4: 0x00 *** OUCH *** OUCH at p-5: 0x00 *** OUCH at p-3: 0x00 at p-4: 0x00 *** OUCH *** OUCH at p-3: 0x00 *** OUCH at p-2: 0x00 at p-2: 0x00 *** OUCH *** OUCH at p-1: 0x00 *** OUCH Because memory is corrupted at the start, the count of bytes requested may be bogus, and checking the trailing pad bytes may segfault. at p-1: 0x00 *** OUCH The 8 pad bytes at tail=0xfdfe7d839f622fad are Because memory is corrupted at the start, the count of bytes requested may be bogus, and checking the trailing pad bytes may segfault. The 8 pad bytes at tail=0xfdfe7d839f622fad are X> {PRINTOK - now a success news (with smell)
In the docu about the
PyInterpreterConfig_OWN_GIL(A Per-Interpreter GIL) there was a paragraph about isolation ...Using an isolated interpreter requires vigilance in preserving that isolation. That especially means not sharing any objects or mutable state without guarantees about thread-safety. Even objects that are otherwise immutable (e.g. None, (1, 5)) can’t normally be shared because of the refcount. One simple but less-efficient approach around this is to use a global lock around all use of some state (or object). Alternately, effectively immutable objects (like integers or strings) can be made safe in spite of their refcounts by making them “immortal”. In fact, this has been done for the builtin singletons, small integers, and a number of other built in objects.and my first question was , does the type object have to be isolated as well?
I started a research about thread local type object
switching to thread-local-types the interpreter does not crash anymore BUT ...
- this thread-local-type will probably break old python thread code which sill assume one-type-per-interpreter even if multiple threads per interpreter are supported.
analysis
- using my simple example from above works fine.
- the concurrency problem is still real
2.1. tests done with my performance test tool → parallel creating of a per-thread-session
2.2. test up to 3 workers → everything is OK
2.3. test 4 or more workers → a race crash happen… - the performance of starting a session via thread versa starting a session vie spawn is almost identical !!
3.1. there is no (performance)-reason to use a thread at all in python.
3.2. the startup-speed for spawn depends on the speed of the filesystem, a 10 times difference between a mounted file system and a local filesystem is possible. the info from3.1.was related to an local filesystem.
race crash
Program terminated with signal SIGABRT, Aborted. #0 0x00007fc0e3999d2b in raise () from /lib64/libc.so.6 [Current thread is 1 (Thread 0x7fc0e0a19700 (LWP 41435))] #0 0x00007fc0e3999d2b in raise () from /lib64/libc.so.6 #1 0x00007fc0e399b3e5 in abort () from /lib64/libc.so.6 #2 0x00007fc0e39dfc87 in __libc_message () from /lib64/libc.so.6 #3 0x00007fc0e39e7d2a in malloc_printerr () from /lib64/libc.so.6 #4 0x00007fc0e39ebde6 in free_check () from /lib64/libc.so.6 #5 0x00007fc0e3ebe832 in _PyMem_RawFree (_unused_ctx=<optimized out>, ptr=<optimized out>) at Objects/obmalloc.c:73 #6 0x00007fc0e3ec047f in PyMem_RawFree (ptr=ptr@entry=0x25c2bb0) at Objects/obmalloc.c:685 #7 0x00007fc0e3fe3808 in PyThread_free_lock (lock=0x25c2bb0) at Python/thread_pthread.h:418 #8 0x00007fc0e404dee2 in rlock_dealloc (self=0x7fc0df702870) at ./Modules/_threadmodule.c:348 #9 0x00007fc0e3ebb83c in _Py_Dealloc (op=op@entry=0x7fc0df702870) at Objects/object.c:2625 #10 0x00007fc0e3ea538b in Py_DECREF (filename=filename@entry=0x7fc0e4065c24 "./Include/object.h", lineno=lineno@entry=798, op=0x7fc0df702870) at ./Include/object.h:690 #11 0x00007fc0e3ea53aa in Py_XDECREF (op=<optimized out>) at ./Include/object.h:798 #12 0x00007fc0e3eaac58 in _PyObject_FreeInstanceAttributes (self=self@entry=0x7fc0df715910) at Objects/dictobject.c:5571 #13 0x00007fc0e3eda1eb in subtype_dealloc (self=0x7fc0df715910) at Objects/typeobject.c:2020 #14 0x00007fc0e3ebb83c in _Py_Dealloc (op=op@entry=0x7fc0df715910) at Objects/object.c:2625 #15 0x00007fc0e3ea538b in Py_DECREF (filename=filename@entry=0x7fc0e4065c24 "./Include/object.h", lineno=lineno@entry=798, op=0x7fc0df715910) at ./Include/object.h:690 #16 0x00007fc0e3ea53aa in Py_XDECREF (op=<optimized out>) at ./Include/object.h:798 #17 0x00007fc0e3eaac58 in _PyObject_FreeInstanceAttributes (self=self@entry=0x7fc0df7158c0) at Objects/dictobject.c:5571 #18 0x00007fc0e3eda1eb in subtype_dealloc (self=0x7fc0df7158c0) at Objects/typeobject.c:2020 #19 0x00007fc0e3ebb83c in _Py_Dealloc (op=op@entry=0x7fc0df7158c0) at Objects/object.c:2625 #20 0x00007fc0e3e6f549 in Py_DECREF (filename=filename@entry=0x7fc0e4065c24 "./Include/object.h", lineno=lineno@entry=798, op=0x7fc0df7158c0) at ./Include/object.h:690 #21 0x00007fc0e3e6f568 in Py_XDECREF (op=<optimized out>) at ./Include/object.h:798 #22 0x00007fc0e3e6f67d in method_dealloc (im=0x7fc0df7029f0) at Objects/classobject.c:240 #23 0x00007fc0e3ebb83c in _Py_Dealloc (op=op@entry=0x7fc0df7029f0) at Objects/object.c:2625 #24 0x00007fc0e3f5a22b in Py_DECREF (filename=filename@entry=0x7fc0e4073f78 "Python/bytecodes.c", lineno=lineno@entry=2737, op=op@entry=0x7fc0df7029f0) at ./Include/object.h:690 #25 0x00007fc0e3f6d264 in _PyEval_EvalFrameDefault (tstate=0x7fc0dfc67950, frame=0x7fc0df677398, throwflag=0) at Python/bytecodes.c:2737 #26 0x00007fc0e3f73246 in _PyEval_EvalFrame (tstate=tstate@entry=0x7fc0dfc67950, frame=<optimized out>, throwflag=throwflag@entry=0) at ./Include/internal/pycore_ceval.h:89 #27 0x00007fc0e3f7337b in _PyEval_Vector (tstate=0x7fc0dfc67950, func=func@entry=0x7fc0df688f50, locals=locals@entry=0x0, args=args@entry=0x7fc0e0a17cf0, argcount=argcount@entry=2, kwnames=kwnames@entry=0x0) at Python/ceval.c:1683 #28 0x00007fc0e3e6bddf in _PyFunction_Vectorcall (func=0x7fc0df688f50, stack=0x7fc0e0a17cf0, nargsf=<optimized out>, kwnames=0x0) at Objects/call.c:419 #29 0x00007fc0e3e6c14e in _PyObject_VectorcallTstate (tstate=tstate@entry=0x7fc0dfc67950, callable=callable@entry=0x7fc0df688f50, args=args@entry=0x7fc0e0a17cf0, nargsf=nargsf@entry=2, kwnames=kwnames@entry=0x0) at ./Include/internal/pycore_call.h:92 #30 0x00007fc0e3e6d04c in object_vacall (tstate=tstate@entry=0x7fc0dfc67950, base=base@entry=0x0, callable=0x7fc0df688f50, vargs=vargs@entry=0x7fc0e0a17d60) at Objects/call.c:850 #31 0x00007fc0e3e6d16d in PyObject_CallMethodObjArgs (obj=0x0, name=<optimized out>) at Objects/call.c:911 #32 0x00007fc0e3fa4f18 in import_find_and_load (tstate=tstate@entry=0x7fc0dfc67950, abs_name=abs_name@entry=0x7fc0e433f160 <const_str_abc>) at Python/import.c:2779 #33 0x00007fc0e3fa7d37 in PyImport_ImportModuleLevelObject (name=name@entry=0x7fc0e433f160 <const_str_abc>, globals=<optimized out>, locals=locals@entry=0x7fc0df6db650, fromlist=fromlist@entry=0x7fc0e42a2460 <_Py_NoneStruct>, level=level@entry=0) at Python/import.c:2862 #34 0x00007fc0e3f5bf7b in import_name (tstate=tstate@entry=0x7fc0dfc67950, frame=frame@entry=0x7fc0df677320, name=0x7fc0e433f160 <const_str_abc>, fromlist=fromlist@entry=0x7fc0e42a2460 <_Py_NoneStruct>, level=level@entry=0x7fc0e438de88 <_PyRuntime+19272>) at Python/ceval.c:2482 #35 0x00007fc0e3f6a90b in _PyEval_EvalFrameDefault (tstate=0x7fc0dfc67950, frame=<optimized out>, throwflag=0) at Python/bytecodes.c:2143 #36 0x00007fc0e3f73246 in _PyEval_EvalFrame (tstate=tstate@entry=0x7fc0dfc67950, frame=<optimized out>, throwflag=throwflag@entry=0) at ./Include/internal/pycore_ceval.h:89 #37 0x00007fc0e3f7337b in _PyEval_Vector (tstate=tstate@entry=0x7fc0dfc67950, func=func@entry=0x7fc0df705c10, locals=locals@entry=0x7fc0df6db650, args=args@entry=0x0, argcount=argcount@entry=0, kwnames=kwnames@entry=0x0) at Python/ceval.c:1683 #38 0x00007fc0e3f73433 in PyEval_EvalCode (co=co@entry=0x25f7340, globals=globals@entry=0x7fc0df6db650, locals=locals@entry=0x7fc0df6db650) at Python/ceval.c:578 #39 0x00007fc0e3f58686 in builtin_exec_impl (module=module@entry=0x7fc0dfbbf9b0, source=0x25f7340, globals=0x7fc0df6db650, locals=0x7fc0df6db650, closure=0x0) at Python/bltinmodule.c:1096 #40 0x00007fc0e3f58777 in builtin_exec (module=0x7fc0dfbbf9b0, args=<optimized out>, args@entry=0x7fc0df715878, nargs=nargs@entry=2, kwnames=kwnames@entry=0x0) at Python/clinic/bltinmodule.c.h:586 #41 0x00007fc0e3eb72b2 in cfunction_vectorcall_FASTCALL_KEYWORDS (func=0x7fc0dfbc8050, args=0x7fc0df715878, nargsf=<optimized out>, kwnames=0x0) at Objects/methodobject.c:438 #42 0x00007fc0e3e6d9fa in _PyVectorcall_Call (tstate=tstate@entry=0x7fc0dfc67950, func=0x7fc0e3eb7251 <cfunction_vectorcall_FASTCALL_KEYWORDS>, callable=callable@entry=0x7fc0dfbc8050, tuple=tuple@entry=0x7fc0df715860, kwargs=kwargs@entry=0x7fc0df702750) at Objects/call.c:271After playing around with this, I was unable to reproduce the data race. Though, I did encounter a deadlock when trying to call
Py_NewInterpreterFromConfigwithout the GIL and the properPy_BEGIN_ALLOW_THREADSsection -- are those present in your code? If not, then this is a user error!Just to be clear what the flow is:
- I start python running my
server.py server.pyload an external library- external library create new thread from C
- new thread start
Py_NewInterpreterFromConfigwithPyInterpreterConfig_OWN_GIL
If I do…
if (create == MQ_FACTORY_NEW_THREAD) { PyThreadState *threadstate = PyEval_SaveThread(); PyGILState_STATE gilstate = PyGILState_Ensure(); static PyInterpreterConfig config = { .use_main_obmalloc = 0, .allow_fork = 0, .allow_exec = 0, .allow_threads = 1, .allow_daemon_threads = 0, .check_multi_interp_extensions = 1, .gil = PyInterpreterConfig_OWN_GIL, }; PyThreadState *tstate = NULL; PyStatus status = Py_NewInterpreterFromConfig(&tstate, &config); MK_RT_REF.threadData = tstate; ...than I get :
Fatal Python error: PyEval_SaveThread: the function must be called with the GIL held, after Python initialization and before Python finalization, but the GIL is released (the current Python thread state is NULL) Python runtime state: initializedI think
Py_NewInterpreterFromConfig→PyInterpreterConfig_OWN_GILcreate and set threadstate and gil.If I skip
PyEval_SaveThreadandPyGILState_Ensureeverything works for easy tests BUT In a load test I get after a whileDebug memory block at address p=0x483a870: API '' 14988823984819142663 bytes originally requested The 7 pad bytes at p-7 are not all FORBIDDENBYTE (0xfd): at p-7: 0x0d *** OUCH at p-6: 0x00 *** OUCH at p-5: 0x00 *** OUCH at p-4: 0x00 *** OUCH at p-3: 0x00 *** OUCH at p-2: 0x00 *** OUCH at p-1: 0x00 *** OUCH Because memory is corrupted at the start, the count of bytes requested may be bogus, and checking the trailing pad bytes may segfault. The 8 pad bytes at tail=0xd00300000483a877 are- I start python running my
13 remaining items
I call the
Py_BEGIN_ALLOW_THREADSon the main thread because there are no other threads before thePyServerThread→ just look to thetidcolumn and thepy_mqmsgque_PyServerThreadthe
py_mqmsgque_sFactoryCToris still blocking…if (create == MQ_FACTORY_NEW_THREAD) { M1 PyGILState_STATE gilstate = PyGILState_Ensure(); M2> Nhi1Exec -r=uds perfserver.py --thread --debug 3 exec[#8] -> 'NHI1_EXT/debug/bin/python3' 'NHI1_HOME/example/py/perfserver.py' '--thread' '--debug' '3' '--uds' '--file' '/tmp/test.uds' S> {perfserver :pid(55814):tid(0x7fb72957fb80):L:dlv(2):ctxId( 0):rc(2):ctx(0x204a980 ):pLinkPrepare }: PREPARE: factory<perfserver>, io->com<UDS> S> {perfserver :pid(55814):tid(0x7fb72957fb80):L:dlv(2):ctxId( 0):rc(2):ctx(0x204a980 ):MqLinkCreate_RT }: START: io->com<UDS> S> {perfserver :pid(55814):tid(0x7fb72957fb80):L:dlv(2):ctxId( 0):rc(2):ctx(0x204a980 ):UdsServer }: create UNIX uds socket file</tmp/test.uds> S> {perfserver :pid(55814):tid(0x7fb72957fb80):L:dlv(2):ctxId( 0):rc(2):ctx(0x204a980 ):GenericServer }: server accept incoming calls S> {perfserver :pid(55814):tid(0x7fb72957fb80):L:dlv(2):ctxId( 0):rc(2):ctx(0x204a980 ):GenericServer }: server get incoming call S> {perfserver :pid(55814):tid(0x7fb72957fb80):L:dlv(2):ctxId( 0):rc(2):ctx(0x204a980 ):pIoStartServer }: MQ_START_SERVER_AS_THREAD S> {perfserver :pid(55814):tid(0x7fb72957fb80):L:dlv(0):ctxId( 0):rc(2):ctx(0x204a980 ):pIoStartServer }: FACTORY[perfserver] ... X> {MqContextC_py.c :pid(55814):tid(0x7fb72957fb80):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_PyServerThread }: num<1> S> {perfserver :pid(55814):tid(0x7fb72957fb80):L:dlv(2):ctxId( 0):rc(2):ctx(0x204a980 ):GenericServer }: server accept incoming calls X> {MqFactoryC_py.c :pid(55814):tid(0x7fb728ccd700):X:dlv(0):ctxId( 0):rc(0):ctx((nil) ):py_mqmsgque_sFactoryCTor }: 11111111111111111 [k/py/MqFactoryC_py.c:171]It's difficult for me to follow the code due to the use of macros, but if it's truly the main thread, then I think the problem is the
PyGILState_*calls -- they aren't necessary to callPy_BEGIN_ALLOW_THREADS. Releasing the GIL state in this case might prevent any future API calls (such asPy_Finalize). Though, I could be wrong on that, the GIL state APIs might have a mechanism to deal with that.Regardless, I don't think this is a CPython issue :(
Found something special under "load" !! → Q: how to check if "ts" is still valid?
The main problem is that every thread is a server that can terminate itself. So if a server terminates between
PyThreadState_Swap#1andPyThreadState_Swap#2and that server "happens" to be returned astsby the firstPyThreadState_Swap#1, then it just CRASHes.- this is probably the reason why it works without load and with load it crashes because a
PyThreadStatedisappears. - just to be clear that python has no control when a thread is deleted
- the global variable PyThreadState saved as local variable
tscan not follow an external thread delete.
MK_UNUSED static void NS(sThreadExit) (MK_RT_ARGS int num) { mk_debug_color(MK_RED,"%s, current state<%p>, rt state<%p>", "thread EXIT", PyThreadState_Get(), MK_RT_REF.threadData); PyThreadState* ts = PyThreadState_Swap(MK_RT_REF.threadData); printV("ts start: %p → %p", ts, MK_RT_REF.threadData); Py_EndInterpreter(MK_RT_REF.threadData); printV("ts end : %p → %p", MK_RT_REF.threadData, ts); // CRASH because of "ts" disappeared between "PyThreadState_Swap#1" and "PyThreadState_Swap#2" // Q: how to check if "ts" is still valid? PyThreadState_Swap(ts); MK_RT_REF.threadData = NULL; }If I switch to
Py_BEGIN_ALLOW_THREADSinstead ofPyThreadState_SwapthePy_EndInterpreter(interpreter_ts)
fails withFatal Python error: Py_EndInterpreter: thread is not currentupdate
even if I disable the
Py_EndInterpreterand thepthread_exitthe python crash with
Debug memory block at address ... FORBIDDENBYTE- this is probably the reason why it works without load and with load it crashes because a
AFAIK,
Py_EndInterpreterwill deallocate the thread state, so trying to usetspast that point is invalid. Why are you callingPyThreadState_Swapinstead of just using the actual thread state for the interpreter?the problem is that this is just an example → this lost
tscould happen everywhere in the code because of thePyThreadState_Swapapi design.> grep -r --include=*.[ch] PyThreadState_Swap Include/internal/pycore_ceval.h:extern PyThreadState * _PyThreadState_SwapNoGIL(PyThreadState *); Include/internal/pycore_pystate.h:PyAPI_FUNC(PyThreadState *) _PyThreadState_Swap( Include/pystate.h:PyAPI_FUNC(PyThreadState *) PyThreadState_Swap(PyThreadState *); Modules/_xxsubinterpretersmodule.c: save_tstate = PyThreadState_Swap(tstate); Modules/_xxsubinterpretersmodule.c: PyThreadState_Swap(save_tstate); Modules/_xxsubinterpretersmodule.c: PyThreadState_Swap(save_tstate); Modules/_xxsubinterpretersmodule.c: save_tstate = PyThreadState_Swap(tstate); Modules/_xxsubinterpretersmodule.c: PyThreadState_Swap(save_tstate); Modules/_xxsubinterpretersmodule.c: PyThreadState *save_tstate = PyThreadState_Swap(tstate); Modules/_xxsubinterpretersmodule.c: PyThreadState_Swap(save_tstate); Modules/_testcapimodule.c: PyThreadState_Swap(NULL); Modules/_testcapimodule.c: PyThreadState_Swap(orig_tstate); Modules/_testcapimodule.c: PyThreadState_Swap(NULL); Modules/_testcapimodule.c: PyThreadState_Swap(mainstate); Modules/_testcapimodule.c: PyThreadState_Swap(mainstate); Modules/_testcapimodule.c: PyThreadState_Swap(NULL); Modules/_testcapimodule.c: PyThreadState_Swap(mainstate); Modules/_testcapimodule.c: PyThreadState_Swap(mainstate); Modules/_testcapimodule.c: PyThreadState *oldts = PyThreadState_Swap(NULL); Modules/_testcapimodule.c: PyThreadState_Swap(oldts); PC/python3dll.c:EXPORT_FUNC(PyThreadState_Swap) Programs/_testembed.c: PyThreadState_Swap(NULL); Programs/_testembed.c: PyThreadState_Swap(mainstate); Python/getargs.c: save_tstate = PyThreadState_Swap(temp_tstate); Python/getargs.c: (void)PyThreadState_Swap(save_tstate); Python/pylifecycle.c: (void) _PyThreadState_SwapNoGIL(tstate); Python/pylifecycle.c: PyThreadState *save_tstate = _PyThreadState_SwapNoGIL(tstate); Python/pylifecycle.c: PyThreadState_Swap(save_tstate); Python/pylifecycle.c: _PyThreadState_SwapNoGIL(save_tstate); Python/pystate.c: The stored thread state is set by PyThreadState_Swap(). Python/pystate.c: PyThreadState *tstate = _PyThreadState_Swap(runtime, NULL); Python/pystate.c: _PyThreadState_Swap(runtime, tstate); Python/pystate.c: PyThreadState *save_tstate = _PyThreadState_Swap(runtime, tstate); Python/pystate.c: _PyThreadState_Swap(runtime, save_tstate); Python/pystate.c:_PyThreadState_SwapNoGIL(PyThreadState *newts) Python/pystate.c:_PyThreadState_Swap(_PyRuntimeState *runtime, PyThreadState *newts) Python/pystate.c:PyThreadState_Swap(PyThreadState *newts) Python/pystate.c: return _PyThreadState_Swap(&_PyRuntime, newts); Python/ceval_gil.c: under our feet using PyThreadState_Swap(). Fix the GIL last Python/ceval_gil.c: if (_PyThreadState_SwapNoGIL(tstate) != NULL) { Python/ceval_gil.c: PyThreadState *new_tstate = _PyThreadState_SwapNoGIL(NULL); Python/ceval_gil.c: PyThreadState *tstate = _PyThreadState_SwapNoGIL(NULL); Python/ceval_gil.c: _PyThreadState_SwapNoGIL(tstate); Python/ceval_gil.c: if (_PyThreadState_SwapNoGIL(NULL) != tstate) { Python/ceval_gil.c: if (_PyThreadState_SwapNoGIL(tstate) != NULL) {Ok, but it's not obvious what the clear "fix" would be here.
PyThreadState_Swapinvalidates the old thread state (which is why your example fails), we can't change that.I just testing around the
tsis the thread-state before the thread-exit is called →it is NOt the thread stata I want delete bot could be.In the above example, you're using
PyThreadState_Swapincorrectly.// I'm not totally sure why you're swapping here, you'll have to clarify. // If MK_RT_REF.threadData *is* the current thread state, as I suspect, then // the call to Py_EndInterpreter will also fail, since it will invalidate it. PyThreadState* ts = PyThreadState_Swap(MK_RT_REF.threadData); // At best, adding a check to turn PyThreadState_Swap into a no-op // if the passed thread state matches the current thread state // could be viable, but I'm not sure how useful it would be. printV("ts start: %p → %p", ts, MK_RT_REF.threadData); // MK_RT_REF.threadData is invalidated, as well as ts (since they're the same pointer) // You can't dereference either of them past this point. Py_EndInterpreter(MK_RT_REF.threadData); printV("ts end : %p → %p", MK_RT_REF.threadData, ts); // This call does nothing -- ts would already be the thread state. // The segfault comes from ts being freed by Py_EndInterpreter. PyThreadState_Swap(ts);
Again, I don't think there's anything here that's a CPython issue :(
I'll submit a PR for some extra documentation on subinterpreters, but I suggest opening a help thread and closing this issue.
I already delete this test code because it was just a test → the original problem of crash somewhere is still an issue.
I can't help with that unless I see the code, but let's move this to discourse. If we find that this is indeed a CPython issue, we can reopen (or better yet, make a new one with a more solid reproducer).
Again, I don't think there's anything here that's a CPython issue :(
well I already use my library as an extension for many languages and obvious able I'm not smart enough to use the python thread api. → I'll think this is an python problem :-)
After trying for the second time in my limited lifetime to do something with thread on python and being miserably wiser twice, I must now admit that python is apparently incapable of providing any usable thread-api.
→ the smartest thing to do with thread on python… activate the ignoreThread switch
int NS(pMqContextC_Init)(MQ_RT_ARGS OT_LNG_T m) { // define type MqContextCTT = MkTypeDup2(MqContextC_TT,"pymqmsgque_MqContextC"); MqContextCT->fIncrSelf = NS(sIncrSelf); MqContextCT->fDecrSelf = NS(sDecrSelf); // >> replaced by os_fork // MqContextCT->fParentBeforeFork = NS(sParentBeforeFork); // MqContextCT->fParentAfterFork = NS(sParentAfterFork); // MqContextCT->fChildAfterFork = NS(sChildAfterFork); MqContextCT->fProcessExit = NS(sProcessExit); //MqContextCT->fThreadExit = NS(sThreadExit); //MqContextCT->ignoreDisasterSetup = true; MqContextCT->ignoreThread = true; ClassInit return 0; error: return -1; }Not disagreeing that multithreading from the C API is difficult! We need to document it better. Though, if you want to get this working, I can help you on discourse, instead of polluting the issues page with relentless back-and-forth debugging.
Reacted by Petr ViktorinReacted by Petr Viktorin@encukou, this should be closed as not planned. I'll create a new issue sometime tomorrow to add some better documentation on using PEP 684.
I've created #123672 to address the documentation problem.
Crash report
What happened?
I started using Py_NewInterpreterFromConfig to add an PER-THREAD interpreter with my library.
The library is thread safe and supports many languages
including other languages with thread support
are working fine. Even python works fine without thread support.
Task: now I started to use thread support in python.
I had to change my code to to Multi-phase initialization, isolate the python data on an per-thread-level and finally get an easy thread example working -> ok
I test a client server application. the server create a thread in my library and this thread is initialized with
I figure out that always after 4 successful simple round trip clinet → server → client the server crash with the message below
MqDisasterSignalhandler.The server code (file sourced into the new interpreter) is quite simple.
the problem is that running this example with a server with valgrind nothing happen (no crash)
the same example using fork or using spawn to startup the server instances works fine.
the problem is only with thread support.
additional Information: because my library only support one server-instance per thread, I choose
PyInterpreterConfig_OWN_GILto be clear that I don't want to have interaction between different python interpreters.all the communication (between processes and threads) is done by my library. my library uses thread-local-storage to isolate the data per-thread.
CPython versions tested on:
3.12
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.12.4 (main, Aug 29 2024, 18:00:55) [GCC 13.3.0]