Создание модуля на C для Tarantool

Введение

Как я уже писал ранее, в данный момент я работаю с отечественной СУБД Tarantool, которая, по совместительству, является еще и сервером приложений. Некоторое время назад я уже писал как можно создавать приложения для Tarantool на языке Lua, а также про построение регистронезависимого индекса в нем.

Несмотря на то, что Lua является основным языком создания модулей для Tarantool их можно создавать еще и на C (с недавнего времени еще и на Rust с помощью Tarantool Rust SDK). К сожалению, в Интернете очень мало инфромации по поводу создания C модулей для Tarantool и этой статьей я собираюсь чуть-чуть заполнить этот пробел.

Описание задачи

В качестве задачи для создания модуля, я взял частичную реализацию протокола EGTS, о котором я писал ранее. Она будет сильно упращенной и будет происходить разбор только секции телеметрических данных.

Каждый клиент будет работать в своем файбере(fiber). Fiber - это набор инструкций, который исполняется с помощью кооперативной многозадачности в Tarantool.

Создание модуля

В создании модуля нам поможет modulekit. Для начала разработки его нужно загрузить

git clone https://github.com/kuznetsovin/tarantool-modulekit.git
git fetch origin
git checkout -b ckit origin/ckit

После этого нужно заменить все вхождения ckit (в именах файлов и в их содержимом) на название нашего модуля, в моем случае это egts.

В итоге получилось что-то такое:

├── egts
│   ├── CMakeLists.txt
│   ├── init.lua
│   ├── lib.c
├── test
│    └── egts_test.lua
├── egts-scm-1.rockspec
├── CMakeLists.txt
├── LICENSE
├── Makefile
└── README.md

Основной код библиотеки будет хранитс в файле lib.c. Весь код я не буду описывать, а опишу только важные функции.

Основная функция для загрузки нашей библиотеки это luaopen_egts_lib:

LUA_API int
luaopen_egts_lib(lua_State *L)
{
    ...

    static const struct luaL_Reg lib [] = {
        {"start_server", start_server},
        {"stop_server", stop_server},
        {NULL, NULL}
    };

    luaL_newlib(L, lib);

    return 1;
}

Здесь происходит описание того, какие функции из нашего модуля будут доступны к вызову из Lua. В нашем случае это start_server и stop_server. Функции добавляются в специальную структуру luaL_Reg, которая потом регистрируется в Lua с помощью функции luaL_newlib. Структура lua_State хранит всю информацию о состоянии исполняющегося Lua потока.

Взаимодействие с lua происходит как работа со стековой машиной, то есть за счет операций добавления на стек и извлечения со стека. Поэтому функции которые будут вызываться из Lua, должны иметь в качестве входного аргумента указатель на lua_State.

Для пример разберем функцию start_server:

static int
start_server(lua_State *L)
{

    if (lua_gettop(L) < 1)
		return luaL_error(L, "usage: start_server(port: number)");

	int port = lua_tointeger(L, 1);
    
    /* 
    socket open code
    ... 
    */ 
    
    f_egts_srv = fiber_new("egts_server", fiber_conn_listner);
	fiber_start(f_egts_srv, sock_srv);

	say_info("start egts server on port %d", port);

	return 0;
}

В начале данной функци провкряется есть ли на стеке значение. Для этого используется функция lua_gettopкоторая возвращает индекс верхнего аргумента стека, и если результат меньше одного значит значений нет.

Затем мы считываем значение со стека с помощью функции lua_tointeger, как можно понять из названия переменной, оно используется для открытия сетевого порта. После этого идет код окртыия сетевого сокета, который я опустил для краткости.

В конце функции создается файбер, с помощью функции fiber_new, который будет слушать наш открытый сокет. Файбер в данном случае используются, как аналог “зеленых потоков” и их управлением занимается не операционная система а tarantool runtime .

Остальное C код я разбирать не буду, так как к теме статьи он не имеет отношения.

Далее файле init.lua можно добавить обертки для более удобного вызова функций сторонним приложением, или какие-то вспомагательные функции:

local c_egts = require('egts.lib')

...

return {
    SYSTEM_SPACE = _SYSTEM_SPACE,
    init_store = init_store,
    start_server = c_egts.start_server;
    stop_server = c_egts.stop_server;
}

Обертки start/stop server нужны для чтобы во внешнем приложении не использовать require('egts.lib') для их вызова, а просто наприсать require('egts'), что более эстетично и не несет лишней смыславной нагрузки для внешнего пользователя.

Сборка модуля

Для сборки модуля нужно выполнить команду:

tarantoolctl rocks make

которая соберет модуль в папку .rosks локальной директории

Работа с модулем из Tarantool

Для работы с модулем достачно его импортировать в приложении:

local egts = require('egts')

egts.init_store()

egts.start_server(5555)

Заключение

Как видно из статьи при создании C модуля для Tarantool есть свои особенности, но в целом большой сложности в этом нет. Исходный код модуля, описанного в статье, можно найти на Github.

Также нужно отметить, что на основе этого модуля я проводил защиту своего курсового проекта на OTUS:

Полезные ссылки

 
comments powered by Disqus