11

UPDATE Well, it looks like adding PyEval_InitThreads() before the call to PyGILState_Ensure() does the trick. In my haste to figure things out I incorrectly attributed my "hanging" to PyEval_InitThreads().

However, after reading some Python documentation I am wondering if this is the correct solution.

It is not safe to call this function when it is unknown which thread (if any) currently has the global interpreter lock.


First of all, I am working on some modified GNU Radio code - particularly a modified gr_bin_statistics_f block. Now, there is a bug report (albeit an old one) which pretty much describes my exact situation.

http://gnuradio.org/redmine/issues/show/199

Now, usrp_spectrum_sense.py which is mentioned in the bug report calls gr_bin_statistics_f (C++) which then calls back to Python periodically to re-tune the USRP (radio).

Here is what happens when the Python code is called:

PyGILState_STATE d_gstate;
d_gstate = PyGILState_Ensure();

// call python code

PyGILState_Release(d_gstate);

So, once we return from the Python code a segmentation fault occurs when PyGILState_Release(d_gstate) is called. While there are differences between my code and the original gr_bin_statistics_f, nothing seems to be remotely related to this.

I read that calling PyEval_InitThreads() before PyGILState_Ensure() has solved the problem for some people, but it just causes my program to hang.

Can anyone shed light on this for me? Or is it simply time to send a message to the GNU Radio mailing list?

Using Python2.7 on Fedora 14 x86_64.

Here is the GDB backtrace:


(gdb) c
Continuing.
[New Thread 0x7fabd3a8d700 (LWP 23969)]
[New Thread 0x7fabd328c700 (LWP 23970)]
[New Thread 0x7fabd2a8b700 (LWP 23971)]
[New Thread 0x7fabd228a700 (LWP 23972)]
[New Thread 0x7fabd1a89700 (LWP 23973)]
[New Thread 0x7fabd1288700 (LWP 23974)]
[New Thread 0x7fabd0a87700 (LWP 23975)]
[New Thread 0x7fabbbfff700 (LWP 23976)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fabbbfff700 (LWP 23976)]
0x00000036b3e0db00 in sem_post () from /lib64/libpthread.so.0
(gdb) bt
#0  0x00000036b3e0db00 in sem_post () from /lib64/libpthread.so.0
#1  0x00000036c1317679 in PyThread_release_lock () from /usr/lib64/libpython2.7.so.1.0
#2  0x00007fabd6159c1f in ~ensure_py_gil_state (this=0x2dc6fc0, x=887000000)
    at gnuradio_swig_py_general.cc:5593
#3  gr_py_feval_dd::calleval (this=0x2dc6fc0, x=887000000) at gnuradio_swig_py_general.cc:5605
#4  0x00007fabd77c4b6e in gr_noise_level_f::tune_window (this=0x2db3ca0, 
    target_freq=) at gr_noise_level_f.cc:97
#5  0x00007fabd77c554b in gr_noise_level_f::work (this=0x2db3ca0, noutput_items=7, 
    input_items=, output_items=)
    at gr_noise_level_f.cc:115
#6  0x00007fabd7860714 in gr_sync_block::general_work (this=0x2db3ca0, 
    noutput_items=, ninput_items=, 
    input_items=, output_items=) at gr_sync_block.cc:64
#7  0x00007fabd7846ce4 in gr_block_executor::run_one_iteration (this=0x7fabbbffed90)
    at gr_block_executor.cc:299
#8  0x00007fabd7864332 in gr_tpb_thread_body::gr_tpb_thread_body (this=0x7fabbbffed90, block=...)
    at gr_tpb_thread_body.cc:49
#9  0x00007fabd785cce7 in operator() (function_obj_ptr=...) at gr_scheduler_tpb.cc:42
#10 operator() (function_obj_ptr=...)
    at /home/tja/Research/energy/detector/gnuradio-3.3.0/gruel/src/include/gruel/thread_body_wrapper.h:49
#11 boost::detail::function::void_function_obj_invoker0, void>::invoke (function_obj_ptr=...) at /usr/include/boost/function/function_template.hpp:153
---Type  to continue, or q  to quit---
#12 0x00007fabd74914ef in operator() (this=)
    at /usr/include/boost/function/function_template.hpp:1013
