mirror of
https://github.com/fluencelabs/redis
synced 2025-03-19 17:10:50 +00:00
Lua debugger: initial REPL.
This commit is contained in:
parent
c494db89b5
commit
d3d1fa9437
@ -375,8 +375,15 @@ static int cliSelect(void) {
|
|||||||
* even if there is already a connected socket. */
|
* even if there is already a connected socket. */
|
||||||
static int cliConnect(int force) {
|
static int cliConnect(int force) {
|
||||||
if (context == NULL || force) {
|
if (context == NULL || force) {
|
||||||
if (context != NULL)
|
if (context != NULL) {
|
||||||
redisFree(context);
|
redisFree(context);
|
||||||
|
/* Disconnection from the server signals end of EVAL
|
||||||
|
* debugging session. */
|
||||||
|
if (config.eval_ldb) {
|
||||||
|
config.eval_ldb = 0;
|
||||||
|
cliRefreshPrompt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.hostsocket == NULL) {
|
if (config.hostsocket == NULL) {
|
||||||
context = redisConnect(config.hostip,config.hostport);
|
context = redisConnect(config.hostip,config.hostport);
|
||||||
@ -637,7 +644,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
|||||||
size_t *argvlen;
|
size_t *argvlen;
|
||||||
int j, output_raw;
|
int j, output_raw;
|
||||||
|
|
||||||
if (!strcasecmp(command,"help") || !strcasecmp(command,"?")) {
|
if (!config.eval_ldb && /* In debugging mode, let's pass "help" to Redis. */
|
||||||
|
(!strcasecmp(command,"help") || !strcasecmp(command,"?"))) {
|
||||||
cliOutputHelp(--argc, ++argv);
|
cliOutputHelp(--argc, ++argv);
|
||||||
return REDIS_OK;
|
return REDIS_OK;
|
||||||
}
|
}
|
||||||
|
172
src/scripting.c
172
src/scripting.c
@ -62,7 +62,9 @@ struct ldbState {
|
|||||||
int bp[LDB_BREAKPOINTS_MAX]; /* An array of breakpoints line numbers. */
|
int bp[LDB_BREAKPOINTS_MAX]; /* An array of breakpoints line numbers. */
|
||||||
int bpcount; /* Number of valid entries inside bp. */
|
int bpcount; /* Number of valid entries inside bp. */
|
||||||
int step; /* Stop at next line ragardless of breakpoints. */
|
int step; /* Stop at next line ragardless of breakpoints. */
|
||||||
robj *src; /* Lua script source code. */
|
sds *src; /* Lua script source code split by line. */
|
||||||
|
int lines; /* Number of lines in 'src'. */
|
||||||
|
sds cbuf; /* Debugger client command buffer. */
|
||||||
} ldb;
|
} ldb;
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------------
|
/* ---------------------------------------------------------------------------
|
||||||
@ -1424,10 +1426,10 @@ void ldbInit(void) {
|
|||||||
ldb.fd = -1;
|
ldb.fd = -1;
|
||||||
ldb.active = 0;
|
ldb.active = 0;
|
||||||
ldb.logs = listCreate();
|
ldb.logs = listCreate();
|
||||||
ldb.traces = listCreate();
|
|
||||||
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
|
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
|
||||||
listSetFreeMethod(ldb.traces, (void (*)(void*))sdsfree);
|
|
||||||
ldb.src = NULL;
|
ldb.src = NULL;
|
||||||
|
ldb.lines = 0;
|
||||||
|
ldb.cbuf = sdsempty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove all the pending messages in the specified list. */
|
/* Remove all the pending messages in the specified list. */
|
||||||
@ -1442,10 +1444,11 @@ void ldbFlushLog(list *log) {
|
|||||||
void ldbEnable(client *c) {
|
void ldbEnable(client *c) {
|
||||||
c->flags |= CLIENT_LUA_DEBUG;
|
c->flags |= CLIENT_LUA_DEBUG;
|
||||||
ldbFlushLog(ldb.logs);
|
ldbFlushLog(ldb.logs);
|
||||||
ldbFlushLog(ldb.traces);
|
|
||||||
ldb.fd = c->fd;
|
ldb.fd = c->fd;
|
||||||
ldb.step = 0;
|
ldb.step = 1;
|
||||||
ldb.bpcount = 0;
|
ldb.bpcount = 0;
|
||||||
|
sdsfree(ldb.cbuf);
|
||||||
|
ldb.cbuf = sdsempty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ldbDisable(client *c) {
|
void ldbDisable(client *c) {
|
||||||
@ -1453,14 +1456,25 @@ void ldbDisable(client *c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Append a log entry to the specified LDB log. */
|
/* Append a log entry to the specified LDB log. */
|
||||||
void ldbLog(list *log, sds entry) {
|
void ldbLog(sds entry) {
|
||||||
listAddNodeTail(log,entry);
|
listAddNodeTail(ldb.logs,entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send ldb.logs and ldb.traces to the debugging client as a multi-bulk
|
/* Send ldb.logs to the debugging client as a multi-bulk reply
|
||||||
* reply consisting of simple strings. Log entries which include newlines
|
* consisting of simple strings. Log entries which include newlines have them
|
||||||
* have them replaced with spaces. The entries sent are also consumed. */
|
* replaced with spaces. The entries sent are also consumed. */
|
||||||
void ldbWriteLogs(void) {
|
void ldbSendLogs(void) {
|
||||||
|
sds proto = sdsempty();
|
||||||
|
proto = sdscatfmt(proto,"*%i\r\n", (int)listLength(ldb.logs));
|
||||||
|
while(listLength(ldb.logs)) {
|
||||||
|
listNode *ln = listFirst(ldb.logs);
|
||||||
|
proto = sdscatlen(proto,"+",1);
|
||||||
|
sdsmapchars(ln->value,"\r\n"," ",2);
|
||||||
|
proto = sdscatsds(proto,ln->value);
|
||||||
|
proto = sdscatlen(proto,"\r\n",2);
|
||||||
|
listDelNode(ldb.logs,ln);
|
||||||
|
}
|
||||||
|
write(ldb.fd,proto,sdslen(proto));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start a debugging session before calling EVAL implementation.
|
/* Start a debugging session before calling EVAL implementation.
|
||||||
@ -1495,9 +1509,12 @@ int ldbStartSession(client *c) {
|
|||||||
|
|
||||||
/* Setup our debugging session. */
|
/* Setup our debugging session. */
|
||||||
anetBlock(NULL,ldb.fd);
|
anetBlock(NULL,ldb.fd);
|
||||||
|
anetSendTimeout(NULL,ldb.fd,5000);
|
||||||
ldb.active = 1;
|
ldb.active = 1;
|
||||||
ldb.src = c->argv[1]; /* First argument of EVAL is the script itself. */
|
/* First argument of EVAL is the script itself. We split it into different
|
||||||
incrRefCount(ldb.src);
|
* lines since this is the way the debugger accesses the source code. */
|
||||||
|
sds srcstring = c->argv[1]->ptr;
|
||||||
|
ldb.src = sdssplitlen(srcstring,sdslen(srcstring),"\n",1,&ldb.lines);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1513,8 +1530,16 @@ void ldbEndSession(client *c) {
|
|||||||
|
|
||||||
/* Otherwise let's restore client's state. */
|
/* Otherwise let's restore client's state. */
|
||||||
anetNonBlock(NULL,ldb.fd);
|
anetNonBlock(NULL,ldb.fd);
|
||||||
|
anetSendTimeout(NULL,ldb.fd,0);
|
||||||
|
|
||||||
|
/* Close the client connectin after sending the final EVAL reply
|
||||||
|
* in order to signal the end of the debugging session. */
|
||||||
|
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
|
||||||
|
|
||||||
|
/* Cleanup. */
|
||||||
|
sdsfreesplitres(ldb.src,ldb.lines);
|
||||||
|
ldb.lines = 0;
|
||||||
ldb.active = 0;
|
ldb.active = 0;
|
||||||
decrRefCount(ldb.src);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wrapper for EVAL / EVALSHA that enables debugging, and makes sure
|
/* Wrapper for EVAL / EVALSHA that enables debugging, and makes sure
|
||||||
@ -1528,14 +1553,125 @@ void evalGenericCommandWithDebugging(client *c, int evalsha) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return a pointer to ldb.src source code line, considering line to be
|
||||||
|
* one-based, and returning a special string for out of range lines. */
|
||||||
|
char *ldbGetSourceLine(int line) {
|
||||||
|
int idx = line-1;
|
||||||
|
if (idx < 0 || idx >= ldb.lines) return "<out of range source code line>";
|
||||||
|
return ldb.src[line];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expect a valid multi-bulk command in the debugging client query buffer.
|
||||||
|
* On success the command is parsed and returned as an array of SDS strings,
|
||||||
|
* otherwise NULL is returned and there is to read more buffer. */
|
||||||
|
sds *ldbReplParseCommand(int *argcp) {
|
||||||
|
sds *argv = NULL;
|
||||||
|
int argc = 0;
|
||||||
|
if (sdslen(ldb.cbuf) == 0) return NULL;
|
||||||
|
|
||||||
|
/* Working on a copy is simpler in this case. We can modify it freely
|
||||||
|
* for the sake of simpler parsing. */
|
||||||
|
sds copy = sdsdup(ldb.cbuf);
|
||||||
|
char *p = copy;
|
||||||
|
|
||||||
|
/* This Redis protocol parser is a joke... just the simplest thing that
|
||||||
|
* works in this context. It is also very forgiving regarding broken
|
||||||
|
* protocol. */
|
||||||
|
|
||||||
|
/* Seek and parse *<count>\r\n. */
|
||||||
|
p = strchr(p,'*'); if (!p) goto protoerr;
|
||||||
|
char *plen = p+1; /* Multi bulk len pointer. */
|
||||||
|
p = strstr(p,"\r\n"); if (!p) goto protoerr;
|
||||||
|
*p = '\0'; p += 2;
|
||||||
|
*argcp = atoi(plen);
|
||||||
|
if (*argcp <= 0 || *argcp > 1024) goto protoerr;
|
||||||
|
|
||||||
|
/* Parse each argument. */
|
||||||
|
argv = zmalloc(sizeof(sds)*(*argcp));
|
||||||
|
argc = 0;
|
||||||
|
while(argc < *argcp) {
|
||||||
|
if (*p != '$') goto protoerr;
|
||||||
|
plen = p+1; /* Bulk string len pointer. */
|
||||||
|
p = strstr(p,"\r\n"); if (!p) goto protoerr;
|
||||||
|
*p = '\0'; p += 2;
|
||||||
|
int slen = atoi(plen); /* Length of this arg. */
|
||||||
|
if (slen <= 0 || slen > 1024) goto protoerr;
|
||||||
|
argv[argc++] = sdsnewlen(p,slen);
|
||||||
|
p += slen; /* Skip the already parsed argument. */
|
||||||
|
if (p[0] != '\r' || p[1] != '\n') goto protoerr;
|
||||||
|
p += 2; /* Skip \r\n. */
|
||||||
|
}
|
||||||
|
sdsfree(copy);
|
||||||
|
return argv;
|
||||||
|
|
||||||
|
protoerr:
|
||||||
|
sdsfreesplitres(argv,argc);
|
||||||
|
sdsfree(copy);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read debugging commands from client. */
|
||||||
|
void ldbRepl(void) {
|
||||||
|
sds *argv;
|
||||||
|
int argc;
|
||||||
|
|
||||||
|
/* We continue processing commands until a command that should return
|
||||||
|
* to the Lua interpreter is found. */
|
||||||
|
while(1) {
|
||||||
|
while((argv = ldbReplParseCommand(&argc)) == NULL) {
|
||||||
|
char buf[1024];
|
||||||
|
int nread = read(ldb.fd,buf,sizeof(buf));
|
||||||
|
if (nread <= 0) {
|
||||||
|
/* Make sure the script runs without user input since the
|
||||||
|
* client is no longer connected. */
|
||||||
|
ldb.step = 0;
|
||||||
|
ldb.bpcount = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ldb.cbuf = sdscatlen(ldb.cbuf,buf,nread);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flush the old buffer. */
|
||||||
|
sdsfree(ldb.cbuf);
|
||||||
|
ldb.cbuf = sdsempty();
|
||||||
|
|
||||||
|
/* Execute the command. */
|
||||||
|
if (!strcasecmp(argv[0],"help")) {
|
||||||
|
ldbLog(sdsnew("Redis Lua debugger help:"));
|
||||||
|
ldbLog(sdsnew("[s]tep : run current line and stop again."));
|
||||||
|
ldbLog(sdsnew("[c]continue: run till next breakpoint."));
|
||||||
|
ldbSendLogs();
|
||||||
|
} else if (!strcasecmp(argv[0],"s") || !strcasecmp(argv[0],"step")) {
|
||||||
|
ldb.step = 1;
|
||||||
|
break;
|
||||||
|
} else if (!strcasecmp(argv[0],"c") || !strcasecmp(argv[0],"continue")){
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
ldbLog(sdsnew("Unknown Redis Lua debugger command."));
|
||||||
|
ldbSendLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free the command vector. */
|
||||||
|
sdsfreesplitres(argv,argc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free the current command argv if we break inside the while loop. */
|
||||||
|
sdsfreesplitres(argv,argc);
|
||||||
|
}
|
||||||
|
|
||||||
/* This is the core of our Lua debugger, called each time Lua is about
|
/* This is the core of our Lua debugger, called each time Lua is about
|
||||||
* to start executing a new line. */
|
* to start executing a new line. */
|
||||||
void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
void luaLdbLineHook(lua_State *lua, lua_Debug *ar) {
|
||||||
lua_getstack(lua,0,ar);
|
lua_getstack(lua,0,ar);
|
||||||
lua_getinfo(lua,"Sl",ar);
|
lua_getinfo(lua,"Sl",ar);
|
||||||
if(strstr(ar->short_src,"user_script") != NULL)
|
if(strstr(ar->short_src,"user_script") == NULL) return;
|
||||||
printf("%s %d\n", ar->short_src, (int) ar->currentline);
|
|
||||||
|
if (ldb.step) {
|
||||||
|
ldb.step = 0;
|
||||||
|
ldbLog(sdscatprintf(sdsempty(),"%d: %s", (int)ar->currentline,
|
||||||
|
ldbGetSourceLine(ar->currentline-1)));
|
||||||
|
ldbSendLogs();
|
||||||
|
ldbRepl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user