123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662 |
- /*
- * Copyright 2024 Vsevolod Stakhov
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #include "lua_common.h"
- #include "lua_thread_pool.h"
- #include "utlist.h"
-
- #include "contrib/hiredis/hiredis.h"
- #include "contrib/hiredis/async.h"
-
- #define REDIS_DEFAULT_TIMEOUT 1.0
-
- static const gchar *M = "rspamd lua redis";
- static void *redis_null;
-
- /***
- * @module rspamd_redis
- * This module implements redis asynchronous client for rspamd LUA API.
- * Here is an example of using of this module:
- * @example
- local rspamd_redis = require "rspamd_redis"
- local rspamd_logger = require "rspamd_logger"
-
- local function symbol_callback(task)
- local redis_key = 'some_key'
- local function redis_cb(err, data)
- if not err then
- rspamd_logger.infox('redis returned %1=%2', redis_key, data)
- end
- end
-
- rspamd_redis.make_request(task, "127.0.0.1:6379", redis_cb,
- 'GET', {redis_key})
- -- or in table form:
- -- rspamd_redis.make_request({task=task, host="127.0.0.1:6379,
- -- callback=redis_cb, timeout=2.0, cmd='GET', args={redis_key}})
- end
- */
-
- LUA_FUNCTION_DEF(redis, make_request);
- LUA_FUNCTION_DEF(redis, make_request_sync);
- LUA_FUNCTION_DEF(redis, connect);
- LUA_FUNCTION_DEF(redis, connect_sync);
- LUA_FUNCTION_DEF(redis, add_cmd);
- LUA_FUNCTION_DEF(redis, exec);
- LUA_FUNCTION_DEF(redis, gc);
-
- static const struct luaL_reg redislib_f[] = {
- LUA_INTERFACE_DEF(redis, make_request),
- LUA_INTERFACE_DEF(redis, make_request_sync),
- LUA_INTERFACE_DEF(redis, connect),
- LUA_INTERFACE_DEF(redis, connect_sync),
- {NULL, NULL}};
-
- static const struct luaL_reg redislib_m[] = {
- LUA_INTERFACE_DEF(redis, add_cmd),
- LUA_INTERFACE_DEF(redis, exec),
- {"__gc", lua_redis_gc},
- {"__tostring", rspamd_lua_class_tostring},
- {NULL, NULL}};
-
- #undef REDIS_DEBUG_REFS
- #ifdef REDIS_DEBUG_REFS
- #define REDIS_RETAIN(x) \
- do { \
- msg_err("retain ref %p, refcount: %d", (x), (x)->ref.refcount); \
- REF_RETAIN(x); \
- } while (0)
-
- #define REDIS_RELEASE(x) \
- do { \
- msg_err("release ref %p, refcount: %d", (x), (x)->ref.refcount); \
- REF_RELEASE(x); \
- } while (0)
- #else
- #define REDIS_RETAIN REF_RETAIN
- #define REDIS_RELEASE REF_RELEASE
- #endif
-
- struct lua_redis_request_specific_userdata;
- /**
- * Struct for userdata representation
- */
- struct lua_redis_userdata {
- redisAsyncContext *ctx;
- struct rspamd_task *task;
- struct rspamd_symcache_dynamic_item *item;
- struct rspamd_async_session *s;
- struct ev_loop *event_loop;
- struct rspamd_config *cfg;
- struct rspamd_redis_pool *pool;
- gchar *server;
- gchar log_tag[RSPAMD_LOG_ID_LEN + 1];
- struct lua_redis_request_specific_userdata *specific;
- gdouble timeout;
- guint16 port;
- guint16 terminated;
- };
-
- #define msg_debug_lua_redis(...) rspamd_conditional_debug_fast(NULL, NULL, \
- rspamd_lua_redis_log_id, "lua_redis", ud->log_tag, \
- G_STRFUNC, \
- __VA_ARGS__)
- INIT_LOG_MODULE(lua_redis)
-
- #define LUA_REDIS_SPECIFIC_REPLIED (1 << 0)
- /* session was finished */
- #define LUA_REDIS_SPECIFIC_FINISHED (1 << 1)
- #define LUA_REDIS_ASYNC (1 << 0)
- #define LUA_REDIS_TEXTDATA (1 << 1)
- #define LUA_REDIS_TERMINATED (1 << 2)
- #define LUA_REDIS_NO_POOL (1 << 3)
- #define LUA_REDIS_SUBSCRIBED (1 << 4)
- #define IS_ASYNC(ctx) ((ctx)->flags & LUA_REDIS_ASYNC)
-
- struct lua_redis_request_specific_userdata {
- gint cbref;
- guint nargs;
- gchar **args;
- gsize *arglens;
- struct lua_redis_userdata *c;
- struct lua_redis_ctx *ctx;
- struct lua_redis_request_specific_userdata *next;
- ev_timer timeout_ev;
- guint flags;
- };
-
- struct lua_redis_ctx {
- guint flags;
- struct lua_redis_userdata async;
- guint cmds_pending;
- ref_entry_t ref;
- GQueue *replies; /* for sync connection only */
- GQueue *events_cleanup; /* for sync connection only */
- struct thread_entry *thread; /* for sync mode, set only if there was yield */
- };
-
- struct lua_redis_result {
- gboolean is_error;
- gint result_ref;
- struct rspamd_symcache_dynamic_item *item;
- struct rspamd_async_session *s;
- struct rspamd_task *task;
- struct lua_redis_request_specific_userdata *sp_ud;
- };
-
- static struct lua_redis_ctx *
- lua_check_redis(lua_State *L, gint pos)
- {
- void *ud = rspamd_lua_check_udata(L, pos, rspamd_redis_classname);
- luaL_argcheck(L, ud != NULL, pos, "'redis' expected");
- return ud ? *((struct lua_redis_ctx **) ud) : NULL;
- }
-
- static void
- lua_redis_free_args(char **args, gsize *arglens, guint nargs)
- {
- guint i;
-
- if (args) {
- for (i = 0; i < nargs; i++) {
- g_free(args[i]);
- }
-
- g_free(args);
- g_free(arglens);
- }
- }
-
- static void
- lua_redis_dtor(struct lua_redis_ctx *ctx)
- {
- struct lua_redis_userdata *ud;
- struct lua_redis_request_specific_userdata *cur, *tmp;
- gboolean is_successful = TRUE;
- struct redisAsyncContext *ac;
-
- ud = &ctx->async;
- msg_debug_lua_redis("destructing %p", ctx);
-
- if (ud->ctx) {
-
- LL_FOREACH_SAFE(ud->specific, cur, tmp)
- {
- ev_timer_stop(ud->event_loop, &cur->timeout_ev);
-
- if (!(cur->flags & LUA_REDIS_SPECIFIC_REPLIED)) {
- is_successful = FALSE;
- }
-
- cur->flags |= LUA_REDIS_SPECIFIC_FINISHED;
- }
-
- ctx->flags |= LUA_REDIS_TERMINATED;
-
- ud->terminated = 1;
- ac = ud->ctx;
- ud->ctx = NULL;
-
- if (!is_successful) {
- rspamd_redis_pool_release_connection(ud->pool, ac,
- RSPAMD_REDIS_RELEASE_FATAL);
- }
- else {
- rspamd_redis_pool_release_connection(ud->pool, ac,
- (ctx->flags & LUA_REDIS_NO_POOL) ? RSPAMD_REDIS_RELEASE_ENFORCE : RSPAMD_REDIS_RELEASE_DEFAULT);
- }
- }
-
- LL_FOREACH_SAFE(ud->specific, cur, tmp)
- {
- lua_redis_free_args(cur->args, cur->arglens, cur->nargs);
-
- if (cur->cbref != -1) {
- luaL_unref(ud->cfg->lua_state, LUA_REGISTRYINDEX, cur->cbref);
- }
-
- g_free(cur);
- }
-
- if (ctx->events_cleanup) {
- g_queue_free(ctx->events_cleanup);
- ctx->events_cleanup = NULL;
- }
- if (ctx->replies) {
- g_queue_free(ctx->replies);
- ctx->replies = NULL;
- }
-
- g_free(ctx);
- }
-
- static gint
- lua_redis_gc(lua_State *L)
- {
- struct lua_redis_ctx *ctx = lua_check_redis(L, 1);
-
- if (ctx) {
- REDIS_RELEASE(ctx);
- }
-
- return 0;
- }
-
- static void
- lua_redis_fin(void *arg)
- {
- struct lua_redis_request_specific_userdata *sp_ud = arg;
- struct lua_redis_userdata *ud;
- struct lua_redis_ctx *ctx;
-
- ctx = sp_ud->ctx;
- ud = sp_ud->c;
-
- if (ev_can_stop(&sp_ud->timeout_ev)) {
- ev_timer_stop(sp_ud->ctx->async.event_loop, &sp_ud->timeout_ev);
- }
-
- msg_debug_lua_redis("finished redis query %p from session %p; refcount=%d",
- sp_ud, ctx, ctx->ref.refcount);
- sp_ud->flags |= LUA_REDIS_SPECIFIC_FINISHED;
-
- REDIS_RELEASE(ctx);
- }
-
- /**
- * Push error of redis request to lua callback
- * @param code
- * @param ud
- */
- static void
- lua_redis_push_error(const gchar *err,
- struct lua_redis_ctx *ctx,
- struct lua_redis_request_specific_userdata *sp_ud,
- gboolean connected)
- {
- struct lua_redis_userdata *ud = sp_ud->c;
- struct lua_callback_state cbs;
- lua_State *L;
-
- if (!(sp_ud->flags & (LUA_REDIS_SPECIFIC_REPLIED | LUA_REDIS_SPECIFIC_FINISHED))) {
- if (sp_ud->cbref != -1) {
-
- lua_thread_pool_prepare_callback(ud->cfg->lua_thread_pool, &cbs);
- L = cbs.L;
-
- lua_pushcfunction(L, &rspamd_lua_traceback);
- int err_idx = lua_gettop(L);
- /* Push error */
- lua_rawgeti(cbs.L, LUA_REGISTRYINDEX, sp_ud->cbref);
-
- /* String of error */
- lua_pushstring(cbs.L, err);
- /* Data is nil */
- lua_pushnil(cbs.L);
-
- if (ud->item) {
- rspamd_symcache_set_cur_item(ud->task, ud->item);
- }
-
- if (lua_pcall(cbs.L, 2, 0, err_idx) != 0) {
- msg_info("call to callback failed: %s", lua_tostring(cbs.L, -1));
- }
-
- lua_settop(L, err_idx - 1);
- lua_thread_pool_restore_callback(&cbs);
- }
-
- sp_ud->flags |= LUA_REDIS_SPECIFIC_REPLIED;
-
- if (connected && ud->s) {
- if (ud->item) {
- rspamd_symcache_item_async_dec_check(ud->task, ud->item, M);
- }
-
- rspamd_session_remove_event(ud->s, lua_redis_fin, sp_ud);
- }
- else {
- lua_redis_fin(sp_ud);
- }
- }
- }
-
- static void
- lua_redis_push_reply(lua_State *L, const redisReply *r, gboolean text_data)
- {
- guint i;
- struct rspamd_lua_text *t;
-
- switch (r->type) {
- case REDIS_REPLY_INTEGER:
- lua_pushinteger(L, r->integer);
- break;
- case REDIS_REPLY_NIL:
- lua_getfield(L, LUA_REGISTRYINDEX, "redis.null");
- break;
- case REDIS_REPLY_STRING:
- case REDIS_REPLY_STATUS:
- if (text_data) {
- t = lua_newuserdata(L, sizeof(*t));
- rspamd_lua_setclass(L, rspamd_text_classname, -1);
- t->flags = 0;
- t->start = r->str;
- t->len = r->len;
- }
- else {
- lua_pushlstring(L, r->str, r->len);
- }
- break;
- case REDIS_REPLY_ARRAY:
- lua_createtable(L, r->elements, 0);
- for (i = 0; i < r->elements; ++i) {
- lua_redis_push_reply(L, r->element[i], text_data);
- lua_rawseti(L, -2, i + 1); /* Store sub-reply */
- }
- break;
- default: /* should not happen */
- msg_info("unknown reply type: %d", r->type);
- break;
- }
- }
-
- /**
- * Push data of redis request to lua callback
- * @param r redis reply data
- * @param ud
- */
- static void
- lua_redis_push_data(const redisReply *r, struct lua_redis_ctx *ctx,
- struct lua_redis_request_specific_userdata *sp_ud)
- {
- struct lua_redis_userdata *ud = sp_ud->c;
- struct lua_callback_state cbs;
- lua_State *L;
-
- if (!(sp_ud->flags & (LUA_REDIS_SPECIFIC_REPLIED | LUA_REDIS_SPECIFIC_FINISHED)) ||
- (sp_ud->flags & LUA_REDIS_SUBSCRIBED)) {
- if (sp_ud->cbref != -1) {
- lua_thread_pool_prepare_callback(ud->cfg->lua_thread_pool, &cbs);
- L = cbs.L;
-
- lua_pushcfunction(L, &rspamd_lua_traceback);
- int err_idx = lua_gettop(L);
- /* Push error */
- lua_rawgeti(cbs.L, LUA_REGISTRYINDEX, sp_ud->cbref);
- /* Error is nil */
- lua_pushnil(cbs.L);
- /* Data */
- lua_redis_push_reply(cbs.L, r, ctx->flags & LUA_REDIS_TEXTDATA);
-
- if (ud->item) {
- rspamd_symcache_set_cur_item(ud->task, ud->item);
- }
-
- gint ret = lua_pcall(cbs.L, 2, 0, err_idx);
-
- if (ret != 0) {
- msg_info("call to lua_redis callback failed (%d): %s",
- ret, lua_tostring(cbs.L, -1));
- }
-
- lua_settop(L, err_idx - 1);
- lua_thread_pool_restore_callback(&cbs);
- }
-
- if (sp_ud->flags & LUA_REDIS_SUBSCRIBED) {
- if (!(sp_ud->flags & LUA_REDIS_SPECIFIC_REPLIED)) {
- if (ev_can_stop(&sp_ud->timeout_ev)) {
- ev_timer_stop(sp_ud->ctx->async.event_loop,
- &sp_ud->timeout_ev);
- }
- }
- }
-
- sp_ud->flags |= LUA_REDIS_SPECIFIC_REPLIED;
-
- if (!(sp_ud->flags & LUA_REDIS_SUBSCRIBED)) {
- if (ud->s) {
- if (ud->item) {
- rspamd_symcache_item_async_dec_check(ud->task,
- ud->item, M);
- }
-
- rspamd_session_remove_event(ud->s, lua_redis_fin, sp_ud);
- }
- else {
- lua_redis_fin(sp_ud);
- }
- }
- }
- }
-
- /**
- * Callback for redis replies
- * @param c context of redis connection
- * @param r redis reply
- * @param priv userdata
- */
- static void
- lua_redis_callback(redisAsyncContext *c, gpointer r, gpointer priv)
- {
- redisReply *reply = r;
- struct lua_redis_request_specific_userdata *sp_ud = priv;
- struct lua_redis_ctx *ctx;
- struct lua_redis_userdata *ud;
- redisAsyncContext *ac;
-
- ctx = sp_ud->ctx;
- ud = sp_ud->c;
-
- if (ud->terminated || !rspamd_lua_is_initialised()) {
- /* We are already at the termination stage, just go out */
- return;
- }
-
- msg_debug_lua_redis("got reply from redis %p for query %p", sp_ud->c->ctx,
- sp_ud);
-
- REDIS_RETAIN(ctx);
-
- /* If session is finished, we cannot call lua callbacks */
- if (!(sp_ud->flags & LUA_REDIS_SPECIFIC_FINISHED) ||
- (sp_ud->flags & LUA_REDIS_SUBSCRIBED)) {
- if (c->err == 0) {
- if (r != NULL) {
- if (reply->type != REDIS_REPLY_ERROR) {
- lua_redis_push_data(reply, ctx, sp_ud);
- }
- else {
- lua_redis_push_error(reply->str, ctx, sp_ud, TRUE);
- }
- }
- else {
- lua_redis_push_error("received no data from server", ctx, sp_ud, TRUE);
- }
- }
- else {
- if (c->err == REDIS_ERR_IO) {
- lua_redis_push_error(strerror(errno), ctx, sp_ud, TRUE);
- }
- else {
- lua_redis_push_error(c->errstr, ctx, sp_ud, TRUE);
- }
- }
- }
-
- if (!(sp_ud->flags & LUA_REDIS_SUBSCRIBED)) {
- ctx->cmds_pending--;
-
- if (ctx->cmds_pending == 0 && !ud->terminated) {
- /* Disconnect redis early as we don't need it anymore */
- ud->terminated = 1;
- ac = ud->ctx;
- ud->ctx = NULL;
-
- if (ac) {
- msg_debug_lua_redis("release redis connection ud=%p; ctx=%p; refcount=%d",
- ud, ctx, ctx->ref.refcount);
- rspamd_redis_pool_release_connection(ud->pool, ac,
- (ctx->flags & LUA_REDIS_NO_POOL) ? RSPAMD_REDIS_RELEASE_ENFORCE : RSPAMD_REDIS_RELEASE_DEFAULT);
- }
- }
- }
-
- REDIS_RELEASE(ctx);
- }
-
- static gint
- lua_redis_push_results(struct lua_redis_ctx *ctx, lua_State *L)
- {
- gint results = g_queue_get_length(ctx->replies);
- gint i;
- gboolean can_use_lua = TRUE;
-
- results = g_queue_get_length(ctx->replies);
-
- if (!lua_checkstack(L, (results * 2) + 1)) {
- luaL_error(L, "cannot resize stack to fit %d commands",
- ctx->cmds_pending);
-
- can_use_lua = FALSE;
- }
-
- for (i = 0; i < results; i++) {
- struct lua_redis_result *result = g_queue_pop_head(ctx->replies);
-
- if (can_use_lua) {
- lua_pushboolean(L, !result->is_error);
- lua_rawgeti(L, LUA_REGISTRYINDEX, result->result_ref);
- }
-
- luaL_unref(L, LUA_REGISTRYINDEX, result->result_ref);
-
- g_queue_push_tail(ctx->events_cleanup, result);
- }
-
- return can_use_lua ? results * 2 : 0;
- }
-
- static void
- lua_redis_cleanup_events(struct lua_redis_ctx *ctx)
- {
- REDIS_RETAIN(ctx); /* To avoid preliminary destruction */
-
- while (!g_queue_is_empty(ctx->events_cleanup)) {
- struct lua_redis_result *result = g_queue_pop_head(ctx->events_cleanup);
-
- if (result->item) {
- rspamd_symcache_item_async_dec_check(result->task, result->item, M);
- }
-
- if (result->s) {
- rspamd_session_remove_event(result->s, lua_redis_fin, result->sp_ud);
- }
- else {
- lua_redis_fin(result->sp_ud);
- }
-
- g_free(result);
- }
-
- REDIS_RELEASE(ctx);
- }
-
- /**
- * Callback for redis replies
- * @param c context of redis connection
- * @param r redis reply
- * @param priv userdata
- */
- static void
- lua_redis_callback_sync(redisAsyncContext *ac, gpointer r, gpointer priv)
- {
- redisReply *reply = r;
-
- struct lua_redis_request_specific_userdata *sp_ud = priv;
- struct lua_redis_ctx *ctx;
- struct lua_redis_userdata *ud;
- struct thread_entry *thread;
- gint results;
-
- ctx = sp_ud->ctx;
- ud = sp_ud->c;
- lua_State *L = ctx->async.cfg->lua_state;
-
- sp_ud->flags |= LUA_REDIS_SPECIFIC_REPLIED;
-
- if (ud->terminated) {
- /* We are already at the termination stage, just go out */
- /* TODO:
- if somebody is waiting for us (ctx->thread), return result,
- otherwise, indeed, ignore
- */
- return;
- }
-
- if (ev_can_stop(&sp_ud->timeout_ev)) {
- ev_timer_stop(ud->event_loop, &sp_ud->timeout_ev);
- }
-
- if (!(sp_ud->flags & LUA_REDIS_SPECIFIC_FINISHED)) {
- msg_debug_lua_redis("got reply from redis: %p for query %p", ac, sp_ud);
-
- struct lua_redis_result *result = g_malloc0(sizeof *result);
-
- if (ac->err == 0) {
- if (r != NULL) {
- if (reply->type != REDIS_REPLY_ERROR) {
- result->is_error = FALSE;
- lua_redis_push_reply(L, reply, ctx->flags & LUA_REDIS_TEXTDATA);
- }
- else {
- result->is_error = TRUE;
- lua_pushstring(L, reply->str);
- }
- }
- else {
- result->is_error = TRUE;
- lua_pushliteral(L, "received no data from server");
- }
- }
- else {
- result->is_error = TRUE;
- if (ac->err == REDIS_ERR_IO) {
- lua_pushstring(L, strerror(errno));
- }
- else {
- lua_pushstring(L, ac->errstr);
- }
- }
-
- /* if error happened, we should terminate the connection,
- and release it */
-
- if (result->is_error && sp_ud->c->ctx) {
- ac = sp_ud->c->ctx;
- /* Set to NULL to avoid double free in dtor */
- sp_ud->c->ctx = NULL;
- ctx->flags |= LUA_REDIS_TERMINATED;
-
- /*
- * This will call all callbacks pending so the entire context
- * will be destructed
- */
- rspamd_redis_pool_release_connection(sp_ud->c->pool, ac,
- RSPAMD_REDIS_RELEASE_FATAL);
- }
-
- result->result_ref = luaL_ref(L, LUA_REGISTRYINDEX);
- result->s = ud->s;
- result->item = ud->item;
- result->task = ud->task;
- result->sp_ud = sp_ud;
-
- g_queue_push_tail(ctx->replies, result);
- }
-
- ctx->cmds_pending--;
-
- if (ctx->cmds_pending == 0) {
- if (ctx->thread) {
- if (!(sp_ud->flags & LUA_REDIS_SPECIFIC_FINISHED)) {
- /* somebody yielded and waits for results */
- thread = ctx->thread;
- ctx->thread = NULL;
-
- results = lua_redis_push_results(ctx, thread->lua_state);
-
- if (ud->item) {
- rspamd_symcache_set_cur_item(ud->task, ud->item);
- }
-
- lua_thread_resume(thread, results);
- lua_redis_cleanup_events(ctx);
- }
- else {
- /* We cannot resume the thread as the associated task has gone */
- lua_thread_pool_terminate_entry_full(ud->cfg->lua_thread_pool,
- ctx->thread, G_STRLOC, true);
- ctx->thread = NULL;
- }
- }
- }
- }
-
- static void
- lua_redis_timeout_sync(EV_P_ ev_timer *w, int revents)
- {
- struct lua_redis_request_specific_userdata *sp_ud =
- (struct lua_redis_request_specific_userdata *) w->data;
- struct lua_redis_ctx *ctx;
- struct lua_redis_userdata *ud;
- redisAsyncContext *ac;
-
- if (sp_ud->flags & LUA_REDIS_SPECIFIC_FINISHED) {
- return;
- }
-
- ud = sp_ud->c;
- ctx = sp_ud->ctx;
- msg_debug_lua_redis("timeout while querying redis server: %p, redis: %p", sp_ud,
- sp_ud->c->ctx);
-
- if (sp_ud->c->ctx) {
- ac = sp_ud->c->ctx;
-
- /* Set to NULL to avoid double free in dtor */
- sp_ud->c->ctx = NULL;
- ac->err = REDIS_ERR_IO;
- errno = ETIMEDOUT;
- ctx->flags |= LUA_REDIS_TERMINATED;
-
- /*
- * This will call all callbacks pending so the entire context
- * will be destructed
- */
- rspamd_redis_pool_release_connection(sp_ud->c->pool, ac,
- RSPAMD_REDIS_RELEASE_FATAL);
- }
- }
-
- static void
- lua_redis_timeout(EV_P_ ev_timer *w, int revents)
- {
- struct lua_redis_request_specific_userdata *sp_ud =
- (struct lua_redis_request_specific_userdata *) w->data;
- struct lua_redis_userdata *ud;
- struct lua_redis_ctx *ctx;
- redisAsyncContext *ac;
-
- if (sp_ud->flags & LUA_REDIS_SPECIFIC_FINISHED) {
- return;
- }
-
- ctx = sp_ud->ctx;
- ud = sp_ud->c;
-
- REDIS_RETAIN(ctx);
- msg_debug_lua_redis("timeout while querying redis server: %p, redis: %p", sp_ud,
- sp_ud->c->ctx);
- lua_redis_push_error("timeout while connecting the server", ctx, sp_ud, TRUE);
-
- if (sp_ud->c->ctx) {
- ac = sp_ud->c->ctx;
- /* Set to NULL to avoid double free in dtor */
- sp_ud->c->ctx = NULL;
- ac->err = REDIS_ERR_IO;
- errno = ETIMEDOUT;
- /*
- * This will call all callbacks pending so the entire context
- * will be destructed
- */
- rspamd_redis_pool_release_connection(sp_ud->c->pool, ac,
- RSPAMD_REDIS_RELEASE_FATAL);
- }
-
- REDIS_RELEASE(ctx);
- }
-
-
- static void
- lua_redis_parse_args(lua_State *L, gint idx, const gchar *cmd,
- gchar ***pargs, gsize **parglens, guint *nargs)
- {
- gchar **args = NULL;
- gsize *arglens;
- gint top;
-
- if (idx != 0 && lua_type(L, idx) == LUA_TTABLE) {
- /* Get all arguments */
- lua_pushvalue(L, idx);
- lua_pushnil(L);
- top = 0;
-
- while (lua_next(L, -2) != 0) {
- gint type = lua_type(L, -1);
-
- if (type == LUA_TNUMBER || type == LUA_TSTRING ||
- type == LUA_TUSERDATA) {
- top++;
- }
- lua_pop(L, 1);
- }
-
- args = g_malloc((top + 1) * sizeof(gchar *));
- arglens = g_malloc((top + 1) * sizeof(gsize));
- arglens[0] = strlen(cmd);
- args[0] = g_malloc(arglens[0]);
- memcpy(args[0], cmd, arglens[0]);
- top = 1;
- lua_pushnil(L);
-
- while (lua_next(L, -2) != 0) {
- gint type = lua_type(L, -1);
-
- if (type == LUA_TSTRING) {
- const gchar *s;
-
- s = lua_tolstring(L, -1, &arglens[top]);
- args[top] = g_malloc(arglens[top]);
- memcpy(args[top], s, arglens[top]);
- top++;
- }
- else if (type == LUA_TUSERDATA) {
- struct rspamd_lua_text *t;
-
- t = lua_check_text(L, -1);
-
- if (t && t->start) {
- arglens[top] = t->len;
- args[top] = g_malloc(arglens[top]);
- memcpy(args[top], t->start, arglens[top]);
- top++;
- }
- }
- else if (type == LUA_TNUMBER) {
- gdouble val = lua_tonumber(L, -1);
- gint r;
- gchar numbuf[64];
-
- if (val == (gdouble) ((int64_t) val)) {
- r = rspamd_snprintf(numbuf, sizeof(numbuf), "%L",
- (int64_t) val);
- }
- else {
- r = rspamd_snprintf(numbuf, sizeof(numbuf), "%f",
- val);
- }
-
- arglens[top] = r;
- args[top] = g_malloc(arglens[top]);
- memcpy(args[top], numbuf, arglens[top]);
- top++;
- }
-
- lua_pop(L, 1);
- }
-
- lua_pop(L, 1);
- }
- else {
- /* Use merely cmd */
-
- args = g_malloc(sizeof(gchar *));
- arglens = g_malloc(sizeof(gsize));
- arglens[0] = strlen(cmd);
- args[0] = g_malloc(arglens[0]);
- memcpy(args[0], cmd, arglens[0]);
- top = 1;
- }
-
- *pargs = args;
- *parglens = arglens;
- *nargs = top;
- }
-
- static struct lua_redis_ctx *
- rspamd_lua_redis_prepare_connection(lua_State *L, gint *pcbref, gboolean is_async)
- {
- struct lua_redis_ctx *ctx = NULL;
- rspamd_inet_addr_t *ip = NULL;
- struct lua_redis_userdata *ud = NULL;
- struct rspamd_lua_ip *addr = NULL;
- struct rspamd_task *task = NULL;
- const gchar *host = NULL;
- const gchar *username = NULL, *password = NULL, *dbname = NULL, *log_tag = NULL;
- gint cbref = -1;
- struct rspamd_config *cfg = NULL;
- struct rspamd_async_session *session = NULL;
- struct ev_loop *ev_base = NULL;
- gboolean ret = FALSE;
- guint flags = 0;
-
- if (lua_istable(L, 1)) {
- /* Table version */
- lua_pushvalue(L, 1);
- lua_pushstring(L, "task");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TUSERDATA) {
- task = lua_check_task_maybe(L, -1);
- }
- lua_pop(L, 1);
-
- if (!task) {
- /* We need to get ev_base, config and session separately */
- lua_pushstring(L, "config");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TUSERDATA) {
- cfg = lua_check_config(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "session");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TUSERDATA) {
- session = lua_check_session(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "ev_base");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TUSERDATA) {
- ev_base = lua_check_ev_base(L, -1);
- }
- lua_pop(L, 1);
-
- if (cfg && ev_base) {
- ret = TRUE;
- }
- else if (!cfg) {
- msg_err_task_check("config is not passed");
- }
- else {
- msg_err_task_check("ev_base is not set");
- }
- }
- else {
- cfg = task->cfg;
- session = task->s;
- ev_base = task->event_loop;
- log_tag = task->task_pool->tag.uid;
- ret = TRUE;
- }
-
- if (pcbref) {
- lua_pushstring(L, "callback");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TFUNCTION) {
- /* This also pops function from the stack */
- cbref = luaL_ref(L, LUA_REGISTRYINDEX);
- *pcbref = cbref;
- }
- else {
- *pcbref = -1;
- lua_pop(L, 1);
- }
- }
-
- lua_pushstring(L, "host");
- lua_gettable(L, -2);
-
- if (lua_type(L, -1) == LUA_TUSERDATA) {
- addr = lua_check_ip(L, -1);
- host = rspamd_inet_address_to_string_pretty(addr->addr);
- }
- else if (lua_type(L, -1) == LUA_TSTRING) {
- host = lua_tostring(L, -1);
-
- if (rspamd_parse_inet_address(&ip,
- host, strlen(host), RSPAMD_INET_ADDRESS_PARSE_DEFAULT)) {
- addr = g_alloca(sizeof(*addr));
- addr->addr = ip;
-
- if (rspamd_inet_address_get_port(ip) == 0) {
- rspamd_inet_address_set_port(ip, 6379);
- }
- }
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "username");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TSTRING) {
- username = lua_tostring(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "password");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TSTRING) {
- password = lua_tostring(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "dbname");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TSTRING) {
- dbname = lua_tostring(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "opaque_data");
- lua_gettable(L, -2);
- if (!!lua_toboolean(L, -1)) {
- flags |= LUA_REDIS_TEXTDATA;
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "no_pool");
- lua_gettable(L, -2);
- if (!!lua_toboolean(L, -1)) {
- flags |= LUA_REDIS_NO_POOL;
- }
- lua_pop(L, 1);
-
- lua_pop(L, 1); /* table */
-
- if (session && rspamd_session_blocked(session)) {
- msg_err_task_check("Session is being destroying");
- ret = FALSE;
- }
-
- if (ret && addr != NULL) {
- ctx = g_malloc0(sizeof(struct lua_redis_ctx));
- REF_INIT_RETAIN(ctx, lua_redis_dtor);
- if (is_async) {
- ctx->flags |= flags | LUA_REDIS_ASYNC;
- ud = &ctx->async;
- }
- else {
- ud = &ctx->async;
- ctx->replies = g_queue_new();
- ctx->events_cleanup = g_queue_new();
- }
-
- ud->s = session;
- ud->cfg = cfg;
- ud->pool = cfg->redis_pool;
- ud->event_loop = ev_base;
- ud->task = task;
-
- if (log_tag) {
- rspamd_strlcpy(ud->log_tag, log_tag, sizeof(ud->log_tag));
- }
- else {
- /* Use pointer itself as a tag */
- rspamd_snprintf(ud->log_tag, sizeof(ud->log_tag),
- "%ud",
- (int) rspamd_cryptobox_fast_hash(&ud, sizeof(ud), 0));
- }
-
- if (task) {
- ud->item = rspamd_symcache_get_cur_item(task);
- }
-
- ret = TRUE;
- }
- else {
- if (cbref != -1) {
- luaL_unref(L, LUA_REGISTRYINDEX, cbref);
- }
-
- msg_err_task_check("incorrect function invocation");
- ret = FALSE;
- }
- }
-
- if (ret) {
- ud->terminated = 0;
- ud->ctx = rspamd_redis_pool_connect(ud->pool,
- dbname, username, password,
- rspamd_inet_address_to_string(addr->addr),
- rspamd_inet_address_get_port(addr->addr));
-
- if (ip) {
- rspamd_inet_address_free(ip);
- }
-
- if (ud->ctx == NULL || ud->ctx->err) {
- if (ud->ctx) {
- msg_err_task_check("cannot connect to redis: %s",
- ud->ctx->errstr);
- rspamd_redis_pool_release_connection(ud->pool, ud->ctx,
- RSPAMD_REDIS_RELEASE_FATAL);
- ud->ctx = NULL;
- }
- else {
- msg_err_task_check("cannot connect to redis (OS error): %s",
- strerror(errno));
- }
-
- REDIS_RELEASE(ctx);
-
- return NULL;
- }
-
- msg_debug_lua_redis("opened redis connection host=%s; ctx=%p; ud=%p",
- host, ctx, ud);
-
- return ctx;
- }
-
- if (ip) {
- rspamd_inet_address_free(ip);
- }
-
- return NULL;
- }
-
- /***
- * @function rspamd_redis.make_request({params})
- * Make request to redis server, params is a table of key=value arguments in any order
- * @param {task} task worker task object
- * @param {ip|string} host server address
- * @param {function} callback callback to be called in form `function (task, err, data)`
- * @param {string} cmd command to be sent to redis
- * @param {table} args numeric array of strings used as redis arguments
- * @param {number} timeout timeout in seconds for request (1.0 by default)
- * @return {boolean} `true` if a request has been scheduled
- */
- static int
- lua_redis_make_request(lua_State *L)
- {
- LUA_TRACE_POINT;
- struct lua_redis_request_specific_userdata *sp_ud;
- struct lua_redis_userdata *ud;
- struct lua_redis_ctx *ctx, **pctx;
- const gchar *cmd = NULL;
- gdouble timeout = REDIS_DEFAULT_TIMEOUT;
- gint cbref = -1;
- gboolean ret = FALSE;
-
- ctx = rspamd_lua_redis_prepare_connection(L, &cbref, TRUE);
-
- if (ctx) {
- ud = &ctx->async;
- sp_ud = g_malloc0(sizeof(*sp_ud));
- sp_ud->cbref = cbref;
- sp_ud->c = ud;
- sp_ud->ctx = ctx;
-
- lua_pushstring(L, "cmd");
- lua_gettable(L, -2);
- cmd = lua_tostring(L, -1);
- lua_pop(L, 1);
-
- lua_pushstring(L, "timeout");
- lua_gettable(L, 1);
- if (lua_type(L, -1) == LUA_TNUMBER) {
- timeout = lua_tonumber(L, -1);
- }
- lua_pop(L, 1);
- ud->timeout = timeout;
-
-
- lua_pushstring(L, "args");
- lua_gettable(L, 1);
- lua_redis_parse_args(L, -1, cmd, &sp_ud->args, &sp_ud->arglens,
- &sp_ud->nargs);
- lua_pop(L, 1);
- LL_PREPEND(ud->specific, sp_ud);
-
- ret = redisAsyncCommandArgv(ud->ctx,
- lua_redis_callback,
- sp_ud,
- sp_ud->nargs,
- (const gchar **) sp_ud->args,
- sp_ud->arglens);
-
- if (ret == REDIS_OK) {
- if (ud->s) {
- rspamd_session_add_event(ud->s,
- lua_redis_fin, sp_ud,
- M);
-
- if (ud->item) {
- rspamd_symcache_item_async_inc(ud->task, ud->item, M);
- }
- }
-
- REDIS_RETAIN(ctx); /* Cleared by fin event */
- ctx->cmds_pending++;
-
- if (ud->ctx->c.flags & REDIS_SUBSCRIBED) {
- msg_debug_lua_redis("subscribe command, never unref/timeout");
- sp_ud->flags |= LUA_REDIS_SUBSCRIBED;
- }
-
- sp_ud->timeout_ev.data = sp_ud;
- ev_now_update_if_cheap((struct ev_loop *) ud->event_loop);
- ev_timer_init(&sp_ud->timeout_ev, lua_redis_timeout, timeout, 0.0);
- ev_timer_start(ud->event_loop, &sp_ud->timeout_ev);
-
- ret = TRUE;
- }
- else {
- msg_info("call to redis failed: %s", ud->ctx->errstr);
- rspamd_redis_pool_release_connection(ud->pool, ud->ctx,
- RSPAMD_REDIS_RELEASE_FATAL);
- ud->ctx = NULL;
- REDIS_RELEASE(ctx);
- ret = FALSE;
- }
- }
- else {
- lua_pushboolean(L, FALSE);
- lua_pushnil(L);
-
- return 2;
- }
-
- lua_pushboolean(L, ret);
-
- if (ret) {
- pctx = lua_newuserdata(L, sizeof(ctx));
- *pctx = ctx;
- rspamd_lua_setclass(L, rspamd_redis_classname, -1);
- }
- else {
- lua_pushnil(L);
- }
-
- return 2;
- }
-
- /***
- * @function rspamd_redis.make_request_sync({params})
- * Make blocking request to redis server, params is a table of key=value arguments in any order
- * @param {ip|string} host server address
- * @param {string} cmd command to be sent to redis
- * @param {table} args numeric array of strings used as redis arguments
- * @param {number} timeout timeout in seconds for request (1.0 by default)
- * @return {boolean + result} `true` and a result if a request has been successful
- */
- static int
- lua_redis_make_request_sync(lua_State *L)
- {
- LUA_TRACE_POINT;
- struct rspamd_lua_ip *addr = NULL;
- rspamd_inet_addr_t *ip = NULL;
- const gchar *cmd = NULL, *host;
- struct timeval tv;
- gboolean ret = FALSE;
- gdouble timeout = REDIS_DEFAULT_TIMEOUT;
- gchar **args = NULL;
- gsize *arglens = NULL;
- guint nargs = 0, flags = 0;
- redisContext *ctx;
- redisReply *r;
-
- if (lua_istable(L, 1)) {
- lua_pushvalue(L, 1);
-
- lua_pushstring(L, "cmd");
- lua_gettable(L, -2);
- cmd = lua_tostring(L, -1);
- lua_pop(L, 1);
-
- lua_pushstring(L, "host");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TUSERDATA) {
- addr = lua_check_ip(L, -1);
- }
- else if (lua_type(L, -1) == LUA_TSTRING) {
- host = lua_tostring(L, -1);
- if (rspamd_parse_inet_address(&ip,
- host, strlen(host), RSPAMD_INET_ADDRESS_PARSE_DEFAULT)) {
- addr = g_alloca(sizeof(*addr));
- addr->addr = ip;
-
- if (rspamd_inet_address_get_port(ip) == 0) {
- rspamd_inet_address_set_port(ip, 6379);
- }
- }
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "timeout");
- lua_gettable(L, -2);
- if (lua_type(L, -1) == LUA_TNUMBER) {
- timeout = lua_tonumber(L, -1);
- }
- lua_pop(L, 1);
-
- lua_pushstring(L, "opaque_data");
- lua_gettable(L, -2);
- if (!!lua_toboolean(L, -1)) {
- flags |= LUA_REDIS_TEXTDATA;
- }
- lua_pop(L, 1);
-
-
- if (cmd) {
- lua_pushstring(L, "args");
- lua_gettable(L, -2);
- lua_redis_parse_args(L, -1, cmd, &args, &arglens, &nargs);
- lua_pop(L, 1);
- }
-
- lua_pop(L, 1);
-
- if (addr && cmd) {
- ret = TRUE;
- }
- }
-
- if (ret) {
- double_to_tv(timeout, &tv);
-
- if (rspamd_inet_address_get_af(addr->addr) == AF_UNIX) {
- ctx = redisConnectUnixWithTimeout(
- rspamd_inet_address_to_string(addr->addr), tv);
- }
- else {
- ctx = redisConnectWithTimeout(
- rspamd_inet_address_to_string(addr->addr),
- rspamd_inet_address_get_port(addr->addr), tv);
- }
-
- if (ip) {
- rspamd_inet_address_free(ip);
- }
-
- if (ctx == NULL || ctx->err) {
- redisFree(ctx);
- lua_redis_free_args(args, arglens, nargs);
- lua_pushboolean(L, FALSE);
-
- return 1;
- }
-
- r = redisCommandArgv(ctx,
- nargs,
- (const gchar **) args,
- arglens);
-
- if (r != NULL) {
- if (r->type != REDIS_REPLY_ERROR) {
- lua_pushboolean(L, TRUE);
- lua_redis_push_reply(L, r, flags & LUA_REDIS_TEXTDATA);
- }
- else {
- lua_pushboolean(L, FALSE);
- lua_pushstring(L, r->str);
- }
-
- freeReplyObject(r);
- redisFree(ctx);
- lua_redis_free_args(args, arglens, nargs);
-
- return 2;
- }
- else {
- msg_info("call to redis failed: %s", ctx->errstr);
- redisFree(ctx);
- lua_redis_free_args(args, arglens, nargs);
- lua_pushboolean(L, FALSE);
- }
- }
- else {
- if (ip) {
- rspamd_inet_address_free(ip);
- }
- msg_err("bad arguments for redis request");
- lua_redis_free_args(args, arglens, nargs);
-
- lua_pushboolean(L, FALSE);
- }
-
- return 1;
- }
-
- /***
- * @function rspamd_redis.connect({params})
- * Make request to redis server, params is a table of key=value arguments in any order
- * @param {task} task worker task object
- * @param {ip|string} host server address
- * @param {number} timeout timeout in seconds for request (1.0 by default)
- * @return {boolean,redis} new connection object or nil if connection failed
- */
- static int
- lua_redis_connect(lua_State *L)
- {
- LUA_TRACE_POINT;
- struct lua_redis_userdata *ud;
- struct lua_redis_ctx *ctx, **pctx;
- gdouble timeout = REDIS_DEFAULT_TIMEOUT;
-
- ctx = rspamd_lua_redis_prepare_connection(L, NULL, TRUE);
-
- if (ctx) {
- ud = &ctx->async;
-
- lua_pushstring(L, "timeout");
- lua_gettable(L, 1);
- if (lua_type(L, -1) == LUA_TNUMBER) {
- timeout = lua_tonumber(L, -1);
- }
-
- lua_pop(L, 1);
- ud->timeout = timeout;
- }
- else {
- lua_pushboolean(L, FALSE);
- lua_pushnil(L);
-
- return 2;
- }
-
- lua_pushboolean(L, TRUE);
- pctx = lua_newuserdata(L, sizeof(ctx));
- *pctx = ctx;
- rspamd_lua_setclass(L, rspamd_redis_classname, -1);
-
- return 2;
- }
-
- /***
- * @function rspamd_redis.connect_sync({params})
- * Make blocking request to redis server, params is a table of key=value arguments in any order
- * @param {ip|string} host server address
- * @param {number} timeout timeout in seconds for request (1.0 by default)
- * @return {redis} redis object if a request has been successful
- */
- static int
- lua_redis_connect_sync(lua_State *L)
- {
- LUA_TRACE_POINT;
- gdouble timeout = REDIS_DEFAULT_TIMEOUT;
- struct lua_redis_ctx *ctx, **pctx;
-
- ctx = rspamd_lua_redis_prepare_connection(L, NULL, FALSE);
-
- if (ctx) {
- if (lua_istable(L, 1)) {
- lua_pushstring(L, "timeout");
- lua_gettable(L, 1);
- if (lua_type(L, -1) == LUA_TNUMBER) {
- timeout = lua_tonumber(L, -1);
- }
- lua_pop(L, 1);
- }
-
- ctx->async.timeout = timeout;
-
- lua_pushboolean(L, TRUE);
- pctx = lua_newuserdata(L, sizeof(ctx));
- *pctx = ctx;
- rspamd_lua_setclass(L, rspamd_redis_classname, -1);
- }
- else {
- lua_pushboolean(L, FALSE);
- lua_pushstring(L, "bad arguments for redis request");
- return 2;
- }
-
- return 2;
- }
-
- /***
- * @method rspamd_redis:add_cmd(cmd, {args})
- * Append new cmd to redis pipeline
- * @param {string} cmd command to be sent to redis
- * @param {table} args array of strings used as redis arguments
- * @return {boolean} `true` if a request has been successful
- */
- static int
- lua_redis_add_cmd(lua_State *L)
- {
- LUA_TRACE_POINT;
- struct lua_redis_ctx *ctx = lua_check_redis(L, 1);
- struct lua_redis_request_specific_userdata *sp_ud;
- struct lua_redis_userdata *ud;
- const gchar *cmd = NULL;
- gint args_pos = 2;
- gint cbref = -1, ret;
-
- if (ctx) {
- if (ctx->flags & LUA_REDIS_TERMINATED) {
- lua_pushboolean(L, FALSE);
- lua_pushstring(L, "Connection is terminated");
-
- return 2;
- }
-
- /* Async version */
- if (lua_type(L, 2) == LUA_TSTRING) {
- /* No callback version */
- cmd = lua_tostring(L, 2);
- args_pos = 3;
- }
- else if (lua_type(L, 2) == LUA_TFUNCTION) {
- lua_pushvalue(L, 2);
- cbref = luaL_ref(L, LUA_REGISTRYINDEX);
- cmd = lua_tostring(L, 3);
- args_pos = 4;
- }
- else {
- return luaL_error(L, "invalid arguments");
- }
-
- sp_ud = g_malloc0(sizeof(*sp_ud));
- if (IS_ASYNC(ctx)) {
- sp_ud->c = &ctx->async;
- ud = &ctx->async;
- sp_ud->cbref = cbref;
- }
- else {
- sp_ud->c = &ctx->async;
- ud = &ctx->async;
- }
- sp_ud->ctx = ctx;
-
- lua_redis_parse_args(L, args_pos, cmd, &sp_ud->args,
- &sp_ud->arglens, &sp_ud->nargs);
-
- LL_PREPEND(sp_ud->c->specific, sp_ud);
-
- if (ud->s && rspamd_session_blocked(ud->s)) {
- lua_pushboolean(L, 0);
- lua_pushstring(L, "session is terminating");
-
- return 2;
- }
-
- if (IS_ASYNC(ctx)) {
- ret = redisAsyncCommandArgv(sp_ud->c->ctx,
- lua_redis_callback,
- sp_ud,
- sp_ud->nargs,
- (const gchar **) sp_ud->args,
- sp_ud->arglens);
- }
- else {
- ret = redisAsyncCommandArgv(sp_ud->c->ctx,
- lua_redis_callback_sync,
- sp_ud,
- sp_ud->nargs,
- (const gchar **) sp_ud->args,
- sp_ud->arglens);
- }
-
- if (ret == REDIS_OK) {
- if (ud->s) {
- rspamd_session_add_event(ud->s,
- lua_redis_fin,
- sp_ud,
- M);
-
- if (ud->item) {
- rspamd_symcache_item_async_inc(ud->task, ud->item, M);
- }
- }
-
- sp_ud->timeout_ev.data = sp_ud;
-
- if (IS_ASYNC(ctx)) {
- ev_timer_init(&sp_ud->timeout_ev, lua_redis_timeout,
- sp_ud->c->timeout, 0.0);
- }
- else {
- ev_timer_init(&sp_ud->timeout_ev, lua_redis_timeout_sync,
- sp_ud->c->timeout, 0.0);
- }
-
- ev_timer_start(ud->event_loop, &sp_ud->timeout_ev);
- REDIS_RETAIN(ctx);
- ctx->cmds_pending++;
- }
- else {
- msg_info("call to redis failed: %s",
- sp_ud->c->ctx->errstr);
- lua_pushboolean(L, 0);
- lua_pushstring(L, sp_ud->c->ctx->errstr);
-
- return 2;
- }
- }
-
- lua_pushboolean(L, true);
-
- return 1;
- }
-
- /***
- * @method rspamd_redis:exec()
- * Executes pending commands (suitable for blocking IO only for now)
- * @return {boolean}, {table}, ...: pairs in format [bool, result] for each request pending
- */
- static int
- lua_redis_exec(lua_State *L)
- {
- LUA_TRACE_POINT;
- struct lua_redis_ctx *ctx = lua_check_redis(L, 1);
-
- if (ctx == NULL) {
- lua_error(L);
-
- return 1;
- }
-
- if (IS_ASYNC(ctx)) {
- lua_pushstring(L, "Async redis pipelining is not implemented");
- lua_error(L);
- return 0;
- }
- else {
- if (ctx->cmds_pending == 0 && g_queue_get_length(ctx->replies) == 0) {
- lua_pushstring(L, "No pending commands to execute");
- lua_error(L);
- }
- if (ctx->cmds_pending == 0 && g_queue_get_length(ctx->replies) > 0) {
- gint results = lua_redis_push_results(ctx, L);
- return results;
- }
- else {
- ctx->thread = lua_thread_pool_get_running_entry(ctx->async.cfg->lua_thread_pool);
- return lua_thread_yield(ctx->thread, 0);
- }
- }
- }
-
- static gint
- lua_load_redis(lua_State *L)
- {
- lua_newtable(L);
- luaL_register(L, NULL, redislib_f);
-
- return 1;
- }
-
- static gint
- lua_redis_null_idx(lua_State *L)
- {
- lua_pushnil(L);
-
- return 1;
- }
-
- static void
- lua_redis_null_mt(lua_State *L)
- {
- luaL_newmetatable(L, "redis{null}");
-
- lua_pushcfunction(L, lua_redis_null_idx);
- lua_setfield(L, -2, "__index");
- lua_pushcfunction(L, lua_redis_null_idx);
- lua_setfield(L, -2, "__tostring");
-
- lua_pop(L, 1);
- }
-
- /**
- * Open redis library
- * @param L lua stack
- * @return
- */
- void luaopen_redis(lua_State *L)
- {
- rspamd_lua_new_class(L, rspamd_redis_classname, redislib_m);
- lua_pop(L, 1);
- rspamd_lua_add_preload(L, "rspamd_redis", lua_load_redis);
-
- /* Set null element */
- lua_redis_null_mt(L);
- redis_null = lua_newuserdata(L, 0);
- luaL_getmetatable(L, "redis{null}");
- lua_setmetatable(L, -2);
- lua_setfield(L, LUA_REGISTRYINDEX, "redis.null");
- }
|