Permalink
Browse files

Lua debugger: foundations implemented.

  • Loading branch information...
antirez committed Nov 6, 2015
1 parent 7cfdccd commit c494db89b5c2ef34758f599ee46ac7265782ad77
Showing with 206 additions and 21 deletions.
  1. +3 −2 src/aof.c
  2. +18 −1 src/redis-cli.c
  3. +181 −17 src/scripting.c
  4. +2 −1 src/server.c
  5. +2 −0 src/server.h
View
@@ -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);
View
@@ -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;
View
@@ -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);
}
View
@@ -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();
View
@@ -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, ...)

0 comments on commit c494db8

Please sign in to comment.