Правильное закрытие libUV

Я пытаюсь выяснить, как исправить эти утечки памяти, которые я получаю при запуске этой программы с Valgrind. Утечки происходят с двумя распределениями в nShell_client_main. Но я не уверен, как правильно их освободить.

Я пытался освободить их в nShell_Connect, но это приводит к тому, что libUV прерывает программу. Я пытался освободить их в конце nShell_client_main, но потом получаю ошибки чтения/записи при закрытии цикла. Кто-нибудь знает, как я должен закрыть эти ручки? Я прочитал это, и я начал. Но он выглядит устаревшим, потому что 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 в этом отрывке кода (взято со страницы Joyent libUV на Github):

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 вручную, но его нет в общедоступных заголовках (и, вероятно, в любом случае это не правильное решение).


person tay10r    schedule 02.09.2014    source источник
comment
Напомните, чтобы избежать проверки вашего кода; расположение параметров вашей функции неортодоксально и действительно странно (и поэтому трудно читаемо) — а также не совсем последовательно.   -  person Jonathan Leffler    schedule 02.09.2014
comment
@JonathanLeffler да, я начал весь проект с написания длинных функций, разбитых вот так. Я немного жалею об этом сейчас, но не было возможности переписать их все.   -  person tay10r    schedule 02.09.2014


Ответы (2)


libuv не выполняется с дескриптором, пока не будет вызван обратный вызов close. Это именно тот момент, когда вы можете освободить ручку.

Я вижу, вы вызываете uv_loop_close, но не проверяете возвращаемое значение. Если все еще есть ожидающие дескрипторы, он вернет UV_EBUSY, поэтому вы должны проверить это.

Если вы хотите закрыть цикл и закрыть все ручки, вам нужно сделать следующее:

  • Используйте uv_stop, чтобы остановить цикл
  • Используйте uv_walk и вызовите uv_close для всех дескрипторов, которые не закрываются
  • Снова запустите цикл с uv_run, чтобы вызвать все обратные вызовы закрытия, и вы можете освободить память в обратных вызовах.
  • Вызовите uv_loop_close, теперь он должен вернуть 0
person saghul    schedule 14.09.2014
comment
Какой режим выполнения мы должны использовать при вызове uv_run? - person ruipacheco; 29.01.2015
comment
Используйте UV_RUN_DEFAULT, так как все дескрипторы закрыты, и может потребоваться более одной итерации цикла, чтобы все они закрылись и вызвали обратные вызовы закрытия. - person saghul; 29.01.2015

Наконец-то я понял, как остановить цикл и очистить все дескрипторы. Я создал набор дескрипторов и дескриптор сигнала 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, как упомянул саггул. После вызова функции 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";
}
person Konstantin Gindemit    schedule 13.11.2017