Reputation: 207
I'm encountering a very strange problem with the Python C API. I have a globally scoped struct 'dcon' that contains a PyObject 'device' member.
static status_t get_mac_addr(uint8_t const ** addr,
size_t * const size){
static uint8_t device_mac_addr[6] = {0};
*addr = device_mac_addr;
*size = sizeof device_mac_addr;
if(PyObject_HasAttrString(dcon.device, "mac_address") == 1){
...
}
return 0;
}
It appears that whenever the 'mac_address' attribute exists, the code executes just fine. Oddly, when 'mac_address' is not a defined attribute I encounter a segault (Segmentation fault (core dumped)
). Here is the backtrace when the segfault is encountered:
(gdb) bt
#0 0x00000000004ca607 in PyErr_Restore ()
#1 0x00000000004aa29f in PyErr_Format ()
#2 0x000000000049e6ee in _PyObject_GenericGetAttrWithDict ()
#3 0x0000000000486387 in PyObject_GetAttrString ()
#4 0x00000000004ea7d7 in PyObject_HasAttrString ()
#5 0x00007ffff4f2056d in get_mac_addr (size=0x7ffff4f1cd28, addr=<optimized out>) at config.c:165
I'm a bit of a novice with the Python C API. My initial thought is that I'm doing something wrong with reference counting, but I can't seem to wrap my head around it.
Upvotes: 0
Views: 715
Reputation: 9041
In a multi-threaded application, any thread calling Python C API functions must first ensure that the thread is holding the Global Interpreter Lock. For functions called from Python code this isn't a problem because the lock is already held.
This isn't the case for code called from outside of the interpreter (callbacks, etc.).
In such case, the GIL must be acquired before Python API functions can be safely called.
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
...
PyGILState_Release(gstate);
This not only ensures that the GIL is held but it also creates the Python thread state for threads that have been created outside of Python (using direct OS calls, not thread.start_new_thread()
).
Any Python exceptions that may occur while calling Python APIs, must be handled before releasing the GIL.
Note that if another thread is currently holding the lock (for example because it's executing Python code), the Ensure call will block until the lock is released by the other thread.
It is ok to use these calls even if the lock is already acquired provided that every call to Ensure() has a matching call to Release().
Upvotes: 1