How to stop yielded threads from being garbage collected

classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

How to stop yielded threads from being garbage collected

Martin Linklater
Hi - apologies for my ignorance, but I'm quite new to Lua.

I am writing a system where multiple Lua threads are created and updated in series on one CPU thread. The lua code can call a Sleep(n) function which calls a C function which yields the thread (lua_yield()) and puts it on an inactive list. This inactive list is checked 30 times a second and any threads that have slept for long enough are then continued by calling lua_resume() on them.

This is working fine if I only have a few threads executing, but if I put the code under stress and create a lot of lua threads, I get the following error when I call lua_resume on a yielded thread:

cannot resume non-suspended coroutine

After doing a bit of digging around on the internet I think this is being caused by the thread being garbage collected while it is yielded. Can someone please verify if my observed behaviour fits in with this diagnosis, and if so, what is the easiest way to stop a yielded thread from being garbage collected ?

Thanks for any help you can give.

- Martin

Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Rena
On Wed, Nov 23, 2011 at 15:48, Martin Linklater <[hidden email]> wrote:

> Hi - apologies for my ignorance, but I'm quite new to Lua.
>
> I am writing a system where multiple Lua threads are created and updated in
> series on one CPU thread. The lua code can call a Sleep(n) function which
> calls a C function which yields the thread (lua_yield()) and puts it on an
> inactive list. This inactive list is checked 30 times a second and any
> threads that have slept for long enough are then continued by calling
> lua_resume() on them.
>
> This is working fine if I only have a few threads executing, but if I put
> the code under stress and create a lot of lua threads, I get the following
> error when I call lua_resume on a yielded thread:
>
> cannot resume non-suspended coroutine
>
> After doing a bit of digging around on the internet I think this is being
> caused by the thread being garbage collected while it is yielded. Can
> someone please verify if my observed behaviour fits in with this diagnosis,
> and if so, what is the easiest way to stop a yielded thread from being
> garbage collected ?
>
> Thanks for any help you can give.
>
> - Martin
>

It shouldn't be collected as long as there's a reference to it.

--
Sent from my toaster.

Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Martin Linklater
Thanks. So after creating the new thread, stack position -1 contains a thread object. When you say to keep a reference to it do I need to create a Lua table entry which contains that thread object ? Or is there a simpler way ?

Cheers

On 23 Nov 2011, at 23:10, HyperHacker wrote:

> On Wed, Nov 23, 2011 at 15:48, Martin Linklater <[hidden email]> wrote:
>> Hi - apologies for my ignorance, but I'm quite new to Lua.
>>
>> I am writing a system where multiple Lua threads are created and updated in
>> series on one CPU thread. The lua code can call a Sleep(n) function which
>> calls a C function which yields the thread (lua_yield()) and puts it on an
>> inactive list. This inactive list is checked 30 times a second and any
>> threads that have slept for long enough are then continued by calling
>> lua_resume() on them.
>>
>> This is working fine if I only have a few threads executing, but if I put
>> the code under stress and create a lot of lua threads, I get the following
>> error when I call lua_resume on a yielded thread:
>>
>> cannot resume non-suspended coroutine
>>
>> After doing a bit of digging around on the internet I think this is being
>> caused by the thread being garbage collected while it is yielded. Can
>> someone please verify if my observed behaviour fits in with this diagnosis,
>> and if so, what is the easiest way to stop a yielded thread from being
>> garbage collected ?
>>
>> Thanks for any help you can give.
>>
>> - Martin
>>
>
> It shouldn't be collected as long as there's a reference to it.
>
> --
> Sent from my toaster.
>


Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Rena
On Wed, Nov 23, 2011 at 16:16, Martin Linklater <[hidden email]> wrote:

> Thanks. So after creating the new thread, stack position -1 contains a thread object. When you say to keep a reference to it do I need to create a Lua table entry which contains that thread object ? Or is there a simpler way ?
>
> Cheers
>
> On 23 Nov 2011, at 23:10, HyperHacker wrote:
>
>> On Wed, Nov 23, 2011 at 15:48, Martin Linklater <[hidden email]> wrote:
>>> Hi - apologies for my ignorance, but I'm quite new to Lua.
>>>
>>> I am writing a system where multiple Lua threads are created and updated in
>>> series on one CPU thread. The lua code can call a Sleep(n) function which
>>> calls a C function which yields the thread (lua_yield()) and puts it on an
>>> inactive list. This inactive list is checked 30 times a second and any
>>> threads that have slept for long enough are then continued by calling
>>> lua_resume() on them.
>>>
>>> This is working fine if I only have a few threads executing, but if I put
>>> the code under stress and create a lot of lua threads, I get the following
>>> error when I call lua_resume on a yielded thread:
>>>
>>> cannot resume non-suspended coroutine
>>>
>>> After doing a bit of digging around on the internet I think this is being
>>> caused by the thread being garbage collected while it is yielded. Can
>>> someone please verify if my observed behaviour fits in with this diagnosis,
>>> and if so, what is the easiest way to stop a yielded thread from being
>>> garbage collected ?
>>>
>>> Thanks for any help you can give.
>>>
>>> - Martin
>>>
>>
>> It shouldn't be collected as long as there's a reference to it.
>>
>> --
>> Sent from my toaster.
>>
>
>

I believe so. At least once you return from that function, the
reference on the stack is gone. I'm not sure if it can be collected
while it's still on the stack, but it's easy to accidentally remove it
from there too. Your best bet is to keep them all in a table. The
registry functions might be more convenient than keeping a table on
the stack for this purpose.

--
Sent from my toaster.

Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Peter Pimley
If you create the thread and you *don't* keep a reference to it then
the thread will be eligible for garbage collection.  Could it be that
the only reason that any of your threads do run is because although
they are eligible for garbage collection, they haven't been collected
*yet*.

