今天在邮件列表中发现一个关于 psql 内存泄露的问题 ,经过测试,确实存在内存泄露。同时,我在查看周围的代码时,还发现其它地方也存在类似的内存泄漏。本文简要记录一下该问题。
内存泄露发生在 validateSQLNamePattern()
函数中,其函数定义如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static bool validateSQLNamePattern (PQExpBuffer buf, const char *pattern, bool have_where, bool force_escape, const char *schemavar, const char *namevar, const char *altnamevar, const char *visibilityrule, bool *added_clause, int maxparts) { PQExpBufferData dbbuf; int dotcnt; bool added; initPQExpBuffer(&dbbuf); added = processSQLNamePattern(pset.db, buf, pattern, have_where, force_escape, schemavar, namevar, altnamevar, visibilityrule, &dbbuf, &dotcnt); if (added_clause != NULL ) *added_clause = added; if (dotcnt >= maxparts) { pg_log_error("improper qualified name (too many dotted names): %s" , pattern); termPQExpBuffer(&dbbuf); return false ; } if (maxparts > 1 && dotcnt == maxparts - 1 ) { if (PQdb(pset.db) == NULL ) { pg_log_error("You are currently not connected to a database." ); return false ; } if (strcmp (PQdb(pset.db), dbbuf.data) != 0 ) { pg_log_error("cross-database references are not implemented: %s" , pattern); return false ; } } return true ; }
相信您一眼就能发现哪儿出现了内存泄露。
initPQExpBuffer()
函数将调用 malloc()
函数分配 512 字节的内存。该函数仅在 dotcnt >= maxparts
时释放了其分配的内存,后续忽略了该内存的释放,从而导致内存泄露。我们可以通过诸如 \dRp
命令来测试。
由于每次泄露的内存比较小,为了能明显的感知内存泄露,我们可以通过 vim 来创建一个包含 10000 条 \dRp
的 SQL 文件,随后使用 psql 连接数据库并执行该文件,同时,使用 top
命令和 /proc/pid/status
观察内存变化。
1 $ while true ; do cat /proc/$pid /status | grep -i rss; sleep 1; done
注意,$pid
为 psql 进程的 ID。
通过上述操作可以看到内存会一直小幅度上涨,并且不会被释放。我们尝试应用下面的补丁,可以避免内存泄露。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @@ -6023,15 +6023,18 @@ validateSQLNamePattern(PQExpBuffer buf, const char *pattern, bool have_where, if (PQdb(pset.db) == NULL) { pg_log_error("You are currently not connected to a database."); + termPQExpBuffer(&dbbuf); return false; } if (strcmp(PQdb(pset.db), dbbuf.data) != 0) { pg_log_error("cross-database references are not implemented: %s", pattern); + termPQExpBuffer(&dbbuf); return false; } } + termPQExpBuffer(&dbbuf); return true; }
当我们在重复上面的测试步骤,可以发现内存基本上不会发生变化。随后我又查看了 psql/describe.c
文件中关于 validateSQLNamePattern()
调用的地方,发现其也存在大量的内存泄露。主要集中在 initPQExpBuffer()
函数调用后,在错误情况下,忽略了该内存的释放。我们可以使用诸如 \dRp .*fdkf.*
的命令来进行测试。由于补丁内容比较大,就不展示了,在下面的参考链接中包含了该 patch。
参考 [1] https://www.postgresql.org/message-id/OS0PR01MB61136CE4A6A21950B82202F8FB8F9%40OS0PR01MB6113.jpnprd01.prod.outlook.com
备注: 图片来源于 https://www.krwalters.com/2018/03/14/memory-leak-in-woodstock/。
笑林广记 - 春生帖
一财主不通文墨,谓友曰:“某人甚是欠通,清早来拜我,就写晚生帖。” 傍一监生曰:“这倒还差不远,好像这两日秋天拜客,竟有写春生帖子的哩。”