mirror of
https://github.com/fluencelabs/redis
synced 2025-03-23 02:50:50 +00:00
Lua debugger: foundations implemented.
This commit is contained in:
parent
7cfdccd94e
commit
c494db89b5
@ -218,8 +218,9 @@ void stopAppendOnly(void) {
|
||||
|
||||
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
|
||||
(long) server.aof_child_pid);
|
||||
if (kill(server.aof_child_pid,SIGUSR1) != -1)
|
||||
wait3(&statloc,0,NULL);
|
||||
if (kill(server.aof_child_pid,SIGUSR1) != -1) {
|
||||
while(wait3(&statloc,0,NULL) != server.aof_child_pid);
|
||||
}
|
||||
/* reset the buffer accumulating changes while the child saves */
|
||||
aofRewriteBufferReset();
|
||||
aofRemoveTempFile(server.aof_child_pid);
|
||||
|
@ -111,6 +111,7 @@ static struct config {
|
||||
sds mb_delim;
|
||||
char prompt[128];
|
||||
char *eval;
|
||||
int eval_ldb;
|
||||
int last_cmd_type;
|
||||
} config;
|
||||
|
||||
@ -141,6 +142,7 @@ static long long mstime(void) {
|
||||
static void cliRefreshPrompt(void) {
|
||||
int len;
|
||||
|
||||
if (config.eval_ldb) return;
|
||||
if (config.hostsocket != NULL)
|
||||
len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
|
||||
config.hostsocket);
|
||||
@ -822,6 +824,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.bigkeys = 1;
|
||||
} else if (!strcmp(argv[i],"--eval") && !lastarg) {
|
||||
config.eval = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--ldb")) {
|
||||
config.eval_ldb = 1;
|
||||
} else if (!strcmp(argv[i],"-c")) {
|
||||
config.cluster_mode = 1;
|
||||
} else if (!strcmp(argv[i],"-d") && !lastarg) {
|
||||
@ -904,6 +908,7 @@ static void usage(void) {
|
||||
" --intrinsic-latency <sec> Run a test to measure intrinsic system latency.\n"
|
||||
" The test will run for the specified amount of seconds.\n"
|
||||
" --eval <file> Send an EVAL command using the Lua script at <file>.\n"
|
||||
" --ldb Used with --eval enable the Redis Lua debugger.\n"
|
||||
" --help Output this help and exit.\n"
|
||||
" --version Output version and exit.\n"
|
||||
"\n"
|
||||
@ -1071,6 +1076,12 @@ static int evalMode(int argc, char **argv) {
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
/* If we are debugging a script, enable the Lua debugger. */
|
||||
if (config.eval_ldb) {
|
||||
redisReply *reply = redisCommand(context, "SCRIPT DEBUG yes");
|
||||
if (reply) freeReplyObject(reply);
|
||||
}
|
||||
|
||||
/* Create our argument vector */
|
||||
argv2 = zmalloc(sizeof(sds)*(argc+3));
|
||||
argv2[0] = sdsnew("EVAL");
|
||||
@ -1086,7 +1097,12 @@ static int evalMode(int argc, char **argv) {
|
||||
argv2[2] = sdscatprintf(sdsempty(),"%d",keys);
|
||||
|
||||
/* Call it */
|
||||
return issueCommand(argc+3-got_comma, argv2);
|
||||
int retval = issueCommand(argc+3-got_comma, argv2);
|
||||
if (config.eval_ldb) {
|
||||
strncpy(config.prompt,"lua debugger> ",sizeof(config.prompt));
|
||||
repl();
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*------------------------------------------------------------------------------
|
||||
@ -2210,6 +2226,7 @@ int main(int argc, char **argv) {
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.eval = NULL;
|
||||
config.eval_ldb = 0;
|
||||
config.last_cmd_type = -1;
|
||||
|
||||
spectrum_palette = spectrum_palette_color;
|
||||
|
198
src/scripting.c
198
src/scripting.c
@ -45,6 +45,25 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
|
||||
char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply);
|
||||
int redis_math_random (lua_State *L);
|
||||
int redis_math_randomseed (lua_State *L);
|
||||
void ldbInit(void);
|
||||
void ldbDisable(client *c);
|
||||
void ldbEnable(client *c);
|
||||
void evalGenericCommandWithDebugging(client *c, int evalsha);
|
||||
void luaLdbLineHook(lua_State *lua, lua_Debug *ar);
|
||||
|
||||
/* Debugger shared state is stored inside this global structure. */
|
||||
#define LDB_BREAKPOINTS_MAX 64
|
||||
struct ldbState {
|
||||
int fd; /* Socket of the debugging client. */
|
||||
int active; /* Are we debugging EVAL right now? */
|
||||
int forked; /* Is this a fork()ed debugging session? */
|
||||
list *logs; /* List of messages to send to the client. */
|
||||
list *traces; /* Messages about Redis commands executed since last stop.*/
|
||||
int bp[LDB_BREAKPOINTS_MAX]; /* An array of breakpoints line numbers. */
|
||||
int bpcount; /* Number of valid entries inside bp. */
|
||||
int step; /* Stop at next line ragardless of breakpoints. */
|
||||
robj *src; /* Lua script source code. */
|
||||
} ldb;
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Utility functions.
|
||||
@ -821,6 +840,7 @@ void scriptingInit(int setup) {
|
||||
server.lua_timedout = 0;
|
||||
server.lua_always_replicate_commands = 0; /* Only DEBUG can change it.*/
|
||||
server.lua_time_limit = LUA_SCRIPT_TIME_LIMIT;
|
||||
ldbInit();
|
||||
}
|
||||
|
||||
luaLoadLibraries(lua);
|
||||
@ -1038,15 +1058,6 @@ int redis_math_randomseed (lua_State *L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* LDB: Redis Lua debugging facilities
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Enable debug mode of Lua scripts for this client. */
|
||||
void ldbEnable(client *c) {
|
||||
c->flags |= CLIENT_LUA_DEBUG;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* EVAL and SCRIPT commands implementation
|
||||
* ------------------------------------------------------------------------- */
|
||||
@ -1214,13 +1225,21 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
/* Set a hook in order to be able to stop the script execution if it
|
||||
* is running for too much time.
|
||||
* We set the hook only if the time limit is enabled as the hook will
|
||||
* make the Lua script execution slower. */
|
||||
* make the Lua script execution slower.
|
||||
*
|
||||
* If we are debugging, we set instead a "line" hook so that the
|
||||
* debugger is call-back at every line executed by the script. */
|
||||
server.lua_caller = c;
|
||||
server.lua_time_start = mstime();
|
||||
server.lua_kill = 0;
|
||||
if (server.lua_time_limit > 0 && server.masterhost == NULL) {
|
||||
if (server.lua_time_limit > 0 && server.masterhost == NULL &&
|
||||
ldb.active == 0)
|
||||
{
|
||||
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
|
||||
delhook = 1;
|
||||
} else if (ldb.active) {
|
||||
lua_sethook(server.lua,luaLdbLineHook,LUA_MASKLINE,0);
|
||||
delhook = 1;
|
||||
}
|
||||
|
||||
/* At this point whether this script was never seen before or if it was
|
||||
@ -1229,7 +1248,7 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
err = lua_pcall(lua,0,1,-2);
|
||||
|
||||
/* Perform some cleanup that we need to do both on error and success. */
|
||||
if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */
|
||||
if (delhook) lua_sethook(lua,NULL,0,0); /* Disable hook */
|
||||
if (server.lua_timedout) {
|
||||
server.lua_timedout = 0;
|
||||
/* Restore the readable handler that was unregistered when the
|
||||
@ -1308,7 +1327,10 @@ void evalGenericCommand(client *c, int evalsha) {
|
||||
}
|
||||
|
||||
void evalCommand(client *c) {
|
||||
evalGenericCommand(c,0);
|
||||
if (!(c->flags & CLIENT_LUA_DEBUG))
|
||||
evalGenericCommand(c,0);
|
||||
else
|
||||
evalGenericCommandWithDebugging(c,0);
|
||||
}
|
||||
|
||||
void evalShaCommand(client *c) {
|
||||
@ -1320,7 +1342,12 @@ void evalShaCommand(client *c) {
|
||||
addReply(c, shared.noscripterr);
|
||||
return;
|
||||
}
|
||||
evalGenericCommand(c,1);
|
||||
if (!(c->flags & CLIENT_LUA_DEBUG))
|
||||
evalGenericCommand(c,1);
|
||||
else {
|
||||
addReplyError(c,"Please use EVAL instead of EVALSHA for debugging");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void scriptCommand(client *c) {
|
||||
@ -1366,12 +1393,149 @@ void scriptCommand(client *c) {
|
||||
server.lua_kill = 1;
|
||||
addReply(c,shared.ok);
|
||||
}
|
||||
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"debug")) {
|
||||
ldbEnable(c);
|
||||
addReply(c,shared.ok);
|
||||
} else if (c->argc == 3 && !strcasecmp(c->argv[1]->ptr,"debug")) {
|
||||
if (clientHasPendingReplies(c)) {
|
||||
addReplyError(c,"SCRIPT DEBUG must be called outside a pipeline");
|
||||
return;
|
||||
}
|
||||
if (!strcasecmp(c->argv[2]->ptr,"no")) {
|
||||
ldbDisable(c);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"yes")) {
|
||||
ldbEnable(c);
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"sync")) {
|
||||
ldbEnable(c);
|
||||
addReply(c,shared.ok);
|
||||
c->flags |= CLIENT_LUA_DEBUG_SYNC;
|
||||
} else {
|
||||
addReplyError(c,"Use SCRIPT DEBUG yes/async/no");
|
||||
}
|
||||
} else {
|
||||
addReplyError(c, "Unknown SCRIPT subcommand or wrong # of args.");
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* LDB: Redis Lua debugging facilities
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Initialize Lua debugger data structures. */
|
||||
void ldbInit(void) {
|
||||
ldb.fd = -1;
|
||||
ldb.active = 0;
|
||||
ldb.logs = listCreate();
|
||||
ldb.traces = listCreate();
|
||||
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
|
||||
listSetFreeMethod(ldb.traces, (void (*)(void*))sdsfree);
|
||||
ldb.src = NULL;
|
||||
}
|
||||
|
||||
/* Remove all the pending messages in the specified list. */
|
||||
void ldbFlushLog(list *log) {
|
||||
listNode *ln;
|
||||
|
||||
while((ln = listFirst(log)) != NULL)
|
||||
listDelNode(log,ln);
|
||||
}
|
||||
|
||||
/* Enable debug mode of Lua scripts for this client. */
|
||||
void ldbEnable(client *c) {
|
||||
c->flags |= CLIENT_LUA_DEBUG;
|
||||
ldbFlushLog(ldb.logs);
|
||||
ldbFlushLog(ldb.traces);
|
||||
ldb.fd = c->fd;
|
||||
ldb.step = 0;
|
||||
ldb.bpcount = 0;
|
||||
}
|
||||
|
||||
void ldbDisable(client *c) {
|
||||
c->flags &= ~(CLIENT_LUA_DEBUG|CLIENT_LUA_DEBUG_SYNC);
|
||||
}
|
||||
|
||||
/* Append a log entry to the specified LDB log. */
|
||||
void ldbLog(list *log, sds entry) {
|
||||
listAddNodeTail(log,entry);
|
||||
}
|
||||
|
||||
/* Send ldb.logs and ldb.traces to the debugging client as a multi-bulk
|
||||
* reply consisting of simple strings. Log entries which include newlines
|
||||
* have them replaced with spaces. The entries sent are also consumed. */
|
||||
void ldbWriteLogs(void) {
|
||||
}
|
||||
|
||||
/* Start a debugging session before calling EVAL implementation.
|
||||
* The techique we use is to capture the client socket file descriptor,
|
||||
* in order to perform direct I/O with it from within Lua hooks. This
|
||||
* way we don't have to re-enter Redis in order to handle I/O.
|
||||
*
|
||||
* The function returns 1 if the caller should proceed to call EVAL,
|
||||
* and 0 if instead the caller should abort the operation (this happens
|
||||
* for the parent in a forked session, since it's up to the children
|
||||
* to continue, or when fork returned an error).
|
||||
*
|
||||
* The caller should call ldbEndSession() only if ldbStartSession()
|
||||
* returned 1. */
|
||||
int ldbStartSession(client *c) {
|
||||
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
|
||||
if (ldb.forked) {
|
||||
pid_t cp = fork();
|
||||
if (cp == -1) {
|
||||
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
|
||||
return 0;
|
||||
} else if (cp == 0) {
|
||||
/* Child */
|
||||
serverLog(LL_WARNING,"Redis forked for debugging eval");
|
||||
closeListeningSockets(0);
|
||||
} else {
|
||||
/* Parent */
|
||||
freeClientAsync(c); /* Close the client in the parent side. */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup our debugging session. */
|
||||
anetBlock(NULL,ldb.fd);
|
||||
ldb.active = 1;
|
||||
ldb.src = c->argv[1]; /* First argument of EVAL is the script itself. */
|
||||
incrRefCount(ldb.src);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* End a debugging session after the EVAL call with debugging enabled
|
||||
* returned. */
|
||||
void ldbEndSession(client *c) {
|
||||
/* If it's a fork()ed session, we just exit. */
|
||||
if (ldb.forked) {
|
||||
writeToClient(c->fd, c, 0);
|
||||
serverLog(LL_WARNING,"Lua debugging session child exiting");
|
||||
exitFromChild(0);
|
||||
}
|
||||
|
||||
/* Otherwise let's restore client's state. */
|
||||
anetNonBlock(NULL,ldb.fd);
|
||||
ldb.active = 0;
|
||||
decrRefCount(ldb.src);
|
||||
}
|
||||
|
||||
/* Wrapper for EVAL / EVALSHA that enables debugging, and makes sure
|
||||
* that when EVAL returns, whatever happened, the session is ended. */
|
||||
void evalGenericCommandWithDebugging(client *c, int evalsha) {
|
||||
if (ldbStartSession(c)) {
|
||||
evalGenericCommand(c,evalsha);
|
||||
ldbEndSession(c);
|
||||
} else {
|
||||
ldbDisable(c);
|
||||
}
|
||||
}
|
||||
|
||||
/* This is the core of our Lua debugger, called each time Lua is about
|
||||
* to start executing a new line. */
|
||||
void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
||||
lua_getstack(lua,0,ar);
|
||||
lua_getinfo(lua,"Sl",ar);
|
||||
if(strstr(ar->short_src,"user_script") != NULL)
|
||||
printf("%s %d\n", ar->short_src, (int) ar->currentline);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1202,7 +1202,8 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
backgroundRewriteDoneHandler(exitcode,bysignal);
|
||||
} else {
|
||||
serverLog(LL_WARNING,
|
||||
"Warning, detected child with unmatched pid: %ld",
|
||||
"Warning, detected child with unmatched pid: %ld"
|
||||
" (EVAL forked debugging session?)",
|
||||
(long)pid);
|
||||
}
|
||||
updateDictResizePolicy();
|
||||
|
@ -251,6 +251,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
||||
#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_LUA_DEBUG (1<<25) /* Run EVAL in debug mode. */
|
||||
#define CLIENT_LUA_DEBUG_SYNC (1<<26) /* EVAL debugging without fork() */
|
||||
|
||||
/* Client block type (btype field in client structure)
|
||||
* if CLIENT_BLOCKED flag is set. */
|
||||
@ -1142,6 +1143,7 @@ int processEventsWhileBlocked(void);
|
||||
int handleClientsWithPendingWrites(void);
|
||||
int clientHasPendingReplies(client *c);
|
||||
void unlinkClient(client *c);
|
||||
int writeToClient(int fd, client *c, int handler_installed);
|
||||
|
||||
#ifdef __GNUC__
|
||||
void addReplyErrorFormat(client *c, const char *fmt, ...)
|
||||
|
Loading…
x
Reference in New Issue
Block a user