mirror of
https://codeberg.org/redict/redict.git
synced 2025-01-22 08:08:53 -05:00
scripting.c source code better organized into sections.
This commit is contained in:
parent
71aa9b75f2
commit
9aa1f94449
336
src/scripting.c
336
src/scripting.c
@ -45,7 +45,37 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
|
|||||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply);
|
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply);
|
||||||
int redis_math_random (lua_State *L);
|
int redis_math_random (lua_State *L);
|
||||||
int redis_math_randomseed (lua_State *L);
|
int redis_math_randomseed (lua_State *L);
|
||||||
void sha1hex(char *digest, char *script, size_t len);
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Utility functions.
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* Perform the SHA1 of the input string. We use this both for hashing script
|
||||||
|
* bodies in order to obtain the Lua function name, and in the implementation
|
||||||
|
* of redis.sha1().
|
||||||
|
*
|
||||||
|
* 'digest' should point to a 41 bytes buffer: 40 for SHA1 converted into an
|
||||||
|
* hexadecimal number, plus 1 byte for null term. */
|
||||||
|
void sha1hex(char *digest, char *script, size_t len) {
|
||||||
|
SHA1_CTX ctx;
|
||||||
|
unsigned char hash[20];
|
||||||
|
char *cset = "0123456789abcdef";
|
||||||
|
int j;
|
||||||
|
|
||||||
|
SHA1Init(&ctx);
|
||||||
|
SHA1Update(&ctx,(unsigned char*)script,len);
|
||||||
|
SHA1Final(hash,&ctx);
|
||||||
|
|
||||||
|
for (j = 0; j < 20; j++) {
|
||||||
|
digest[j*2] = cset[((hash[j]&0xF0)>>4)];
|
||||||
|
digest[j*2+1] = cset[(hash[j]&0xF)];
|
||||||
|
}
|
||||||
|
digest[40] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Redis reply to Lua type conversion functions.
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* Take a Redis reply in the Redis protocol format and convert it into a
|
/* Take a Redis reply in the Redis protocol format and convert it into a
|
||||||
* Lua type. Thanks to this function, and the introduction of not connected
|
* Lua type. Thanks to this function, and the introduction of not connected
|
||||||
@ -201,6 +231,79 @@ void luaSortArray(lua_State *lua) {
|
|||||||
lua_pop(lua,1); /* Stack: array (sorted) */
|
lua_pop(lua,1); /* Stack: array (sorted) */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Lua reply to Redis reply conversion functions.
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
void luaReplyToRedisReply(client *c, lua_State *lua) {
|
||||||
|
int t = lua_type(lua,-1);
|
||||||
|
|
||||||
|
switch(t) {
|
||||||
|
case LUA_TSTRING:
|
||||||
|
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
|
||||||
|
break;
|
||||||
|
case LUA_TBOOLEAN:
|
||||||
|
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.nullbulk);
|
||||||
|
break;
|
||||||
|
case LUA_TNUMBER:
|
||||||
|
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
|
||||||
|
break;
|
||||||
|
case LUA_TTABLE:
|
||||||
|
/* We need to check if it is an array, an error, or a status reply.
|
||||||
|
* Error are returned as a single element table with 'err' field.
|
||||||
|
* Status replies are returned as single element table with 'ok'
|
||||||
|
* field. */
|
||||||
|
lua_pushstring(lua,"err");
|
||||||
|
lua_gettable(lua,-2);
|
||||||
|
t = lua_type(lua,-1);
|
||||||
|
if (t == LUA_TSTRING) {
|
||||||
|
sds err = sdsnew(lua_tostring(lua,-1));
|
||||||
|
sdsmapchars(err,"\r\n"," ",2);
|
||||||
|
addReplySds(c,sdscatprintf(sdsempty(),"-%s\r\n",err));
|
||||||
|
sdsfree(err);
|
||||||
|
lua_pop(lua,2);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pop(lua,1);
|
||||||
|
lua_pushstring(lua,"ok");
|
||||||
|
lua_gettable(lua,-2);
|
||||||
|
t = lua_type(lua,-1);
|
||||||
|
if (t == LUA_TSTRING) {
|
||||||
|
sds ok = sdsnew(lua_tostring(lua,-1));
|
||||||
|
sdsmapchars(ok,"\r\n"," ",2);
|
||||||
|
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
|
||||||
|
sdsfree(ok);
|
||||||
|
lua_pop(lua,1);
|
||||||
|
} else {
|
||||||
|
void *replylen = addDeferredMultiBulkLength(c);
|
||||||
|
int j = 1, mbulklen = 0;
|
||||||
|
|
||||||
|
lua_pop(lua,1); /* Discard the 'ok' field value we popped */
|
||||||
|
while(1) {
|
||||||
|
lua_pushnumber(lua,j++);
|
||||||
|
lua_gettable(lua,-2);
|
||||||
|
t = lua_type(lua,-1);
|
||||||
|
if (t == LUA_TNIL) {
|
||||||
|
lua_pop(lua,1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
luaReplyToRedisReply(c, lua);
|
||||||
|
mbulklen++;
|
||||||
|
}
|
||||||
|
setDeferredMultiBulkLength(c,replylen,mbulklen);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
addReply(c,shared.nullbulk);
|
||||||
|
}
|
||||||
|
lua_pop(lua,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Lua redis.* functions implementations.
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
#define LUA_CMD_OBJCACHE_SIZE 32
|
#define LUA_CMD_OBJCACHE_SIZE 32
|
||||||
#define LUA_CMD_OBJCACHE_MAX_LEN 64
|
#define LUA_CMD_OBJCACHE_MAX_LEN 64
|
||||||
int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||||
@ -608,29 +711,9 @@ int luaLogCommand(lua_State *lua) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
/* ---------------------------------------------------------------------------
|
||||||
long long elapsed;
|
* Lua engine initialization and reset.
|
||||||
UNUSED(ar);
|
* ------------------------------------------------------------------------- */
|
||||||
UNUSED(lua);
|
|
||||||
|
|
||||||
elapsed = mstime() - server.lua_time_start;
|
|
||||||
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
|
||||||
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
|
||||||
server.lua_timedout = 1;
|
|
||||||
/* Once the script timeouts we reenter the event loop to permit others
|
|
||||||
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
|
|
||||||
* we need to mask the client executing the script from the event loop.
|
|
||||||
* If we don't do that the client may disconnect and could no longer be
|
|
||||||
* here when the EVAL command will return. */
|
|
||||||
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
|
|
||||||
}
|
|
||||||
if (server.lua_timedout) processEventsWhileBlocked();
|
|
||||||
if (server.lua_kill) {
|
|
||||||
serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL.");
|
|
||||||
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
|
|
||||||
lua_error(lua);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void luaLoadLib(lua_State *lua, const char *libname, lua_CFunction luafunc) {
|
void luaLoadLib(lua_State *lua, const char *libname, lua_CFunction luafunc) {
|
||||||
lua_pushcfunction(lua, luafunc);
|
lua_pushcfunction(lua, luafunc);
|
||||||
@ -872,94 +955,6 @@ void scriptingReset(void) {
|
|||||||
scriptingInit();
|
scriptingInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Perform the SHA1 of the input string. We use this both for hashing script
|
|
||||||
* bodies in order to obtain the Lua function name, and in the implementation
|
|
||||||
* of redis.sha1().
|
|
||||||
*
|
|
||||||
* 'digest' should point to a 41 bytes buffer: 40 for SHA1 converted into an
|
|
||||||
* hexadecimal number, plus 1 byte for null term. */
|
|
||||||
void sha1hex(char *digest, char *script, size_t len) {
|
|
||||||
SHA1_CTX ctx;
|
|
||||||
unsigned char hash[20];
|
|
||||||
char *cset = "0123456789abcdef";
|
|
||||||
int j;
|
|
||||||
|
|
||||||
SHA1Init(&ctx);
|
|
||||||
SHA1Update(&ctx,(unsigned char*)script,len);
|
|
||||||
SHA1Final(hash,&ctx);
|
|
||||||
|
|
||||||
for (j = 0; j < 20; j++) {
|
|
||||||
digest[j*2] = cset[((hash[j]&0xF0)>>4)];
|
|
||||||
digest[j*2+1] = cset[(hash[j]&0xF)];
|
|
||||||
}
|
|
||||||
digest[40] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
void luaReplyToRedisReply(client *c, lua_State *lua) {
|
|
||||||
int t = lua_type(lua,-1);
|
|
||||||
|
|
||||||
switch(t) {
|
|
||||||
case LUA_TSTRING:
|
|
||||||
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
|
|
||||||
break;
|
|
||||||
case LUA_TBOOLEAN:
|
|
||||||
addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.nullbulk);
|
|
||||||
break;
|
|
||||||
case LUA_TNUMBER:
|
|
||||||
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
|
|
||||||
break;
|
|
||||||
case LUA_TTABLE:
|
|
||||||
/* We need to check if it is an array, an error, or a status reply.
|
|
||||||
* Error are returned as a single element table with 'err' field.
|
|
||||||
* Status replies are returned as single element table with 'ok'
|
|
||||||
* field. */
|
|
||||||
lua_pushstring(lua,"err");
|
|
||||||
lua_gettable(lua,-2);
|
|
||||||
t = lua_type(lua,-1);
|
|
||||||
if (t == LUA_TSTRING) {
|
|
||||||
sds err = sdsnew(lua_tostring(lua,-1));
|
|
||||||
sdsmapchars(err,"\r\n"," ",2);
|
|
||||||
addReplySds(c,sdscatprintf(sdsempty(),"-%s\r\n",err));
|
|
||||||
sdsfree(err);
|
|
||||||
lua_pop(lua,2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lua_pop(lua,1);
|
|
||||||
lua_pushstring(lua,"ok");
|
|
||||||
lua_gettable(lua,-2);
|
|
||||||
t = lua_type(lua,-1);
|
|
||||||
if (t == LUA_TSTRING) {
|
|
||||||
sds ok = sdsnew(lua_tostring(lua,-1));
|
|
||||||
sdsmapchars(ok,"\r\n"," ",2);
|
|
||||||
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
|
|
||||||
sdsfree(ok);
|
|
||||||
lua_pop(lua,1);
|
|
||||||
} else {
|
|
||||||
void *replylen = addDeferredMultiBulkLength(c);
|
|
||||||
int j = 1, mbulklen = 0;
|
|
||||||
|
|
||||||
lua_pop(lua,1); /* Discard the 'ok' field value we popped */
|
|
||||||
while(1) {
|
|
||||||
lua_pushnumber(lua,j++);
|
|
||||||
lua_gettable(lua,-2);
|
|
||||||
t = lua_type(lua,-1);
|
|
||||||
if (t == LUA_TNIL) {
|
|
||||||
lua_pop(lua,1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
luaReplyToRedisReply(c, lua);
|
|
||||||
mbulklen++;
|
|
||||||
}
|
|
||||||
setDeferredMultiBulkLength(c,replylen,mbulklen);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
addReply(c,shared.nullbulk);
|
|
||||||
}
|
|
||||||
lua_pop(lua,1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set an array of Redis String Objects as a Lua array (table) stored into a
|
/* Set an array of Redis String Objects as a Lua array (table) stored into a
|
||||||
* global variable. */
|
* global variable. */
|
||||||
void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
|
void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
|
||||||
@ -973,6 +968,53 @@ void luaSetGlobalArray(lua_State *lua, char *var, robj **elev, int elec) {
|
|||||||
lua_setglobal(lua,var);
|
lua_setglobal(lua,var);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* Redis provided math.random
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* We replace math.random() with our implementation that is not affected
|
||||||
|
* by specific libc random() implementations and will output the same sequence
|
||||||
|
* (for the same seed) in every arch. */
|
||||||
|
|
||||||
|
/* The following implementation is the one shipped with Lua itself but with
|
||||||
|
* rand() replaced by redisLrand48(). */
|
||||||
|
int redis_math_random (lua_State *L) {
|
||||||
|
/* the `%' avoids the (rare) case of r==1, and is needed also because on
|
||||||
|
some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */
|
||||||
|
lua_Number r = (lua_Number)(redisLrand48()%REDIS_LRAND48_MAX) /
|
||||||
|
(lua_Number)REDIS_LRAND48_MAX;
|
||||||
|
switch (lua_gettop(L)) { /* check number of arguments */
|
||||||
|
case 0: { /* no arguments */
|
||||||
|
lua_pushnumber(L, r); /* Number between 0 and 1 */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: { /* only upper limit */
|
||||||
|
int u = luaL_checkint(L, 1);
|
||||||
|
luaL_argcheck(L, 1<=u, 1, "interval is empty");
|
||||||
|
lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2: { /* lower and upper limits */
|
||||||
|
int l = luaL_checkint(L, 1);
|
||||||
|
int u = luaL_checkint(L, 2);
|
||||||
|
luaL_argcheck(L, l<=u, 2, "interval is empty");
|
||||||
|
lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: return luaL_error(L, "wrong number of arguments");
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int redis_math_randomseed (lua_State *L) {
|
||||||
|
redisSrand48(luaL_checkint(L, 1));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------------
|
||||||
|
* EVAL and SCRIPT commands implementation
|
||||||
|
* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* Define a lua function with the specified function name and body.
|
/* Define a lua function with the specified function name and body.
|
||||||
* The function name musts be a 42 characters long string, since all the
|
* The function name musts be a 42 characters long string, since all the
|
||||||
* functions we defined in the Lua context are in the form:
|
* functions we defined in the Lua context are in the form:
|
||||||
@ -1018,6 +1060,31 @@ int luaCreateFunction(client *c, lua_State *lua, char *funcname, robj *body) {
|
|||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
|
||||||
|
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
|
||||||
|
long long elapsed;
|
||||||
|
UNUSED(ar);
|
||||||
|
UNUSED(lua);
|
||||||
|
|
||||||
|
elapsed = mstime() - server.lua_time_start;
|
||||||
|
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
|
||||||
|
serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
|
||||||
|
server.lua_timedout = 1;
|
||||||
|
/* Once the script timeouts we reenter the event loop to permit others
|
||||||
|
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
|
||||||
|
* we need to mask the client executing the script from the event loop.
|
||||||
|
* If we don't do that the client may disconnect and could no longer be
|
||||||
|
* here when the EVAL command will return. */
|
||||||
|
aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
|
||||||
|
}
|
||||||
|
if (server.lua_timedout) processEventsWhileBlocked();
|
||||||
|
if (server.lua_kill) {
|
||||||
|
serverLog(LL_WARNING,"Lua script killed by user with SCRIPT KILL.");
|
||||||
|
lua_pushstring(lua,"Script killed by user with SCRIPT KILL...");
|
||||||
|
lua_error(lua);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void evalGenericCommand(client *c, int evalsha) {
|
void evalGenericCommand(client *c, int evalsha) {
|
||||||
lua_State *lua = server.lua;
|
lua_State *lua = server.lua;
|
||||||
char funcname[43];
|
char funcname[43];
|
||||||
@ -1221,49 +1288,6 @@ void evalShaCommand(client *c) {
|
|||||||
evalGenericCommand(c,1);
|
evalGenericCommand(c,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We replace math.random() with our implementation that is not affected
|
|
||||||
* by specific libc random() implementations and will output the same sequence
|
|
||||||
* (for the same seed) in every arch. */
|
|
||||||
|
|
||||||
/* The following implementation is the one shipped with Lua itself but with
|
|
||||||
* rand() replaced by redisLrand48(). */
|
|
||||||
int redis_math_random (lua_State *L) {
|
|
||||||
/* the `%' avoids the (rare) case of r==1, and is needed also because on
|
|
||||||
some systems (SunOS!) `rand()' may return a value larger than RAND_MAX */
|
|
||||||
lua_Number r = (lua_Number)(redisLrand48()%REDIS_LRAND48_MAX) /
|
|
||||||
(lua_Number)REDIS_LRAND48_MAX;
|
|
||||||
switch (lua_gettop(L)) { /* check number of arguments */
|
|
||||||
case 0: { /* no arguments */
|
|
||||||
lua_pushnumber(L, r); /* Number between 0 and 1 */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1: { /* only upper limit */
|
|
||||||
int u = luaL_checkint(L, 1);
|
|
||||||
luaL_argcheck(L, 1<=u, 1, "interval is empty");
|
|
||||||
lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2: { /* lower and upper limits */
|
|
||||||
int l = luaL_checkint(L, 1);
|
|
||||||
int u = luaL_checkint(L, 2);
|
|
||||||
luaL_argcheck(L, l<=u, 2, "interval is empty");
|
|
||||||
lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: return luaL_error(L, "wrong number of arguments");
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int redis_math_randomseed (lua_State *L) {
|
|
||||||
redisSrand48(luaL_checkint(L, 1));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
|
||||||
* SCRIPT command for script environment introspection and control
|
|
||||||
* ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
void scriptCommand(client *c) {
|
void scriptCommand(client *c) {
|
||||||
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"flush")) {
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"flush")) {
|
||||||
scriptingReset();
|
scriptingReset();
|
||||||
@ -1311,3 +1335,5 @@ void scriptCommand(client *c) {
|
|||||||
addReplyError(c, "Unknown SCRIPT subcommand or wrong # of args.");
|
addReplyError(c, "Unknown SCRIPT subcommand or wrong # of args.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -250,6 +250,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||||||
#define CLIENT_REPLY_OFF (1<<22) /* Don't send replies to client. */
|
#define CLIENT_REPLY_OFF (1<<22) /* Don't send replies to client. */
|
||||||
#define CLIENT_REPLY_SKIP_NEXT (1<<23) /* Set CLIENT_REPLY_SKIP for next cmd */
|
#define CLIENT_REPLY_SKIP_NEXT (1<<23) /* Set CLIENT_REPLY_SKIP for next cmd */
|
||||||
#define CLIENT_REPLY_SKIP (1<<24) /* Don't send just this reply. */
|
#define CLIENT_REPLY_SKIP (1<<24) /* Don't send just this reply. */
|
||||||
|
#define CLIENT_LUA_DEBUG (1<<25) /* Run EVAL in debug mode. */
|
||||||
|
|
||||||
/* Client block type (btype field in client structure)
|
/* Client block type (btype field in client structure)
|
||||||
* if CLIENT_BLOCKED flag is set. */
|
* if CLIENT_BLOCKED flag is set. */
|
||||||
|
Loading…
Reference in New Issue
Block a user