我是libgpiod的作者,在最新版本中,我提供了一组实现为C扩展模块的面向对象的python3绑定。
该模块的完整代码可以在here中找到。
最近,一个用户报告了模块中的内存泄漏。从那时起,我一直在尝试对其进行调试,并设法找到并修复了其他一些与内存相关的问题,但并不是造成这种确切泄漏的罪魁祸首。
下面是报告者用来触发问题的脚本:
#!/usr/bin/env python3
import gpiod
import logging
import os
import psutil
import sys
import time
this_process = psutil.Process(os.getpid())
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
chip = gpiod.Chip('1', gpiod.Chip.OPEN_BY_NUMBER)
gpio_line = chip.get_line(12)
gpio_line.request(consumer="test", type=gpiod.LINE_REQ_DIR_OUT)
count = 0
mem_used_prev = 0
while True:
mem_used = this_process.memory_info().rss
if mem_used != mem_used_prev:
logging.info('count: {} memory usage: {}'.format(count, this_process.memory_info().rss))
mem_used_prev = mem_used
gpio_line.set_value(1)
count += 1
示例输出:
2018-07-19 11:21:13,505 - INFO - count: 0 memory usage: 13459456
2018-07-19 11:21:13,516 - INFO - count: 638 memory usage: 14008320
2018-07-19 11:21:13,529 - INFO - count: 1298 memory usage: 14278656
2018-07-19 11:21:13,543 - INFO - count: 1958 memory usage: 14548992
2018-07-19 11:21:13,557 - INFO - count: 2618 memory usage: 14819328
2018-07-19 11:21:13,569 - INFO - count: 3278 memory usage: 15089664
2018-07-19 11:21:13,583 - INFO - count: 3938 memory usage: 15360000
2018-07-19 11:21:13,596 - INFO - count: 4598 memory usage: 15630336
2018-07-19 11:21:13,611 - INFO - count: 5258 memory usage: 15900672
每两次迭代,内存使用量就会突然增加。我想这是调整堆大小的时候,但是实际的泄漏可能在每次迭代时都会发生。
在进行调查时,我注意到使用单个GPIO线进行的所有操作都会发生泄漏,这涉及将该单个对象包装到代表一组GPIO线的LineBulk对象中-这样做是为了重用代码,以便gpiod_Line_set_value()
只需调用gpiod_LineBulk_set_values()
即可获得由一行组成的集合。
接下来,我注意到调用chip.get_lines()
时也会发生泄漏,这也需要创建LineBulk对象。
基于此,我相信泄漏发生在gpiod_LineBulk_init()
中的某个地方,具体实现如下:
static int gpiod_LineBulk_init(gpiod_LineBulkObject *self, PyObject *args)
{
PyObject *lines, *iter, *next;
Py_ssize_t i;
int rv;
rv = PyArg_ParseTuple(args, "O", &lines);
if (!rv)
return -1;
self->num_lines = PyObject_Size(lines);
if (self->num_lines < 1) {
PyErr_SetString(PyExc_TypeError,
"Argument must be a non-empty sequence");
return -1;
}
if (self->num_lines > GPIOD_LINE_BULK_MAX_LINES) {
PyErr_SetString(PyExc_TypeError,
"Too many objects in the sequence");
return -1;
}
self->lines = PyMem_RawCalloc(self->num_lines, sizeof(PyObject *));
if (!self->lines) {
PyErr_SetString(PyExc_MemoryError, "Out of memory");
return -1;
}
iter = PyObject_GetIter(lines);
if (!iter) {
PyMem_RawFree(self->lines);
return -1;
}
for (i = 0;;) {
next = PyIter_Next(iter);
if (!next) {
Py_DECREF(iter);
break;
}
if (next->ob_type != &gpiod_LineType) {
PyErr_SetString(PyExc_TypeError,
"Argument must be a sequence of GPIO lines");
Py_DECREF(next);
Py_DECREF(iter);
goto errout;
}
self->lines[i++] = next;
}
self->iter_idx = -1;
return 0;
errout:
if (i > 0) {
for (--i; i >= 0; i--)
Py_DECREF(self->lines[i]);
}
PyMem_RawFree(self->lines);
self->lines = NULL;
return -1;
}
此函数采用一系列Line对象并将其打包到LineBulk对象中,该对象实现了一组允许操作GPIO的方法。
我一直在尝试使用各种工具找出罪魁祸首。 Tracemalloc并没有太大帮助,因为它没有纳入C代码中。我跟踪了PyObject_Malloc和Free并使用gdb调用了相关的析构函数,但是一切似乎都还不错,在需要时对象似乎被破坏了。 Valgrind也不报告任何泄漏。
我目前没有主意,也没有使用python C API的丰富经验。任何建议都将不胜感激。
答案 0 :(得分:0)
只需结束这个问题:我就发现了问题。这是因为没有调用PyObject_Del()作为析构函数的最后一个动作。