threaded_resolver_worker_cb leaks memory when lookup fails and connection is already canceled
threaded_resolver_worker_cb
leaks local_error
when should_return
is false.
gchar *name = do_lookup_by_address (data->lookup_by_address.address,
cancellable,
&local_error);
g_mutex_lock (&data->lock);
should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, COMPLETED);
g_mutex_unlock (&data->lock);
if (should_return)
{
if (addresses != NULL)
g_task_return_pointer (task, g_steal_pointer (&addresses), (GDestroyNotify) g_resolver_free_addresses);
else
g_task_return_error (task, g_steal_pointer (&local_error));
}
It seems g_error_free(local_error)
should be called when should_return
is false.
Tested with the below code
#include <gio/gio.h>
#include <stdio.h>
#include <stdlib.h>
static void on_connected(GObject *source_object, GAsyncResult *res, gpointer user_data);
static GMainLoop *loop;
static const char *host;
static int port;
static volatile int mode = 0;
static GCancellable *cancellable = NULL;
static gboolean timer_func(gpointer user_data) {
switch (mode) {
case 0: {
break;
}
case 1: {
// cancel
printf("calling cancel\n");
g_cancellable_cancel(cancellable);
mode = 2;
return TRUE;
}
case 2: {
printf("calling unref\n");
g_object_unref(cancellable);
mode = 0;
return TRUE;
}
}
printf("connecting %s\n", host);
GSocketClient *client = g_socket_client_new();
//GProxyResolver *proxy_resolver = g_proxy_resolver_get_default();
//g_socket_client_set_proxy_resolver(client, proxy_resolver);
cancellable = g_cancellable_new();
g_socket_client_connect_to_host_async(client,
host,
port,
cancellable, // GCancellable object
on_connected, // callback
NULL); //
mode = 1;
//return FALSE; //one shot
return TRUE; // loop
}
int main(int argc, char *argv[]) {
if (argc >= 2) {
host = argv[1];
} else {
host = "172.28.79.99";
}
if (argc >= 3) {
port = atoi(argv[2]);
} else {
port = 9051;
}
g_timeout_add_seconds(5, timer_func, NULL);
// start GLib main loop
loop = g_main_loop_new(NULL, FALSE);
g_main_loop_run(loop);
printf("main run done\n");
// cleanup resources
g_main_loop_unref(loop);
return 0;
}
// called when connection is done
void on_connected(GObject *source_object, GAsyncResult *res, gpointer user_data) {
GSocketClient *client = G_SOCKET_CLIENT(source_object);
GError *error = NULL;
// get connection result
GSocketConnection *connection = g_socket_client_connect_to_host_finish(client, res, &error);
if (error != NULL) {
fprintf(stderr, "Error: %s\n", error->message);
if (connection != NULL){
// it seems it doesn't come here
printf("unref connection\n");
g_object_unref(connection);
}
g_error_free(error);
} else if (connection != NULL) {
printf("Connected to the host successfully!\n");
g_io_stream_close(G_IO_STREAM(connection), NULL, NULL);
g_object_unref(connection);
//g_socket_connection_close(connection, NULL, NULL);
//g_object_unref(connection);
}
g_object_unref(client);
}
I've added sleep(10)
after getaddrinfo
in do_lookup_by_name()
to reproduce the leak.
retval = getaddrinfo (hostname, NULL, &addrinfo_hints, &res);
sleep(10); // added to reproduce the leak
valgrind --leak-check=full ./glibapp notexist.com
<snip>
==898688== 196 (32 direct, 164 indirect) bytes in 2 blocks are definitely lost in loss record 1,452 of 1,508
==898688== at 0x4845828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==898688== by 0x4B3CB7D: g_malloc (gmem.c:130)
==898688== by 0x4B60EAB: g_slice_alloc (gslice.c:252)
==898688== by 0x4B60ED1: g_slice_alloc0 (gslice.c:276)
==898688== by 0x4B10749: g_error_allocate (gerror.c:710)
==898688== by 0x4B107BF: g_error_new_steal (gerror.c:724)
==898688== by 0x4B108E4: g_error_new_valist (gerror.c:767)
==898688== by 0x4B10FF8: g_set_error (gerror.c:965)
==898688== by 0x494FF02: do_lookup_by_name (gthreadedresolver.c:340)
==898688== by 0x49529D3: threaded_resolver_worker_cb (gthreadedresolver.c:1573)
==898688== by 0x4B73A6F: g_thread_pool_thread_proxy (gthreadpool.c:350)
==898688== by 0x4B732D0: g_thread_proxy (gthread.c:831)
Tested on glib2.0-2.78.0 (ubuntu23.10)