2008-12-08 2 views
5

Функция ниже берет дескриптор файла python, считывает в упакованных двоичных данных из файла, создает словарь Python и возвращает его. Если я зациклирую его бесконечно, он будет постоянно потреблять ОЗУ. Что случилось с моей RefCounting?Почему мой Python C Extension утечки памяти?

static PyObject* __binParse_getDBHeader(PyObject *self, PyObject *args){ 

PyObject *o; //generic object 
PyObject* pyDB = NULL; //this has to be a py file object 

if (!PyArg_ParseTuple(args, "O", &pyDB)){ 
    return NULL; 
} else { 
    Py_INCREF(pyDB); 
    if (!PyFile_Check(pyDB)){ 
     Py_DECREF(pyDB); 
     PyErr_SetString(PyExc_IOError, "argument 1 must be open file handle"); 
     return NULL; 
    } 
} 

FILE *fhDB = PyFile_AsFile(pyDB); 

long offset = 0; 
DB_HEADER *pdbHeader = malloc(sizeof(DB_HEADER)); 
fseek(fhDB,offset,SEEK_SET); //at the beginning 
fread(pdbHeader, 1, sizeof(DB_HEADER), fhDB); 
if (ferror(fhDB)){ 
    fclose(fhDB); 
    Py_DECREF(pyDB); 
    PyErr_SetString(PyExc_IOError, "failed reading database header"); 
    return NULL; 
} 
Py_DECREF(pyDB); 

PyObject *pyDBHeader = PyDict_New(); 
Py_INCREF(pyDBHeader); 

o=PyInt_FromLong(pdbHeader->version_number); 
PyDict_SetItemString(pyDBHeader, "version", o); 
Py_DECREF(o); 

PyObject *pyTimeList = PyList_New(0); 
Py_INCREF(pyTimeList); 

int i; 
for (i=0; i<NUM_DRAWERS; i++){ 
    //epochs 
    o=PyInt_FromLong(pdbHeader->last_good_test[i]); 
    PyList_Append(pyTimeList, o); 
    Py_DECREF(o); 
} 
PyDict_SetItemString(pyDBHeader, "lastTest", pyTimeList); 
Py_DECREF(pyTimeList); 

o=PyInt_FromLong(pdbHeader->temp); 
PyDict_SetItemString(pyDBHeader, "temp", o); 
Py_DECREF(o); 

free(pdbHeader); 
return (pyDBHeader); 
} 

Спасибо за взглянуть,

LarsenMTL

ответ

16

PyDict_New() возвращает новую ссылку, проверьте docs для PyDict. Поэтому, если вы увеличиваете количество ссылок сразу после его создания, у вас есть две ссылки на него. Один передается вызывающему абоненту, когда вы возвращаете его в качестве значения результата, а другой никогда не выходит.

Вам также не нужно увеличивать значение pyTimeList. Это ваше, когда вы его создаете. Однако вам нужно уменьшить его, но вы только уменьшаете его один раз, так что он также просочился.

Вам также не нужно звонить Py_INCREF по телефону pyDB. Это заимствованная ссылка, и она не исчезнет, ​​пока ваша функция не вернется, потому что она все еще упоминается в нижнем стеке стека.

Только в том случае, если вы хотите сохранить ссылку в другой структуре где-нибудь, вам нужно указать сумму пересчета.

Cf. API docs

+0

Torsten, спасибо, я только что узнал больше в ваших 4 параграфах, затем я все время смотрел на документы. Я проверю все свои заимствованные и верну новые ссылки. – Mark 2008-12-08 20:40:28

5

OT: Использование последовательных вызовов PyList_Append является проблемой производительности. Так как вы знаете, сколько результатов вы получите заранее, вы можете использовать:

PyObject *pyTimeList = PyList_New(NUM_DRAWERS); 
int i; 
for (i=0; i<NUM_DRAWERS; i++){ 
    o = PyInt_FromLong(pdbHeader->last_good_test[i]); 
    PyList_SET_ITEM(pyTimeList, i, o); 
} 

Заметим, что вы не можете уменьшить RefCount из o после вызова PyList_SET_ITEM, поскольку он «крадет» ссылку. Проверьте docs.

2

Я не знаю о Python-C. Тем не менее, мой опыт с подсчетом ссылок на COM говорит о том, что вновь созданный объект с подсчетом ссылок имеет счетчик ссылок . Итак, ваш Py_INCREF (pyDB) после PyArg_ParseTuple (args, «O», & pyDB) и PyObject * pyDBHeader = PyDict_New(); являются виновниками. Их ссылочный счет уже равен 2.

Смежные вопросы