Thin Ice
--------
There are a few situations where seemingly harmless use of a borrowed
reference can lead to problems. These all have to do with implicit
invocations of the interpreter, which can cause the owner of a
reference to dispose of it.
The first and most important case to know about is using `Py_DECREF()'
on an unrelated object while borrowing a reference to a list item. For
instance:
bug(PyObject *list) {
PyObject *item = PyList_GetItem(list, 0);
PyList_SetItem(list, 1, PyInt_FromLong(0L));
PyObject_Print(item, stdout, 0); /* BUG! */
}
This function first borrows a reference to `list[0]', then replaces
`list[1]' with the value `0', and finally prints the borrowed
reference. Looks harmless, right? But it's not!
Let's follow the control flow into `PyList_SetItem()'. The list owns
references to all its items, so when item 1 is replaced, it has to
dispose of the original item 1. Now let's suppose the original item 1
was an instance of a user-defined class, and let's further suppose that
the class defined a `__del__()' method. If this class instance has a
reference count of 1, disposing of it will call its `__del__()' method.
Since it is written in Python, the `__del__()' method can execute
arbitrary Python code. Could it perhaps do something to invalidate the
reference to `item' in `bug()'? You bet! Assuming that the list
passed into `bug()' is accessible to the `__del__()' method, it could
execute a statement to the effect of `del list[0]', and assuming this
was the last reference to that object, it would free the memory
associated with it, thereby invalidating `item'.
The solution, once you know the source of the problem, is easy:
temporarily increment the reference count. The correct version of the
function reads:
no_bug(PyObject *list) {
PyObject *item = PyList_GetItem(list, 0);
Py_INCREF(item);
PyList_SetItem(list, 1, PyInt_FromLong(0L));
PyObject_Print(item, stdout, 0);
Py_DECREF(item);
}
This is a true story. An older version of Python contained variants of
this bug and someone spent a considerable amount of time in a C
debugger to figure out why his `__del__()' methods would fail...
The second case of problems with a borrowed reference is a variant
involving threads. Normally, multiple threads in the Python
interpreter can't get in each other's way, because there is a global
lock protecting Python's entire object space. However, it is possible
to temporarily release this lock using the macro
`Py_BEGIN_ALLOW_THREADS', and to re-acquire it using
`Py_END_ALLOW_THREADS'. This is common around blocking I/O calls, to
let other threads use the CPU while waiting for the I/O to complete.
Obviously, the following function has the same problem as the previous
one:
bug(PyObject *list) {
PyObject *item = PyList_GetItem(list, 0);
Py_BEGIN_ALLOW_THREADS
...some blocking I/O call...
Py_END_ALLOW_THREADS
PyObject_Print(item, stdout, 0); /* BUG! */
}