#13 boost::detail::thread_data >::run (this=)
    at /usr/include/boost/thread/detail/thread.hpp:61
#14 0x00007fabd725ca55 in thread_proxy () from /usr/lib64/libboost_thread-mt.so.1.44.0
#15 0x00000036b3e06d5b in start_thread () from /lib64/libpthread.so.0
#16 0x00000036b3ae4a7d in clone () from /lib64/libc.so.6
(gdb) 

Thanks for looking!

Mr. Shickadance
  • 5,283
  • 9
  • 45
  • 61

2 Answers2

13

Python expects a certain amount of initialization to be done by the main thread before anything attempts to call back in from a subthread.

If the main thread is an application that is embedding Python, then it should call PyEval_InitThreads() immediately after calling Py_Initialize().

If the main thread is instead the Python interpreter itself (as seems to be the case here), then the module using the multithreaded extension module should include an "import threading" early to ensure that PyEval_InitThreads() is called correctly before any subthreads are spawned.

Victor Uriarte
  • 479
  • 4
  • 9
ncoghlan
  • 40,168
  • 10
  • 71
  • 80
  • If I understood correctly, upon `import threading`, `PyEval_InitThreads` is automatically called, right? I have a very similar situation to this question, where I have a python script giving a callback to a C library that spawns a thread and calls that callback. I get a segfault in `PyGILState_Release` and following your advice, added `import threading` right after `import sys` and `import signal`. The segfault didn't go away though. Do I need to additionally call something in the python script? – Shahbaz Feb 01 '13 at 14:29
  • @Shahbaz: Failing to call `PyEval_InitThreads` early enough is just one of many things that can cause a segfault when writing C extensions for Python. For example, segfaults are a very common symptom of refcounting bugs, and it's not uncommon for those to show up when `PyGILState_Release` is cleaning up the thread state. – ncoghlan Feb 03 '13 at 04:18
  • I hear you. Indeed, adding that to my init function solved the problem. I asked you though, because of your last paragraph, in which you said the module using my module should do `import threading` so that `PyEval_InitThreads` is called. But it seems that _still_ I need to do a `PyEval_InitThreads` myself anyway. – Shahbaz Feb 03 '13 at 13:27
  • Doing the import in the module requires that that module itself be imported from the main thread. The fact doing didn't work, but putting an explicit call in your init function did, suggests *something* else was importing your C extension early and starting a new thread. – ncoghlan Feb 05 '13 at 11:22
  • I see. Although I don't know what could have done that. Nevertheless, if I can call `PyEval_InitThreads` and then later some other module (such as `threading`) also calls it, without causing any problems, then I'm alright with it. – Shahbaz Feb 05 '13 at 11:36
7

I ran into this exact problem as well. The documentation for anything relating to threads in CPython is unfortunately patchy at best.

Essentially, you need to do the following:

In your main thread, BEFORE any other threads are spawned, you need to call PyEval_InitThreads(). A good place to do this is right after you call PyInitialize().

Now, PyEval_InitThreads() not only initializes the Python interpreter thread-state, it also implicitly acquires the Global Interpreter Lock. This means, you need to release the lock before you call PyGILEnsure_State() in some other thread, otherwise your program will hang. You can do this with the function PyEval_ReleaseLock().

So basically, in your main thread, before any other threads are launched, you want to say:

PyInitialize();
PyEval_InitThreads();
PyEval_ReleaseLock();

Then, in any additional thread, anytime you use the Python API you need to say:

PyGILState_STATE gstate;
gstate = PyGILState_Ensure();

/* ... some code that does things with Python ... */

PyGILState_Release(gstate); 
Charles Salvia
  • 52,325
  • 13
  • 128
  • 140
  • 1
    [PyEval_ReleaseLock() is incorrect](http://bugs.python.org/issue1720250). You could use [PyEval_SaveThread/PyEval_RestoreThread instead](http://hg.python.org/cpython/file/5b0595339c9d/Include/ceval.h#l115). – jfs Aug 26 '12 at 23:59
  • @J.F.Sebastian can you explain why? – Alex Jan 15 '13 at 22:42
  • downvoted, until this gets corrected with @J.F.Sebastian 's comment – denfromufa Feb 08 '17 at 05:57