1

I got problem when making a class to utilize multitasking system in Lua. This class should enable run multiple function at same time by use of Lua's coroutine system.

I'd like to write the ideal usage of the class in both host code and Lua code.

IN HOST CODE

int main(void)
{
    //Make state.
    auto L{luaL_newstate()};
    luaL_openlibs(L);

    //Enable multitasking.
    TaskSystem tasks{L};

    if (luaL_dofile(L, "script.lua") != 0)
    {
        auto msg{lua_tostring(L, -1)};
        std::cerr << msg << std::endl;
    }
    //Consider this as main loop of games.
    while (true)
    {
        tasks.resume_all();
        std::cin.get();
    }

    lua_close(L);
}

IN LUA CODE

--30 tasks run parallel.
--'run_task' creates coroutine from the function given,
-- and registers it to  the list of coroutines in the host code.
for i = 0, 30 do
    run_task
    (
    function()
        while true do
            print(i) 
            coroutine.yield()
        end
    end
    )
end

I expect the example I show to run functions given to 'run_task' in Lua code as if they are in multitasking system.

Then I show the implementation of 'class TaskSystem'. Sorry, this code is a bit long.

#include <list>
#include <boost/range/algorithm_ext/erase.hpp>
class TaskSystem
    {
    private:
        lua_State* main_thread;
        struct Task
        {
            lua_State* thread;
            ThreadStatus status;
            bool to_continue(void) const {return status == LUA_YIELD; }
        };
        using TaskList = std::list<Task>;
        TaskList task_list;
        static int run_task(lua_State* _parent)
        {
            //coroutine.create(func)
            lua_getglobal(_parent, "coroutine");
            lua_getfield(_parent, -1, "create");
            lua_pushvalue(_parent, 1); //argument
            lua_pcall(_parent, 1, 1, 0);

            //Converte coroutine into thread object.
            auto co{lua_tothread(_parent, -1)};

            lua_settop(_parent, 0);

            //Register the coroutine.
            auto& task_list{*static_cast<TaskList*>(lua_touserdata(_parent, lua_upvalueindex(1)))};
            task_list.push_back({co, lua_resume(co, nullptr, 0)});

            return 0;
        }
    public:
        TaskSystem(void) :main_thread(nullptr), task_list(){}
        //Enable multitasking.
        TaskSystem(lua_State* _main_thread)
        {
            initialize(_main_thread);
        }
        //Enable multitasiking in the thread.
        void initialize(lua_State* _main_thread)
        {
            if (!_main_thread)
            {
                throw std::runtime_error("TaskSystem must be initialized by valid thread.");
            }

            main_thread = _main_thread;
            lua_pushlightuserdata(main_thread, &task_list); //Task list as upvalue.
            lua_pushcclosure(main_thread, &run_task, 1);
            lua_setglobal(main_thread, "run_task");
        }
        //Resume all tasks.
        void resume_all(void)
        {
            //Remove all threads not resumable.
            //'boost::remove_erase_if' is the function splicing the container(1st arg) according to the lambda(2nd arg).
            boost::remove_erase_if(task_list, [](const Task& _t) {return !_t.to_continue(); });

            //Resume all thread on the list.
            for (auto& t : task_list)
            {
                if (t.to_continue())
                {
                    t.status = lua_resume(t.thread, nullptr, 0);
                }
            }
        }
        //Return the number of the tasks.
        std::size_t count(void) const { return task_list.size(); }
    };

The problem confusing me is the unknown exception thrown by 'TaskSystem::resume_all' when you try to run this code.

The exception has tendency to be thrown when the number of tasks is 24 or larger.

I once thought stackoverflow happened in Lua environment, but the exception thrown even if I extend the stack size by 'lua_checkstack'.

If someone notice the resolution, please help me.

My environment

Lua version: 5.2.4

IDE/compiler: Microsoft visual studio community 2015

Host code language: C++14

REVISED CODE

class TaskSystem
{
private:
    using ThreadRef = decltype(luaL_ref(nullptr, 0));
    lua_State* main_thread;
    struct Task
    {
        ThreadRef thread;
        ThreadStatus status;
        bool to_continue(void) const {return status == LUA_YIELD; }
    };
    using TaskList = std::list<Task>;
    TaskList task_list;
    static int run_task(lua_State* _parent)
    {
        //coroutine.create(func)
        lua_getglobal(_parent, "coroutine");
        lua_getfield(_parent, -1, "create");
        lua_pushvalue(_parent, 1); //argument
        lua_pcall(_parent, 1, 1, 0);

        //co is reference to the thread object.
        auto co{luaL_ref(_parent, LUA_REGISTRYINDEX)};

        //Register the coroutine.
        auto& task_list{*static_cast<TaskList*>(lua_touserdata(_parent, lua_upvalueindex(1)))};
        task_list.push_back({co, LUA_YIELD});

        return 0;
    }
public:
    TaskSystem(void) :main_thread(nullptr), task_list(){}
    //Enable multitasking.
    TaskSystem(lua_State* _main_thread)
    {
        initialize(_main_thread);
    }
    //Enable multitasiking in the thread.
    void initialize(lua_State* _main_thread)
    {
        if (!_main_thread)
        {
            throw std::runtime_error("TaskSystem must be initialized by valid thread.");
        }

        main_thread = _main_thread;
        lua_pushlightuserdata(main_thread, &task_list); //Task list as upvalue.
        lua_pushcclosure(main_thread, &run_task, 1);
        lua_setglobal(main_thread, "run_task");
    }
    //Resume all tasks.
    void resume_all(void)
    {
        //Remove all threads not resumable.
        boost::remove_erase_if(task_list, [](const Task& _t) {return !_t.to_continue(); });

        //Resume all thread on the list.
        for (auto& t : task_list)
        {
            //Get thread object,
            lua_rawgeti(main_thread, LUA_REGISTRYINDEX, t.thread);
            auto thread{ lua_tothread(main_thread, -1) };
            //and call it.
            t.status = lua_resume(thread, nullptr, 0);
            //Disable referenct to the thread if its life-time ended.
            if(!t.to_continue()){luaL_unref(main_thread, LUA_REGISTRYINDEX, t.thread);}
        }
    }
    //Return the number of the tasks.
    std::size_t count(void) const { return task_list.size(); }
}
darkspider
  • 136
  • 5
  • 2
    Lua coroutines are subject to garbage-collection. The `lua_settop(_parent, 0);` removes the only reference to the coroutine you've just created, so it will be collected at some point, and the pointer you save becomes dangling. I suggest using `luaL_ref()` on the coroutine. – siffiejoe Oct 23 '16 at 07:13
  • Thank you to answer my question! After got your answer, I revised the code as above. But sadly I received the exception again. In my understandings, by being registered to LUA_REGISTRYINDEX, the newly created thread object avoid from being GCed. – darkspider Oct 23 '16 at 08:50
  • I noticed remains of result of 'lua_rawgeti' caused stackoverflow. The trouble seems to be resolved for now. Thank you very much. – darkspider Oct 23 '16 at 10:13

0 Answers0