0

I have a kind of callback function in my Lua script which I would like to call from different threads on the C++ side (0-100 times per second). So far it basically work, but as soon as I call it multiple times in a very short period of time it crashes the program causing errors like:
-As????ion failed: 0, file ...LuaFunction.h, line 146 or this one (completely random)

I think this happens, when it gets called from the C++ side before it finished another task. The most obvious thing for me to try (mutex lock all threads during the lua-function call) didn't help at all. :/
If I only call the Lua-function like once per 2 seconds, then I don't get any errors at all (Well, until the clean up part, if it gets to that point it will crash without a specific error).

Here is my code (I tried to crop and simplify my code as much as possible, and added a lot of commenting):

#include "stdafx.hpp"
#include <pthread.h> //for multithreading
#include <windows.h>
#include <iostream>
#include <map>
using namespace std;

unsigned int maxThreads = 100;
map<unsigned int, pthread_t> threads;
map<unsigned int, bool> threadsState;

pthread_mutex_t mutex; //to lock the pthreads (to keep printing from overlapping etc)

LuaPlus::LuaState* pState = LuaPlus::LuaState::Create( true ); //initialize LuaPlus
LuaPlus::LuaObject globals = pState->GetGlobals();

struct argumentStruct { //to pass multiple arguments to the function called when starting a pthread
    unsigned int threadId;
    int a;
    int b;
};

map<unsigned int, struct argumentStruct> argumentMap; //we store the arguments of active threads in here

void *ThreadFunction(void *arguments) { //will be called for every pthread we're going to create
    struct argumentStruct*args = (struct argumentStruct*)arguments; //get the arrgument struct
    int threadId = args->threadId; //get variables for each struct field
    int a = args->a;
    int b = args->b;
    Sleep(3000); //since this is a very simplified version of my actual project
    int c = a+b;
    pthread_mutex_lock(&mutex); //lock pthreads for the next lines
      LuaPlus::LuaFunction<int> CPP_OnMyEvent = pState->GetGlobal("LUA_OnMyEvent"); //get the Lua callback function to call on the C++ side
      CPP_OnMyEvent(a,b,c); //call to our lua-callback function
    pthread_mutex_unlock(&mutex); //unlock pthreads
    threadsState[threadId] = false; //mark the thread as finished/ready to get overwritten by a new one
    return NULL;
}
bool AddThread(int a, int b) {
    for (;;) {
        if (threads.size() < maxThreads) { //if our array of threads isn't full yet, create a new thread
            int id = threads.size();
            argumentMap[id].threadId = threads.size();
            argumentMap[id].a = a;
            argumentMap[id].b = b;
            threadsState[id] = true; //mark the thread as existing/running
            pthread_create(&threads[id], NULL, &ThreadFunction, (void *)&argumentMap[id]);
            return true;
        } else {
            unsigned int id;
            for (auto thread=threads.begin(); thread!=threads.end(); ++thread) {
                id = thread->first;
                if(!threadsState[id]) { //if thread with id "id" has finished, create a new thread on it's pthread_t
                    argumentMap[id].threadId = id;
                    argumentMap[id].a = a;
                    argumentMap[id].b = b;
                    threadsState[id] = true; //mark the thread as existing/running
                    pthread_join(threads[id], NULL);
                    pthread_create(&threads[id], NULL, &ThreadFunction, (void *)&argumentMap[id]);
                    return true;
                }
            }
        }
    }
    return false;
}


int main() {
    pthread_mutex_init(&mutex, NULL); //initialize the mutex
    //LuaPlus::LuaState* pState = LuaPlus::LuaState::Create( true ); //we already initialized this globally
    //LuaPlus::LuaObject globals = pState->GetGlobals();
    //pState->DoString("function LUA_OnMyEvent(arg1,arg2) print(arg1..arg2) end"); //it's already in main.lua

    globals.RegisterDirect("AddThread", AddThread);

    char pPath[ MAX_PATH ];
    GetCurrentDirectory(MAX_PATH,pPath);
    strcat_s(pPath,MAX_PATH,"\\main.lua");
    if( pState->DoFile(pPath) ) { //run our main.lua script which contains the callback function that will run a print
        if( pState->GetTop() == 1 )
            std::cout << "An error occured: " << pState->CheckString(1) << std::endl;
    }

    for (auto thread=threads.begin(); thread!=threads.end(); ++thread) { //wait for threads to finish
        unsigned int id = thread->first;
        if(threadsState[id])
            pthread_join(threads[id], NULL);
    }

    //clean up
    LuaPlus::LuaState::Destroy( pState );
    pState = nullptr;
    pthread_mutex_destroy(&mutex);
    getchar(); //keep console from closing
    return 0;
}

main.lua

function LUA_OnMyEvent(a,b,c)
    print(a.."+"..b.."="..c)
end

for i=1, 999, 1 do
    AddThread(i,i*2)
