我正在尝试找出如何修复使用 Valgrind 运行此程序时遇到的这些内存泄漏。泄漏发生在
nShell_client_main
中的两个分配中。但我不是
确定如何正确释放它们。
我尝试在 nShell_Connect 处释放它们,但这会导致 libUV 中止程序。我尝试在
nShell_client_main
结束时释放它们,但是在关闭循环时出现读/写错误。有谁知道我应该如何关闭这些句柄?我读过this,这让我开始了。但是,它似乎已经过时了,因为 uv_ip4_addr
在最新版本中有不同的原型。
(
nShell_main
是“入口”点)
#include "nPort.h"
#include "nShell-main.h"
void nShell_Close(
uv_handle_t * term_handle
){
}
void nShell_Connect(uv_connect_t * term_handle, int status){
uv_close((uv_handle_t *) term_handle, 0);
}
nError * nShell_client_main(nShell * n_shell, uv_loop_t * n_shell_loop){
int uv_error = 0;
nError * n_error = 0;
uv_tcp_t * n_shell_socket = 0;
uv_connect_t * n_shell_connect = 0;
struct sockaddr_in dest_addr;
n_shell_socket = malloc(sizeof(uv_tcp_t));
if (!n_shell_socket){
// handle error
}
uv_error = uv_tcp_init(n_shell_loop, n_shell_socket);
if (uv_error){
// handle error
}
uv_error = uv_ip4_addr("127.0.0.1", NPORT, &dest_addr);
if (uv_error){
// handle error
}
n_shell_connect = malloc(sizeof(uv_connect_t));
if (!n_shell_connect){
// handle error
}
uv_error = uv_tcp_connect(n_shell_connect, n_shell_socket, (struct sockaddr *) &dest_addr, nShell_Connect);
if (uv_error){
// handle error
}
uv_error = uv_run(n_shell_loop, UV_RUN_DEFAULT);
if (uv_error){
// handle error
}
return 0;
}
nError * nShell_loop_main(nShell * n_shell){
int uv_error = 0;
nError * n_error = 0;
uv_loop_t * n_shell_loop = 0;
n_shell_loop = malloc(sizeof(uv_loop_t));
if (!n_shell_loop){
// handle error
}
uv_error = uv_loop_init(n_shell_loop);
if (uv_error){
// handle error
}
n_error = nShell_client_main(n_shell, n_shell_loop);
if (n_error){
// handle error
}
uv_loop_close(n_shell_loop);
free(n_shell_loop);
return 0;
}
断言发生在这段代码摘录中的 switch 语句末尾(取自 Github 上的 Joyent 的 libUV 页面):
void uv_close(uv_handle_t* handle, uv_close_cb close_cb) {
assert(!(handle->flags & (UV_CLOSING | UV_CLOSED)));
handle->flags |= UV_CLOSING;
handle->close_cb = close_cb;
switch (handle->type) {
case UV_NAMED_PIPE:
uv__pipe_close((uv_pipe_t*)handle);
break;
case UV_TTY:
uv__stream_close((uv_stream_t*)handle);
break;
case UV_TCP:
uv__tcp_close((uv_tcp_t*)handle);
break;
case UV_UDP:
uv__udp_close((uv_udp_t*)handle);
break;
case UV_PREPARE:
uv__prepare_close((uv_prepare_t*)handle);
break;
case UV_CHECK:
uv__check_close((uv_check_t*)handle);
break;
case UV_IDLE:
uv__idle_close((uv_idle_t*)handle);
break;
case UV_ASYNC:
uv__async_close((uv_async_t*)handle);
break;
case UV_TIMER:
uv__timer_close((uv_timer_t*)handle);
break;
case UV_PROCESS:
uv__process_close((uv_process_t*)handle);
break;
case UV_FS_EVENT:
uv__fs_event_close((uv_fs_event_t*)handle);
break;
case UV_POLL:
uv__poll_close((uv_poll_t*)handle);
break;
case UV_FS_POLL:
uv__fs_poll_close((uv_fs_poll_t*)handle);
break;
case UV_SIGNAL:
uv__signal_close((uv_signal_t*) handle);
/* Signal handles may not be closed immediately. The signal code will */
/* itself close uv__make_close_pending whenever appropriate. */
return;
default:
assert(0); // assertion is happening here
}
uv__make_close_pending(handle);
}
我可以手动调用
uv__tcp_close
,但它不在公共标头中(而且可能不是正确的解决方案)。
libuv 在调用其关闭回调之前不会处理完句柄。这正是您可以释放手柄的时刻。
我看到你调用了
uv_loop_close
,但你没有检查返回值。如果仍有待处理的句柄,它将返回 UV_EBUSY,因此您应该检查这一点。
如果要关闭循环并关闭所有句柄,则需要执行以下操作:
uv_stop
停止循环uv_walk
并在所有未关闭的句柄上调用 uv_close
uv_run
再次运行循环,以便调用所有关闭回调,并且您可以释放回调中的内存uv_loop_close
,现在应该返回0我终于弄清楚如何停止循环并清理所有句柄。 我创建了一堆句柄和 SIGINT 信号句柄:
uv_signal_t *sigint = new uv_signal_t;
uv_signal_init(uv_default_loop(), sigint);
uv_signal_start(sigint, on_sigint_received, SIGINT);
当收到 SIGINT 时(在控制台中按下 Ctrl+C),将调用
on_sigint_received
回调。
on_sigint_received
看起来像:
void on_sigint_received(uv_signal_t *handle, int signum)
{
int result = uv_loop_close(handle->loop);
if (result == UV_EBUSY)
{
uv_walk(handle->loop, on_uv_walk, NULL);
}
}
触发回调函数
on_uv_walk
:
void on_uv_walk(uv_handle_t* handle, void* arg)
{
uv_close(handle, on_uv_close);
}
它尝试关闭每个打开的 libuv 句柄。 注意:我不会在
uv_stop
之前调用uv_walk
,正如提到的saghul。
调用 on_sigint_received
函数后,libuv 循环继续执行,并在下一次迭代中为每个打开的句柄调用 on_uv_close
。如果调用 uv_stop
函数,则不会调用 on_uv_close
回调。
void on_uv_close(uv_handle_t* handle)
{
if (handle != NULL)
{
delete handle;
}
}
之后 libuv 没有打开的句柄并完成循环(从
uv_run
退出):
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
int result = uv_loop_close(uv_default_loop());
if (result)
{
cerr << "failed to close libuv loop: " << uv_err_name(result) << endl;
}
else
{
cout << "libuv loop is closed successfully!\n";
}
但是我确实遇到了一些问题。他的 on_uv_close() 函数以核心转储结束。此外 uv_signal_t 变量导致 valgrind 报告“肯定丢失”内存泄漏。
我正在使用他的代码来修复这两种情况。
void on_uv_walk(uv_handle_t* handle, void* arg) {
uv_close(handle, NULL);
}
void on_sigint_received(uv_signal_t *handle, int signum) {
int result = uv_loop_close(handle->loop);
if(result == UV_EBUSY) {
uv_walk(handle->loop, on_uv_walk, NULL);
}
}
int main(int argc, char *argv[]) {
uv_signal_t *sigint = new uv_signal_t;
uv_signal_init(uv_default_loop(), sigint);
uv_signal_start(sigint, on_sigint_received, SIGINT);
uv_loop_t* main_loop = uv_default_loop();
...
uv_run(main_loop, UV_RUN_DEFAULT));
uv_loop_close(uv_default_loop());
delete sigint;
return 0;
}
我认为这与Jeff Greer的答案基本相同,但有更多背景。
与让 valgrind 使用最新版本的 libuv 相关,我发现某些行受到
libuv/test/task.h
的祝福,使得 2 个误报释放消失了。
// You have to define this. DON'T define this to assert as it will skip the call when NDEBUG is defined
#define ASSERT(expr) expr
// Secret knowledge hidden within libuv's test folder
static void close_walk_cb(uv_handle_t* handle, void* arg) {
if (!uv_is_closing(handle))
uv_close(handle, NULL);
}
static void close_loop(uv_loop_t* loop) {
uv_walk(loop, close_walk_cb, NULL);
uv_run(loop, UV_RUN_DEFAULT);
}
/* This macro cleans up the event loop. This is used to avoid valgrind
* warnings about memory being "leaked" by the event loop.
*/
#define MAKE_VALGRIND_HAPPY(loop) \
do { \
close_loop(loop); \
ASSERT(0 == uv_loop_close(loop)); \
uv_library_shutdown(); \
} while (0)
你就这样用吧
// Signal handling stuff for ctrl+C
// This one is needed if you something set on server->data needs to be freed
void on_close(uv_handle_t* handle) {
// free(handle->data); <- Uncomment if you know you need to free this
}
// Actual signal handler
void on_signal(uv_signal_t *handle, int signum) {
uv_tcp_t *server = handle->data;
uv_close((uv_handle_t*) server, on_close);
uv_stop(handle->loop);
}
int main() {
uv_tcp_t server;
uv_tcp_init(uv_default_loop(), &server);
struct sockaddr_in addr;
uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);
// Don't forget this step if you're closing the program with ctrl+C
uv_signal_t sig;
uv_signal_init(loop, &sig);
sig.data = &server;
uv_signal_start(&sig, on_signal, SIGINT);
uv_run(loop, UV_RUN_DEFAULT);
uv_loop_close(loop);
MAKE_VALGRIND_HAPPY(uv_default_loop());
}
在没有请求的情况下使用 ctrl-c 启动和关闭服务器时 valgrind 的输出
MAKE_VALGRIND_HAPPY
被注释掉了
make; valgrind --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=1 --exit-on-first-error=yes ./server
make: `server' is up to date.
==9426== Memcheck, a memory error detector
==9426== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==9426== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==9426== Command: ./server
==9426==
^Csig.c:410: Closing
==9426==
==9426== HEAP SUMMARY:
==9426== in use at exit: 200 bytes in 2 blocks
==9426== total heap usage: 4 allocs, 2 frees, 1,256 bytes allocated
==9426==
==9426== 72 bytes in 1 blocks are still reachable in loss record 1 of 2
==9426== at 0x54AE1F4: calloc (vg_replace_malloc.c:1328)
==9426== by 0x54EDD6F: uv_loop_init (in /usr/lib64/libuv.so.1.0.0)
==9426== by 0x54E64A7: uv_default_loop (in /usr/lib64/libuv.so.1.0.0)
==9426== by 0x401B3F: main (sig.c:445)
==9426==
==9426==
==9426== Exit program on first error (--exit-on-first-error=yes)
刚刚使用 ctrl-c 启动和关闭服务器时 valgrind 的输出,没有请求
MAKE_VALGRIND_HAPPY
在 main
结束时执行
make; valgrind --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=1 --exit-on-first-error=yes ./server
clang -o server -Wall -Wsizeof-pointer-div -Wmissing-field-initializers -O0 -g3 sig.c -luv
==9542== Memcheck, a memory error detector
==9542== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==9542== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
==9542== Command: ./server
==9542==
^Csig.c:410: Closing
==9542==
==9542== HEAP SUMMARY:
==9542== in use at exit: 0 bytes in 0 blocks
==9542== total heap usage: 4 allocs, 4 frees, 1,256 bytes allocated
==9542==
==9542== All heap blocks were freed -- no leaks are possible
==9542==
==9542== For lists of detected and suppressed errors, rerun with: -s
==9542== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)