C++ embedder API

If you want to embed Emilua in your own Boost.Asio-based programs, this is the list of steps you need to do:

  1. Compile and link against Emilua (use Meson or pkg-config to have the appropriate compiler flags filled automatically).

  2. #include <emilua/state.hpp>

  3. Instantiate emilua::app_context. This object needs to stay alive for as long as at least one Lua VM is alive. If you want to be sure, just make sure it outlives boost::asio::io_context and you’re good to go.

  4. Create an emilua::properties_service object with the same concurrency hint passed to boost::asio::io_context and add it to the boost::asio::io_context object using boost::asio::make_service. This step will be needed for as long as Boost.Asio refuses to add a getter for the concurrency hint: https://github.com/chriskohlhoff/asio/pull/1254.

  5. Call emilua::make_vm() (see src/main.ypp for an example).

  6. Call emilua::vm_context::fiber_resume() inside the strand returned by emilua::vm_context::strand() to start the Lua VM created in the previous step (see src/main.ypp for an example).

  7. Optionally synchronize against other threads before you exit the application. If you’re going to spawn actors in foreign boost::asio::io_context objects in your Lua programs then it’s a good idea to include this step. See below.

Emilua is not designed to work properly with boost::asio::io_context::stop(). Many cleanup steps will be missed if you call this function. If you need to use it, then spawn Emilua programs in their own boost::asio::io_context instances.


This type stores process-wide info that is shared among all Lua VMs (e.g. process arguments, environment, module paths, module caches, default logger, which VM is the master VM, …​).

If you want to embed the Lua programs in your binary as well you can pre-populate the module cache here with the contents of all Lua files you intend to ship in the binary. modules_cache_registry is the member you’re looking for. Do this before you start the first Lua VM. However there’s a better way (see next section).

Builtin modules

Just use linker args to override some or all of the following 3 functions:

  • emilua_get_builtin_module

  • emilua_get_builtin_rdf_ec

  • emilua_get_builtin_native_module

Using this method instead of pre-filling the module cache allows actors spawned in subprocesses to import these modules as well. This method may also lead to better start-up times and a smaller memory footprint (you could even use gperf to have the best module search performance).

Master VM

If you want to allow your Lua programs to change process state that is shared among all program threads (e.g. current working directory, signal handlers, …​) then you need to elect one Lua VM to be the master VM.

The 1-one snippet that follows is enough to finish this setup. This step must be done before you call fiber_resume().

appctx.master_vm = vm_ctx;

Cleanup at exit

First make sure emilua::app_context outlives boost::asio::io_context.

After boost::asio::io_context::run() returns you can use the following snippet to synchronize against extra threads and boost::asio::io_context objects your Lua scripts created[1].

    std::unique_lock<std::mutex> lk{appctx.extra_threads_count_mtx};
    while (appctx.extra_threads_count > 0)

Actors spawned in different processes

Emilua has the ability to spawn Lua VMs in their own processes for isolation or sandboxing purposes. To enable this feature, a supervisor process must be created while the program is still single-threaded.

For communication with the supervisor process, Emilua uses an UNIX socket. The file descriptor for this process is stored in app_context::ipc_actor_service_sockfd. See src/main.ypp for an example on how to initialize this variable.

You also need to initialize emilua::app_context::environp. This is a pointer to environ. This step must be done before you fork to create the supervisor process. Emilua uses an indirection instead of using environ directly because FreeBSD is not POSIX-compliant and the usual declaration for environ doesn’t work from shared libraries. Emilua never modifies the environment for the main process, so you don’t need to worry about what Emilua uses this variable for. Its usage is internal to Emilua and won’t affect your C++ program.

emilua::app_context::environp = &environ;
#endif // BOOST_OS_UNIX

On Linux, you also need to initialize emilua::clone_stack_address.

If you don’t intend to have Lua VMs tied to their own processes triggered by Lua programs then you can skip this step.

RT signals

Emilua reserves a RT signal for internal uses (cancelling IO operations which have poor system APIs). This signal can be configured at build time:

meson configure -Deintr_rtsigno=RTSIGNO

If you choose the value 0, this support is disabled altogether and Emilua won’t reserve any RT signal by itself. If this support is enabled, you must add some code similar to the following one in main():

struct sigaction sa;
std::memset(&sa, 0, sizeof(struct sigaction));

sigaddset(&sa.sa_mask, EMILUA_CONFIG_EINTR_RTSIGNO);
sigprocmask(SIG_BLOCK, &sa.sa_mask, /*oldset=*/NULL);

sa.sa_sigaction = emilua::longjmp_on_rtsigno;
sa.sa_flags = SA_RESTART | SA_SIGINFO;
sigaction(EMILUA_CONFIG_EINTR_RTSIGNO, /*act=*/&sa, /*oldact=*/NULL);

1. Emilua only instantiates new threads and boost::asio::io_context objects if your Lua programs explicitly ask for that when it calls spawn_vm(). You can also disable this feature altogether at build time.