end
6
  • lua states are not thread safe. You cannot use the same state from multiple threads at the same time. Have you read lua-users.org/wiki/ThreadsTutorial ? Commented Sep 19, 2014 at 3:59
  • @EtanReisner I'm not sure if that really applies to LuaPlus. I also tried to create on LuaPlus state for every thread, but it didn't work at all. I think a new LuaPlus state basically creates a new scripting environment which doesn't have access to the others. And the thing with the mutex is already in my code and doesn't help much. Commented Sep 19, 2014 at 15:06
  • Can you remove the call to AddThread(X,y) from Lua and call it from C++? That would avoid a big race condition problem. Commented Sep 20, 2014 at 19:31
  • @SérgioCastelani Well, I could wrap the AddThread function into another one on the C++ side and make the wrapper function available to the Lua side, but I highly doubt that this would make any difference because in the end they all have to lead to the pthread_create() function. Commented Sep 20, 2014 at 19:45
  • Why hot use an events system in your main loop? Commented Sep 22, 2014 at 1:36

1 Answer 1

1
+250

I don't know Lua enough to give you a solution at Lua side, but this view of the problem may help you reaching that out.

When you call AddThread() from Lua, something like this will happen:

1. LuaState allocations
2. AddThread() execution
3. LuaState unwinding

While on ThreadFunction()...

A. Mutex lock
B. LuaState allocations
C. LUA_OnMyEvent() execution
D. LuaState unwinding 
E. Mutex Unlock

There is no mutex control at AddThread, so a race condition can happen between 1/3 and B/D. However, adding the mutex to AddThread would not solve the problem, because it would still run between 1 and 3.

If AddThread() is called only at the program initialization, then you could block all threads till initialization is done. If it is called frequently during program execution, then I would make those calls from a separate LuaState.

[EDIT] 2nd idea: Use a producer/consumer approach. Then C++ threads won't need to run Lua code.

C++ suggestion:

//-- start Task.h --

struct Task{
  static list<Task*> runningTasks;
  static list<Task*> doneTasks;
  static pthread_mutex_t mutex;
  list<Task*>::iterator iterator;

  virtual ~Task(){}

  bool start(){
    pthread_mutex_lock(&mutex);
    bool hasSpace = runningTasks.size() < 100;
    if(hasSpace){
      runningTasks.push_front(this);
      iterator = runningTasks.begin();
      pthread_t unusedID;
      pthread_create(&unusedID, NULL, Task::threadBody, this);
    }
    pthread_mutex_unlock(&mutex);
    return hasSpace;
  }

  virtual void run() = 0;
  virtual void processResults() = 0;

protected:
  void finish(){
    pthread_mutex_lock(&mutex);
    runningTasks.erase(iterator);
    doneTasks.push_front(this);
    pthread_mutex_unlock(&mutex);
  }

  static void* threadBody(void* instance){
    Task* task = static_cast<Task*>(instance);
    task->run();
    task->finish();
    return NULL;
  }
};
//-- end Task.h --

//-- start Task.cpp --

//Instantiate Task's static attributes
pthread_mutex_t Task::mutex;
list<Task*> Task::runningTasks;
list<Task*> Task::doneTasks;

//-- end Task.cpp --

struct SumTask: public Task{
  int a, b, c;
  void run(){
    Sleep(3000);
    c = a+b;
  }
  void processResults(){
    LuaPlus::LuaFunction<int> CPP_OnMyEvent = pState->GetGlobal("LUA_OnMyEvent");
    CPP_OnMyEvent(a,b,c);
  }
}

//functions called by Lua
bool runSumTask(int a, int b){
  SumTask task* = new SumTask();
  task->a = a; task->b = b;
  bool ok = task->start();
  if(!ok)
    delete task;
  return ok;
}

int gatherResults(){
  pthread_mutex_lock(&Task::mutex);
  int totalResults = Task::doneTasks.size();
  while(Task::doneTasks.size() > 0){
    Task* t = Task::doneTasks.front();
    Task::doneTasks.pop_front();
    t->processResults();
    delete t;
  }
  pthread_mutex_unlock(&Task::mutex);
  return totalResults;
}

int main() {
    //Must initialize/destroy Task::mutex
    pthread_mutex_init(&Task::mutex, NULL);
    //...
    pthread_mutex_destroy(&Task::mutex);
}

Lua code:

function LUA_OnMyEvent(a,b,c)
  print(a.."+"..b.."="..c)
end

local totalRunning = 0;
for i=1, 999, 1 do
  if (runSumTask(i,i*2))
    totalRunning = totalRunning + 1;

  totalRunning -= gatherResults();
end

while(totalRunning > 0) do
  totalRunning -= gatherResults();
  mySleepFunction(...);
end
Sign up to request clarification or add additional context in comments.

17 Comments

The whole point of my program is that it runs specific code asynchronously all the time, so I have to add threads dynamically. I don't see a way to implement it differently.
@Forivin Producer-consumer is actually what you need. Lua on it's own thread in never-ending loop of blocking calls to give_me_next_task(), and C++-side of this function waiting on shared queue.
Okay I don't really understand the code yet, but I tried to run it to see if this is what I was looking for.. A lot of errors came up. I was able to resolve the most, but there are still some that I don't really understand. I tried to compile this: pastebin.com/GVcJheYq and got these errors: pastebin.com/bhDKAvi5
@Forivin Sry! I had the time to only give you a code suggestion. Couldn't really compile it. Now I've tested the C++ code and updated the answer with a working version.
@SérgioCastelani You code still has some issues, but this time I was able to resolve them to compile it. But as soon as I run the exe, I get an "Access Violation" Error. Here is the code: pastebin.com/9s4EEKiF and here is a screenshot of the error: i.snag.gy/WbyAS.jpg
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.