This would be easy to check by running a full garbage collection cycle
every frame (I'm guessing it's a game if it's 30Hz, right?).  Do this
by calling lua_gc with LUA_GCCOLLECT.  See
http://www.lua.org/manual/5.1/manual.html#lua_gc  This will slow
things down a bit, so consider removing it again when you have
finished debugging.

Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Martin Linklater
Thanks for that Peter - it now breaks instantly 8).

On 24 Nov 2011, at 09:57, Peter Pimley wrote:

> If you create the thread and you *don't* keep a reference to it then
> the thread will be eligible for garbage collection.  Could it be that
> the only reason that any of your threads do run is because although
> they are eligible for garbage collection, they haven't been collected
> *yet*.
>
> This would be easy to check by running a full garbage collection cycle
> every frame (I'm guessing it's a game if it's 30Hz, right?).  Do this
> by calling lua_gc with LUA_GCCOLLECT.  See
> http://www.lua.org/manual/5.1/manual.html#lua_gc  This will slow
> things down a bit, so consider removing it again when you have
> finished debugging.
>


Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Duncan Cross
In reply to this post by Martin Linklater
On Wed, Nov 23, 2011 at 11:16 PM, Martin Linklater
<[hidden email]> wrote:
> Thanks. So after creating the new thread, stack position -1 contains a thread object. When you say to keep a reference to it do I need to create a Lua table entry which contains that thread object ? Or is there a simpler way ?
>
> Cheers

What I would do is use the registry table, and create an entry where
the key is a light-userdata pointer to the thread. Something like
this:

 // thread object is on the top of L's stack
 lua_pushlightuserdata(L, lua_topointer(L, -1));
 lua_pushvalue(L, -2);
 lua_settable(L, LUA_REGISTRYINDEX);

Later, if you want to remove this entry from the registry table and
make the thread garbage-collectable again, push another light userdata
and set its registry table entry to nil.

-Duncan

Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Peter Pimley
In reply to this post by Martin Linklater
On 24 November 2011 10:05, Martin Linklater <[hidden email]> wrote:
> Thanks for that Peter - it now breaks instantly 8).

:)

So now, as HyperHacker says, you need to store a reference to the
thread somewhere in Lua.  It sounds like you ought to have a table
containing all your active threads.  That table itself will need to be
stored somewhere, either as a member of another table, or a global, or
in the registry.  I don't know what you're creating, but it sounds
like the registry would be the best place to put it.  See
http://www.lua.org/pil/27.3.2.html .   In C, do something like:

// with the active threads table on the top of the stack...
int active_thread_table = luaL_ref (lua, LUA_REGISTRYINDEX);

// and then later, when you want to iterate over that table...
lua_gettable (lua, active_thread_table);
// active thread table is now on top of the stack.

(Sorry if any of that is obvious.)

Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Rena
In reply to this post by Duncan Cross
On Thu, Nov 24, 2011 at 03:57, Duncan Cross <[hidden email]> wrote:

> On Wed, Nov 23, 2011 at 11:16 PM, Martin Linklater
> <[hidden email]> wrote:
>> Thanks. So after creating the new thread, stack position -1 contains a thread object. When you say to keep a reference to it do I need to create a Lua table entry which contains that thread object ? Or is there a simpler way ?
>>
>> Cheers
>
> What I would do is use the registry table, and create an entry where
> the key is a light-userdata pointer to the thread. Something like
> this:
>
>  // thread object is on the top of L's stack
>  lua_pushlightuserdata(L, lua_topointer(L, -1));
>  lua_pushvalue(L, -2);
>  lua_settable(L, LUA_REGISTRYINDEX);
>
> Later, if you want to remove this entry from the registry table and
> make the thread garbage-collectable again, push another light userdata
> and set its registry table entry to nil.
>
> -Duncan
>

Er, why pushing pointers to the objects instead of the thread itself?

--
Sent from my toaster.

Reply | Threaded
Open this post in threaded view
|

Re: How to stop yielded threads from being garbage collected

Duncan Cross
On Fri, Nov 25, 2011 at 2:45 AM, HyperHacker <[hidden email]> wrote:

> On Thu, Nov 24, 2011 at 03:57, Duncan Cross <[hidden email]> wrote:
>> On Wed, Nov 23, 2011 at 11:16 PM, Martin Linklater
>> <[hidden email]> wrote:
>>> Thanks. So after creating the new thread, stack position -1 contains a thread object. When you say to keep a reference to it do I need to create a Lua table entry which contains that thread object ? Or is there a simpler way ?
>>>
>>> Cheers
>>
>> What I would do is use the registry table, and create an entry where
>> the key is a light-userdata pointer to the thread. Something like
>> this:
>>
>>  // thread object is on the top of L's stack
>>  lua_pushlightuserdata(L, lua_topointer(L, -1));
>>  lua_pushvalue(L, -2);
>>  lua_settable(L, LUA_REGISTRYINDEX);
>>
>> Later, if you want to remove this entry from the registry table and
>> make the thread garbage-collectable again, push another light userdata
>> and set its registry table entry to nil.
>>
>> -Duncan
>>
>
> Er, why pushing pointers to the objects instead of the thread itself?
>
> --
> Sent from my toaster.
>
>

Well, you can't push an arbitrary thread object directly onto a sister
lua_State's stack. There's lua_pushthread(), but that only takes one
parameter - it pushes a thread's object onto its *own* stack. It's
possible that's okay, but I couldn't remember whether it is
inadvisable to do things on a thread state's stack if it might be a
dead coroutine or something, so using the pointer seemed safer. There
might be no problem.

-Duncan