Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is calling a lua function(as a callback) from another thread safe enough?

Tags:

visual-c++

lua

Actually I am using visual C++ to try to bind lua functions as callbacks for socket events(in another thread). I initialize the lua stuff in one thread and the socket is in another thread, so every time the socket sends/receives a message, it will call the lua function and the lua function determines what it should do according to the 'tag' within the message.

So my questions are:

  1. Since I pass the same Lua state to lua functions, is that safe? Doesn't it need some kinda protection? The lua functions are called from another thead so I guess they might be called simultaneously.

  2. If it is not safe, what's the solution for this case?

like image 652
Mickey Shine Avatar asked May 16 '13 02:05

Mickey Shine


3 Answers

It is not safe to call back asynchronously into a Lua state.

There are many approaches to dealing with this. The most popular involve some kind of polling.

A recent generic synchronization library is DarkSideSync

A popular Lua binding to libev is lua-ev

This SO answer recommends Lua Lanes with LuaSocket.

like image 183
Doug Currie Avatar answered Oct 22 '22 04:10

Doug Currie


  1. It is not safe to call function within one Lua state simultaneously in multiple threads.

  2. I was dealing with the same problem, since in my application all basics such as communication are handled by C++ and all the business logic is implemented in Lua. What I do is create a pool of Lua states that are all created and initialised on an incremental basis (once there's not enough states, create one and initialise with common functions / objects). It works like this:

    • Once a connection thread needs to call a Lua function, it checks out an instance of Lua state, initialises specific globals (I call it a thread / connection context) in a separate (proxy) global table that prevents polluting the original global, but is indexed by the original global
    • Call a Lua function
    • Check the Lua state back in to the pool, where it is restored to the "ready" state (dispose of the proxy global table)

I think this approach would be well suited for your case as well. The pool checks each state (on an interval basis) when it was last checked out. When the time difference is big enough, it destroys the state to preserve resources and adjust the number of active states to current server load. The state that is checked out is the most recently used among the available states.

There are some things you need to consider when implementing such a pool:

  • Each state needs to be populated with the same variables and global functions, which increases memory consumption.
  • Implementing an upper limit for state count in the pool
  • Ensuring all the globals in each state are in a consistent state, if they happen to change (here I would recommend prepopulating only static globals, while populating dynamic ones when checking out a state)
  • Dynamic loading of functions. In my case there are many thousands of functions / procedures that can be called in Lua. Having them constantly loaded in all states would be a huge waste. So instead I keep them byte code compiled on the C++ side and have them loaded when needed. It turns out not to impact performance that much in my case, but your mileage may vary. One thing to keep in mind is to load them only once. Say you invoke a script that needs to call another dynamically loaded function in a loop. Then you should load the function as a local once before the loop. Doing it otherwise would be a huge performance hit.

Of course this is just one idea, but one that turned out to be best suited for me.

like image 1
W.B. Avatar answered Oct 22 '22 05:10

W.B.


  1. It's not safe, as the others mentioned
  2. Depends on your usecase

Simplest solution is using a global lock using the lua_lock and lua_unlock macros. That would use a single Lua state, locked by a single mutex. For a low number of callbacks it might suffice, but for higher traffic it probably won't due to the overhead incurred.

Once you need better performance, the Lua state pool as mentioned by W.B. is a nice way to handle this. Trickiest part here I find synchronizing the global data across the multiple states.

DarkSideSync, mentioned by Doug, is useful in cases where the main application loop resides on the Lua side. I specifically wrote it for that purpose. In your case this doesn't seem a fit. Having said that; depending on your needs, you might consider changing your application so the main loop does reside on the Lua side. If you only handle sockets, then you can use LuaSocket and no synchronization is required at all. But obviously that depends on what else the application does.

like image 1
Tieske Avatar answered Oct 22 '22 05:10

Tieske