Re: Garbage collection of userdata inside userdata

"'Stefano' via lua-l" <[email protected]> Fri, 17 Jul 2026 04:50:39 -0700 (PDT)
Newsgroups gmane.comp.lang.lua.general
Message-ID <[email protected]>
------=_Part_411460_754449308.1784289039836
Content-Type: multipart/alternative; 
	boundary="----=_Part_411461_1651373719.1784289039836"

------=_Part_411461_1651373719.1784289039836
Content-Type: text/plain; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

Thanks, Sean. What you suggested worked. Initially I was hesitant to try it=
=20
because I thought that even if I tie the term set to the data set lifespan,=
=20
it would still be double-freed.=20
But the tests now pass and Valgrind doesn't raise any invalid read/free=20
warnings.=20

TopchetoEU: the only place I am freeing the pointers is in the __gc methods=
=20
related to the Lua objects containing the C data. The complication with=20
this particular structure is that the data set is meant to free the=20
contained term set in its C free function, but also, when I access the term=
=20
set from the data set in Lua, a new Lua term set object is created, that=20
points to the structure owned by the data set. Since a term set can also be=
=20
an independently created object, I must define a __gc method for that too.=
=20

On Wednesday, July 15, 2026 at 6:29:36=E2=80=AFAM UTC-4 TopchetoEU wrote:

> On Wednesday, 15 July 2026 00:27:17 EEST 'Stefano Cossu' via lua-l wrote:
> > Hello,
> >=20
> > I have a Lua C module defining two different userdata types, one is
> > called a Dataset and the other a TermSet. Both can be used and
> > garbage-collected independently. A Dataset object has a TermSet as a
> > "gr" attribute. I am setting this term set as a uservalue for the=20
> dataset.
> >=20
> > The problem is that if I access the term set from the dataset, it gets
> > garbage collected when it falls out of scope, and then, when the datase=
t
> > is garbage collected too, it calls a free function that attempts to fre=
e
> > the term set that was already freed, resulting in a double-free error.
> >=20
> > This is the function used for creating the object:
> >=20
> > // Called by dataset factories
> > int dataset_to_udata (lua_State *L, VOLK_Store *store)
> > {
> > VOLK_Dataset **dsp =3D lua_newuserdatauv (L, sizeof (*dsp), 2);
> > luaL_getmetatable (L, "volksdata.Dataset");
> > lua_setmetatable (L, -2);
> > VOLK_Dataset *ds =3D VOLK_dataset_new (store);
> > LUA_NLCHECK (ds, "Error creating dataset.");
> > *dsp =3D ds;
> >=20
> > // Set store uservalue.
> > lua_pushvalue(L, 1);
> > lua_setiuservalue (L, -2, 1);
> >=20
> > // Set gr uservalue.
> > VOLK_TermSet **ts_p =3D lua_newuserdata (L, sizeof (*ts_p));
> > LUA_NLCHECK (ts_p, "Error allocating memory for term set.");
> > luaL_getmetatable (L, "volksdata.TermSet");
> > lua_setmetatable (L, -2);
> > lua_setiuservalue (L, -2, 2);
> >=20
> > return 1;
> > }
> >=20
> > And the access function:
> >=20
> > static int
> > get_graphs (lua_State *L)
> > {
> > (void) check_dataset (L, 1);
> > lua_getiuservalue (L, 1, 2);
> >=20
> > return 1;
> > }
> >=20
> > I thought that setting the uservalue would prevent the term set from
> > being garbage collected when it falls out of scope, but I guess that it
> > still gets garbage collected when the dataset is destroyed.
> >=20
> > Which approach shall I use? I guess I'd want to prevent the independent
> > term set from ever being garbage collected. I can't use a light userdat=
a
> > pointer because the access function must return a full usable Lua
> > object. I also shouldn't duplicate the term set because it's expensive
> > and the two copies could fall out of sync, which is undesirable.
> >=20
> > Thanks for any suggestions.
> > s
>
> If your objects are GC-managed, conceptually, you should *never* free the=
m=20
> manually. Instead, what I'd do is have the term set in one uservalue slot=
=20
> in=20
> the dataset userdata. This way, dataset will keep its term set alive unti=
l=20
> it=20
> itself falls out of scope *by design*.
>
> Furthermore, do all deallocation stuff only in the `__gc` metamethod of=
=20
> the=20
> uservalues, and *never* deallocate one GC'd object's data from aonther's=
=20
> `__gc` metamethod.
>
> P.S.: Since lua 5.3-ish (afaik), userdata can have multiple uservalue=20
> slots,=20
> use that to your advantage.
>
>
>
>

--=20
You received this message because you are subscribed to the Google Groups "=
lua-l" group.
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/lua-l/807f4=
393-a19a-4e96-9386-fc4d77312f58n%40googlegroups.com.

------=_Part_411461_1651373719.1784289039836
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

Thanks, Sean. What you suggested worked. Initially I was hesitant to try it=
 because I thought that even if I tie the term set to the data set lifespan=
, it would still be double-freed.=C2=A0<div>But the tests now pass and Valg=
rind doesn't raise any invalid read/free warnings.=C2=A0</div><div><br /><d=
iv>TopchetoEU: the only place I am freeing the pointers is in the __gc meth=
ods related to the Lua objects containing the C data. The complication with=
 this particular structure is that the data set is meant to free the contai=
