[
https://issues.apache.org/jira/browse/THRIFT-5881?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18118175#comment-18118175
]
Jens Geyer commented on THRIFT-5881:
------------------------------------
The leak is confirmed. The fix is to chain up to the parent class's finalize,
not to clear the instance data by hand.
The finalize function the c_glib generator writes for structs and exceptions
(and for the argument and result structs of every service method) frees the
members, but never calls the parent class's finalize. So GObject's own
finalize, {{g_object_finalize()}}, never runs for these objects. That is the
function that calls {{g_datalist_clear()}} on the instance data, so everything
kept there is lost: data attached with {{g_object_set_data_full()}}, and the
data {{g_object_unref()}} itself stores there while it disposes of the object.
Measured with GLib 2.72.4 (Ubuntu 22.04) under valgrind: creating and releasing
a generated struct 1,000 times lost 48,000 bytes in 1,000 blocks, all allocated
inside {{g_object_unref()}}. With the fix, nothing is lost.
h4. Why not {{g_datalist_clear()}} in the generated finalize
* Chaining up is what GObject expects of an overridden finalize. It runs
whatever the finalize functions further up the chain do, now and in future GLib
versions. A hand-written {{g_datalist_clear()}} repeats one step of
{{g_object_finalize()}} and reaches into the instance's internals; anything
else in that chain, including a finalize added to {{ThriftStruct}} later, would
still be skipped.
* It would not cover the library classes, which have the same gap (see below).
h4. The change
The generated code now does what {{G_DEFINE_TYPE}} does for a hand-written
class: it keeps the parent class in a static variable, sets it in class_init
and chains up at the end of finalize:
{code:c}
static gpointer t_test_xtruct_parent_class = NULL;
static void
t_test_xtruct_finalize (GObject *object)
{
/* ... free the members, as before ... */
G_OBJECT_CLASS (t_test_xtruct_parent_class)->finalize (object);
}
static void
t_test_xtruct_class_init (TTestXtructClass * cls)
{
/* ... */
t_test_xtruct_parent_class = g_type_class_peek_parent (cls);
/* ... */
}
{code}
h4. The same gap in the library
Eight hand-written classes override finalize without chaining up:
{{ThriftApplicationException}}, {{ThriftBufferedTransport}},
{{ThriftFDTransport}}, {{ThriftFramedTransport}}, {{ThriftMemoryBuffer}},
{{ThriftServerSocket}}, {{ThriftSocket}} and {{ThriftZlibTransport}}.
{{ThriftSSLSocket}} chains up correctly, but into {{ThriftSocket}}'s finalize,
so its data was lost as well: 72 bytes for a single instance in the same
measurement, none after the fix. The generated clients, handlers and
processors, and the remaining library classes, already chain up or do not
override finalize. The library classes are fixed under this ticket too, so it
now also carries the C glib - Library component.
A new test, {{testfinalize}}, and a new case in {{testzlibtransport}} attach
data with a destroy notification to an object, drop the last reference and
check that the notification ran. They fail without the fix and pass with it.
The change is in [PR #3934|https://github.com/apache/thrift/pull/3934].
_Drafted with AI assistance (Claude Opus 5.5); reviewed and posted by Jens
Geyer._
> [Glib] The {class}_finalize method created by c_glib generator is leaking
> memory
> --------------------------------------------------------------------------------
>
> Key: THRIFT-5881
> URL: https://issues.apache.org/jira/browse/THRIFT-5881
> Project: Thrift
> Issue Type: Bug
> Components: C glib - Compiler, C glib - Library
> Reporter: Kevin Rodgers
> Priority: Major
> Time Spent: 10m
> Remaining Estimate: 0h
>
> In newer versions of glib the {{g_object_unref}} call has been updated to
> freeze the object’s notification queue. This causes memory to be allocated
> and saved in
> [object->qdata|https://github.com/GNOME/glib/blob/c1e8174992182d7a6e6499ea9ab2cf76ad81a3ca/gobject/gobject.c#L4828].This
> memory is freed when the notification queue is thawed, which usually happens
> in the \{class}_finalize method call of the object’s parent class.
> In the thrift generated finalize method there is no call to finalize the
> parent object and the memory that has been allocated to freeze notifications
> is lost. This causes the process size to increase as objects are created and
> destroyed. I have fixed this in my environment by updating
> [t_c_glib_generator.cc|https://github.com/apache/thrift/blob/master/compiler/cpp/src/thrift/generate/t_c_glib_generator.cc#L3306]{color:#172b4d}
> to call g_datalist_clear{color} at the very end of each \{class}_finalize
> method.
> {{Example:}}
> static void
> {{test_object_finalize (GObject *object)}}
> {
> {{ TestObject *tobject = TEST_OBJECT (object);}}
> {{ /* satisfy -Wall in case we don't use tobject */}}
> {{ THRIFT_UNUSED_VAR (tobject);}}
> {{ if (tobject->versions != NULL)}}
> {{ {}}
> {{ g_free(tobject->versions);}}
> {{ tobject->versions = NULL;}}
> }
> {{ g_datalist_clear(&object->qdata); // added to free memory}}
> {{}}}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)