ned term set in its C free function, but also, when I access the term set f=
rom the data set in Lua, a new Lua term set object is created, that points =
to the structure owned by the data set. Since a term set can also be an ind=
ependently created object, I must define a __gc method for that too.=C2=A0<=
/div><div><br /></div></div><div class=3D"gmail_quote"><div dir=3D"auto" cl=
ass=3D"gmail_attr">On Wednesday, July 15, 2026 at 6:29:36=E2=80=AFAM UTC-4 =
TopchetoEU wrote:<br/></div><blockquote class=3D"gmail_quote" style=3D"marg=
in: 0 0 0 0.8ex; border-left: 1px solid rgb(204, 204, 204); padding-left: 1=
ex;">On Wednesday, 15 July 2026 00:27:17 EEST &#39;Stefano Cossu&#39; via l=
ua-l wrote:
<br>&gt; Hello,
<br>&gt;=20
<br>&gt; I have a Lua C module defining two different userdata types, one i=
s
<br>&gt; called a Dataset and the other a TermSet. Both can be used and
<br>&gt; garbage-collected independently. A Dataset object has a TermSet as=
 a
<br>&gt; &quot;gr&quot; attribute. I am setting this term set as a uservalu=
e for the dataset.
<br>&gt;=20
<br>&gt; The problem is that if I access the term set from the dataset, it =
gets
<br>&gt; garbage collected when it falls out of scope, and then, when the d=
ataset
<br>&gt; is garbage collected too, it calls a free function that attempts t=
o free
<br>&gt; the term set that was already freed, resulting in a double-free er=
ror.
<br>&gt;=20
<br>&gt; This is the function used for creating the object:
<br>&gt;=20
<br>&gt; // Called by dataset factories
<br>&gt; int dataset_to_udata (lua_State *L, VOLK_Store *store)
<br>&gt; {
<br>&gt;      VOLK_Dataset **dsp =3D lua_newuserdatauv (L, sizeof (*dsp), 2=
);
<br>&gt;      luaL_getmetatable (L, &quot;volksdata.Dataset&quot;);
<br>&gt;      lua_setmetatable (L, -2);
<br>&gt;      VOLK_Dataset *ds =3D VOLK_dataset_new (store);
<br>&gt;      LUA_NLCHECK (ds, &quot;Error creating dataset.&quot;);
<br>&gt;      *dsp =3D ds;
<br>&gt;=20
<br>&gt;      // Set store uservalue.
<br>&gt;      lua_pushvalue(L, 1);
<br>&gt;      lua_setiuservalue (L, -2, 1);
<br>&gt;=20
<br>&gt;      // Set gr uservalue.
<br>&gt;      VOLK_TermSet **ts_p =3D lua_newuserdata (L, sizeof (*ts_p));
<br>&gt;      LUA_NLCHECK (ts_p, &quot;Error allocating memory for term set=
.&quot;);
<br>&gt;      luaL_getmetatable (L, &quot;volksdata.TermSet&quot;);
<br>&gt;      lua_setmetatable (L, -2);
<br>&gt;      lua_setiuservalue (L, -2, 2);
<br>&gt;=20
<br>&gt;      return 1;
<br>&gt; }
<br>&gt;=20
<br>&gt; And the access function:
<br>&gt;=20
<br>&gt; static int
<br>&gt; get_graphs (lua_State *L)
<br>&gt; {
<br>&gt;      (void) check_dataset (L, 1);
<br>&gt;      lua_getiuservalue (L, 1, 2);
<br>&gt;=20
<br>&gt;      return 1;
<br>&gt; }
<br>&gt;=20
<br>&gt; I thought that setting the uservalue would prevent the term set fr=
om
<br>&gt; being garbage collected when it falls out of scope, but I guess th=
at it
<br>&gt; still gets garbage collected when the dataset is destroyed.
<br>&gt;=20
<br>&gt; Which approach shall I use? I guess I&#39;d want to prevent the in=
dependent
<br>&gt; term set from ever being garbage collected. I can&#39;t use a ligh=
t userdata
<br>&gt; pointer because the access function must return a full usable Lua
<br>&gt; object. I also shouldn&#39;t duplicate the term set because it&#39=
;s expensive
<br>&gt; and the two copies could fall out of sync, which is undesirable.
<br>&gt;=20
<br>&gt; Thanks for any suggestions.
<br>&gt; s
<br>
<br>If your objects are GC-managed, conceptually, you should *never* free t=
hem=20
<br>manually. Instead, what I&#39;d do is have the term set in one uservalu=
e slot in=20
<br>the dataset userdata. This way, dataset will keep its term set alive un=
til it=20
<br>itself falls out of scope *by design*.
<br>
<br>Furthermore, do all deallocation stuff only in the `__gc` metamethod of=
 the=20
<br>uservalues, and *never* deallocate one GC&#39;d object&#39;s data from =
aonther&#39;s=20
<br>`__gc` metamethod.
<br>
<br>P.S.: Since lua 5.3-ish (afaik), userdata can have multiple uservalue s=
lots,=20
<br>use that to your advantage.
<br>
<br>
<br>
<br></blockquote></div>

<p></p>

-- <br />
You received this message because you are subscribed to the Google Groups &=
quot;lua-l&quot; group.<br />
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to <a href=3D"mailto:[email protected]">lua-l+unsubsc=
[email protected]</a>.<br />
To view this discussion visit <a href=3D"https://groups.google.com/d/msgid/=
lua-l/807f4393-a19a-4e96-9386-fc4d77312f58n%40googlegroups.com?utm_medium=
=3Demail&utm_source=3Dfooter">https://groups.google.com/d/msgid/lua-l/807f4=
393-a19a-4e96-9386-fc4d77312f58n%40googlegroups.com</a>.<br />

------=_Part_411461_1651373719.1784289039836--

------=_Part_411460_754449308.1784289039836--