static void* _timer_instance(void *arg) { int fatal; static int fatal_count = 0; struct timeval timeout; kv_timer_event_t *ev; kv_timer_t *timer = (kv_timer_t*)arg; assert(timer->state == KV_TIMER_STATE_LOADING); timer->state = KV_TIMER_STATE_RUNNING; for (;;) { timeout.tv_sec = 0; timeout.tv_usec = timer->interval; /** interval is very short, ignore signal interrupt */ fatal = select(0, NULL, NULL, NULL, &timeout); if (fatal == -1) { fatal_count++; if (fatal_count >= 3) abort(); continue; } fatal_count = 0; ev = timer->list; while(ev) { if (ustime() - ev->elapse_base >= ev->timeout) { ev->elapse_base = ustime(); /** restart */ ev->proc(); } ev = ev->next; } } }
void busysleep(int usec) { int64_t t = ustime() + usec; while (t > ustime()) { ; // Nothing. } }
int do_delete_event(struct redisClient *c,sds funcname) { redisSrand48(0); server.lua_random_dirty = 0; server.lua_write_dirty = 0; lua_getglobal(server.lua,(char *)funcname); if (lua_isnil(server.lua,1)) { addReplyError(c,"no funcname triggle_scipts in lua"); return 0; } luaTriggleSetGlobalArray(server.lua,"KEYS",c->argv+1,c->argc-1); redisLog(REDIS_NOTICE,"stack: %d",lua_gettop(server.lua)); #ifdef BRIDGE_DEBUG for(int i=0;i<c->argc-1;i++){ redisLog(REDIS_NOTICE,"%s",(c->argv+1)[i]->ptr); } #endif selectDb(server.lua_client,c->db->id); server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0) { lua_sethook(server.lua,luaMaskCountHook,LUA_MASKCOUNT,100000); server.lua_time_start = ustime()/1000; } else { lua_sethook(server.lua,luaMaskCountHook,0,0); } if (lua_pcall(server.lua,0,1,0)) { selectDb(c,server.lua_client->db->id); addReplyErrorFormat(c,"Error running script (call to %s): %s\n", (char*)funcname, lua_tostring(server.lua,-1)); lua_pop(server.lua,1); lua_gc(server.lua,LUA_GCCOLLECT,0); return -1; } selectDb(c,server.lua_client->db->id); // luaReplyToRedisReply(c,server.lua); server.lua_timedout = 0; server.lua_caller = NULL; lua_gc(server.lua,LUA_GCSTEP,1); //for slaves // return 0; }
// get web from redis and calculate pagerank void pagerank_redis(redisContext **conts, float v_quadratic_error, web* w) { redisContext *c = conts[0]; redisReply *reply; long long t_start; long long t_end; unsigned int i; float q = 1; printf("Start PageRank from redis: \n"); printf("Damping factor: %f\n", g_damping_factor); printf("Quadratic error range: %f\n", v_quadratic_error); printf("----------------------------------\n"); reply = redisCommand(c, "GET next.uid"); if (reply->str == NULL) die("Cannot not find data in redis!"); unsigned long user_num = atol(reply->str) + 1; printf("user num: %ld\n", user_num); freeReplyObject(reply); t_start = ustime(); if (w->size == 0) { for (i = 0; i < g_n_sharded; ++i) { reply = redisCommand(conts[i], "DEL newly.edited.linkto"); freeReplyObject(reply); reply = redisCommand(conts[i], "DEL newly.edited.linkfrom"); freeReplyObject(reply); } web_init(w, user_num); gen_web_sharded_redis(conts, w, 0); } else { size_t origin_size = w->size; web_init_from_oldweb(w, user_num); printf("web expansion fin..."); update_web_sharded_redis(conts, w); printf("web update fin...\n"); //printf("origin dangpage size: %zd\n", w->dangling_pages->size); update_dangling_pages(w, origin_size); //printf("updated dangpage size: %zd\n", w->dangling_pages->size); } printf("dangling user size: %zd\n", w->dangling_pages->size); t_end = ustime(); printf("init: %lf seconds\n", getruntime(t_end, t_start)); t_start = ustime(); for (i = 1; q > v_quadratic_error; ++i) { q = calculate_pagerank_parallel(w); //printf("iteration %d deviate value: %f\n", i, q); //web_display_pagerank(w); } t_end = ustime(); printf("calculate: %lf seconds(%d iterations)\n", getruntime(t_end, t_start), i - 1); }
/* Call() is the core of execution of a command */ static void call(ugClient *c, int flags) { long long start = ustime(), duration; /* Call the command. */ c->cmd->proc(c); duration = ustime()-start; c->cmd->microseconds += duration; c->cmd->calls++; }
/* This is how rewriting of the append only file in background works: * * 1) The user calls BGREWRITEAOF * 2) Redis calls this function, that forks(): * 2a) the child rewrite the append only file in a temp file. * 2b) the parent accumulates differences in server.aof_rewrite_buf. * 3) When the child finished '2a' exists. * 4) The parent will trap the exit code, if it's OK, will append the * data accumulated into server.aof_rewrite_buf into the temp file, and * finally will rename(2) the temp file in the actual file name. * The the new file is reopened as the new append only file. Profit! */ int rewriteAppendOnlyFileBackground(void) { pid_t childpid; long long start; if (server.aof_child_pid != -1) return REDIS_ERR; start = ustime(); if ((childpid = fork()) == 0) { char tmpfile[256]; /* Child */ closeListeningSockets(0); redisSetProcTitle("redis-aof-rewrite"); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) { size_t private_dirty = zmalloc_get_private_dirty(); if (private_dirty) { redisLog(REDIS_NOTICE, "AOF rewrite: %zu MB of memory used by copy-on-write", private_dirty/(1024*1024)); } exitFromChild(0); } else { exitFromChild(1); } } else { /* Parent */ server.stat_fork_time = ustime()-start; server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */ latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000); if (childpid == -1) { redisLog(REDIS_WARNING, "Can't rewrite append only file in background: fork: %s", strerror(errno)); return REDIS_ERR; } redisLog(REDIS_NOTICE, "Background append only file rewriting started by pid %d",childpid); server.aof_rewrite_scheduled = 0; server.aof_rewrite_time_start = time(NULL); server.aof_child_pid = childpid; updateDictResizePolicy(); /* We set appendseldb to -1 in order to force the next call to the * feedAppendOnlyFile() to issue a SELECT command, so the differences * accumulated by the parent into server.aof_rewrite_buf will start * with a SELECT statement and it will be safe to merge. */ server.aof_selected_db = -1; replicationScriptCacheFlush(); return REDIS_OK; } return REDIS_OK; /* unreached */ }
/* * 脚本超时钩子 */ void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { long long elapsed; REDIS_NOTUSED(ar); REDIS_NOTUSED(lua); // 计算已执行时间 elapsed = (ustime()/1000) - server.lua_time_start; if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) { redisLog(REDIS_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. */ // 当脚本运行超时时,将正在执行的客户端从读事件中移除 // 并允许其他客户端执行 SCRIPT KILL 或者 SHUTDOWN NOSAVE aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE); } if (server.lua_timedout) // 在脚本上下文中,启动文件事件处理(等待 SCRIPT KILL 或 SHUTDOWN NOSAVE) aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); if (server.lua_kill) { // 杀死脚本 redisLog(REDIS_WARNING,"Lua script killed by user with SCRIPT KILL."); lua_pushstring(lua,"Script killed by user with SCRIPT KILL..."); lua_error(lua); } }
static void write_handler(ae_event_loop *el, int fd, void *priv, int mask) { client *c = (client *)priv; /* Initialize request when nothing was written. */ if (c->written == 0) { if (conf.requests_issued++ >= conf.requests) { free_client(c); return; } c->start = ustime(); c->latency = -1; } if (sdslen(c->obuf) > c->written) { char *ptr = c->obuf + c->written; int nwritten = write(c->fd, ptr, sdslen(c->obuf) - c->written); if (nwritten == -1) { if (errno != EPIPE) { fprintf(stderr, "write failed:%s\n", strerror(errno)); } free_client(c); return; } c->written += nwritten; if (sdslen(c->obuf) == c->written) { ae_delete_file_event(conf.el, c->fd, AE_WRITABLE); ae_create_file_event(conf.el, c->fd, AE_READABLE, read_handler, c); } } }
// 判断超时,并作超时的处理 void luaMaskCountHook(lua_State *lua, lua_Debug *ar) { long long elapsed; REDIS_NOTUSED(ar); REDIS_NOTUSED(lua); // 计算已经超时的时间 elapsed = (ustime()/1000) - server.lua_time_start; if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) { redisLog(REDIS_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; // 超时了,关闭监听发送 lua 脚本命令的客户端 /* 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); } // lua 脚本执行超时,redis 会检测对否有其他客户端会发送 SCRIPT KILL 命令 // 尝试终结这个脚本的执行 if (server.lua_timedout) aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT); // 如果 lua 脚本被停止了,强制产生错误,结束 lua if (server.lua_kill) { redisLog(REDIS_WARNING,"Lua script killed by user with SCRIPT KILL."); lua_pushstring(lua,"Script killed by user with SCRIPT KILL..."); lua_error(lua); } }
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { client c = privdata; void *reply = NULL; REDIS_NOTUSED(el); REDIS_NOTUSED(fd); REDIS_NOTUSED(mask); /* Calculate latency only for the first read event. This means that the * server already sent the reply and we need to parse it. Parsing overhead * is not part of the latency, so calculate it only once, here. */ if (c->latency < 0) c->latency = ustime()-(c->start); if (redisBufferRead(c->context) != REDIS_OK) { fprintf(stderr,"Error: %s\n",c->context->errstr); exit(1); } else { if (redisGetReply(c->context,&reply) != REDIS_OK) { fprintf(stderr,"Error: %s\n",c->context->errstr); exit(1); } if (reply != NULL) { if (reply == (void*)REDIS_REPLY_ERROR) { fprintf(stderr,"Unexpected error reply, exiting...\n"); exit(1); } if (config.requests_finished < config.requests) config.latency[config.requests_finished++] = c->latency; clientDone(c); } } }
/* convert a linked list encoding to a list array encoding */ robj *cowListCopy(robj *val) { long long sttime; robj *newval; sttime = ustime(); if (val->encoding == REDIS_ENCODING_ZIPLIST) { size_t bytes; redisLog(REDIS_NOTICE, "cowListCopy REDIS_ENCODING_ZIPLIST"); newval = createZiplistObject(); /* do raw memory copy */ bytes = ziplistBlobLen(val->ptr); newval->ptr = zrealloc(newval->ptr, bytes); memcpy(newval->ptr, val->ptr, bytes); return newval; } else if (val->encoding == REDIS_ENCODING_LINKEDLIST) { list *list = val->ptr; cowListArray *lar; redisLog(REDIS_NOTICE, "cowListCopy REDIS_ENCODING_LINKEDLIST"); lar = cowConvertListToArray(list); newval = createObject(REDIS_LIST, lar); newval->encoding = REDIS_ENCODING_LINKEDLISTARRAY; return newval; } else { /* error. unexpected encoding */ return NULL; } }
/* if copy on write active, then ensure there is a copy of the value that is safe to modify or delete, and update DB dict entry to refer to this value*/ void cowEnsureExpiresCopy(redisDb *db) { long long sttime; if (server.isBackgroundSaving == 0 || server.cowDictCopied == NULL || server.cowSaveDb[db->id].expires == NULL) { /* no copy needed */ return; } else { /* ensure DB expires is copied */ if (server.cowSaveDb[db->id].expires == server.db[db->id].expires) { sttime = ustime(); server.db[db->id].expires = copyonwrite_dictobj(server.cowSaveDb[db->id].expires, NULL); redisLog(REDIS_NOTICE, "elapsed COW DB expires time %d", (unsigned int)(ustime() - sttime)); } } }
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) { client c = privdata; void *reply = NULL; REDIS_NOTUSED(el); REDIS_NOTUSED(fd); REDIS_NOTUSED(mask); /* Calculate latency only for the first read event. This means that the * server already sent the reply and we need to parse it. Parsing overhead * is not part of the latency, so calculate it only once, here. */ if (c->latency < 0) c->latency = ustime()-(c->start); if (redisBufferRead(c->context) != REDIS_OK) { fprintf(stderr,"Error: %s\n",c->context->errstr); exit(1); } else { while(c->pending) { if (redisGetReply(c->context,&reply) != REDIS_OK) { fprintf(stderr,"Error: %s\n",c->context->errstr); exit(1); } if (reply != NULL) { if (reply == (void*)REDIS_REPLY_ERROR) { fprintf(stderr,"Unexpected error reply, exiting...\n"); exit(1); } freeReplyObject(reply); /* This is an OK for prefix commands such as auth and select.*/ if (c->prefix_pending > 0) { c->prefix_pending--; c->pending--; /* Discard prefix commands on first response.*/ if (c->prefixlen > 0) { size_t j; sdsrange(c->obuf, c->prefixlen, -1); /* We also need to fix the pointers to the strings * we need to randomize. */ for (j = 0; j < c->randlen; j++) c->randptr[j] -= c->prefixlen; c->prefixlen = 0; } continue; } if (config.requests_finished < config.requests) config.latency[config.requests_finished++] = c->latency; c->pending--; if (c->pending == 0) { clientDone(c); break; } } else { break; } } } }
static void init_per_lcore() { lcore_conf_t *qconf; unsigned lcore_id = rte_lcore_id(); qconf = &sk.lcore_conf[lcore_id]; qconf->tsc_hz = rte_get_tsc_hz(); qconf->start_us = (uint64_t )ustime(); qconf->start_tsc = rte_rdtsc(); }
/* This is how rewriting of the append only file in background works: * * 1) The user calls BGREWRITEAOF * 2) Redis calls this function, that forks(): * 2a) the child rewrite the append only file in a temp file. * 2b) the parent accumulates differences in server.bgrewritebuf. * 3) When the child finished '2a' exists. * 4) The parent will trap the exit code, if it's OK, will append the * data accumulated into server.bgrewritebuf into the temp file, and * finally will rename(2) the temp file in the actual file name. * The the new file is reopened as the new append only file. Profit! */ int rewriteAppendOnlyFileBackground(void) { pid_t childpid; long long start; if (server.bgrewritechildpid != -1) return REDIS_ERR; if (server.vm_enabled) waitEmptyIOJobsQueue(); start = ustime(); if ((childpid = fork()) == 0) { char tmpfile[256]; /* Child */ if (server.vm_enabled) vmReopenSwapFile(); if (server.ipfd > 0) close(server.ipfd); if (server.sofd > 0) close(server.sofd); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) { _exit(0); } else { _exit(1); } } else { /* Parent */ server.stat_fork_time = ustime()-start; if (childpid == -1) { redisLog(REDIS_WARNING, "Can't rewrite append only file in background: fork: %s", strerror(errno)); return REDIS_ERR; } redisLog(REDIS_NOTICE, "Background append only file rewriting started by pid %d",childpid); server.aofrewrite_scheduled = 0; server.bgrewritechildpid = childpid; updateDictResizePolicy(); /* We set appendseldb to -1 in order to force the next call to the * feedAppendOnlyFile() to issue a SELECT command, so the differences * accumulated by the parent into server.bgrewritebuf will start * with a SELECT statement and it will be safe to merge. */ server.appendseldb = -1; return REDIS_OK; } return REDIS_OK; /* unreached */ }
void kv_timer_add(kv_timer_t *timer, long long usec, timer_proc proc) { kv_timer_event_t *te; te = zmalloc(sizeof(*te)); te->timeout = usec < 0 ? -usec : usec; te->elapse_base = ustime(); te->proc = proc; te->next = 0; if (!timer->list) { timer->list = te; } else { te->next = timer->list; timer->list = te; } }
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) { client c = privdata; REDIS_NOTUSED(el); REDIS_NOTUSED(fd); REDIS_NOTUSED(mask); /* Initialize request when nothing was written. */ if (c->written == 0) { /* Enforce upper bound to number of requests. */ if (config.requests_issued++ >= config.requests) { freeClient(c); return; } /* Really initialize: randomize keys and set start time. */ if (config.randomkeys) randomizeClientKey(c); c->start = ustime(); c->latency = -1; } if (sdslen(c->obuf) > c->written) { void *ptr = c->obuf+c->written; #ifdef _WIN32 int nwritten = send(c->context->fd,ptr,sdslen(c->obuf)-c->written, 0); #else int nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written); #endif if (nwritten == -1) { if (errno != EPIPE) fprintf(stderr, "Writing to socket: %s\n", strerror(errno)); freeClient(c); return; } c->written += nwritten; if (sdslen(c->obuf) == c->written) { aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE); aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c); } } }
static void read_handler(ae_event_loop *el, int fd, void *priv, int mask) { client *c = (client *)priv; int nread; char buffer[4096]; nread = read(fd, buffer, 4096); if (nread == -1) { if (errno == EAGAIN) { return; } fprintf(stderr, "Error: %s\n", strerror(errno)); exit(1); } else if (nread == 0) { fprintf(stderr, "Error: %s\n", "Server close connection."); exit(1); } c->read += nread; if (c->read == sdslen(c->obuf)) { c->latency = ustime() - c->start; ++conf.requests_finished; client_done(c); } }
/* A background append only file rewriting (BGREWRITEAOF) terminated its work. * Handle this. */ void backgroundRewriteDoneHandler(int exitcode, int bysignal) { if (!bysignal && exitcode == 0) { int newfd, oldfd; char tmpfile[256]; long long now = ustime(); redisLog(REDIS_NOTICE, "Background AOF rewrite terminated with success"); /* Flush the differences accumulated by the parent to the * rewritten AOF. */ snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int)server.aof_child_pid); newfd = open(tmpfile,O_WRONLY|O_APPEND); if (newfd == -1) { redisLog(REDIS_WARNING, "Unable to open the temporary AOF produced by the child: %s", strerror(errno)); goto cleanup; } if (aofRewriteBufferWrite(newfd) == -1) { redisLog(REDIS_WARNING, "Error trying to flush the parent diff to the rewritten AOF: %s", strerror(errno)); close(newfd); goto cleanup; } redisLog(REDIS_NOTICE, "Parent diff successfully flushed to the rewritten AOF (%lu bytes)", aofRewriteBufferSize()); /* The only remaining thing to do is to rename the temporary file to * the configured file and switch the file descriptor used to do AOF * writes. We don't want close(2) or rename(2) calls to block the * server on old file deletion. * * There are two possible scenarios: * * 1) AOF is DISABLED and this was a one time rewrite. The temporary * file will be renamed to the configured file. When this file already * exists, it will be unlinked, which may block the server. * * 2) AOF is ENABLED and the rewritten AOF will immediately start * receiving writes. After the temporary file is renamed to the * configured file, the original AOF file descriptor will be closed. * Since this will be the last reference to that file, closing it * causes the underlying file to be unlinked, which may block the * server. * * To mitigate the blocking effect of the unlink operation (either * caused by rename(2) in scenario 1, or by close(2) in scenario 2), we * use a background thread to take care of this. First, we * make scenario 1 identical to scenario 2 by opening the target file * when it exists. The unlink operation after the rename(2) will then * be executed upon calling close(2) for its descriptor. Everything to * guarantee atomicity for this switch has already happened by then, so * we don't care what the outcome or duration of that close operation * is, as long as the file descriptor is released again. */ if (server.aof_fd == -1) { /* AOF disabled */ /* Don't care if this fails: oldfd will be -1 and we handle that. * One notable case of -1 return is if the old file does * not exist. */ oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK); } else { /* AOF enabled */ oldfd = -1; /* We'll set this to the current AOF filedes later. */ } /* Rename the temporary file. This will not unlink the target file if * it exists, because we reference it with "oldfd". */ if (rename(tmpfile,server.aof_filename) == -1) { redisLog(REDIS_WARNING, "Error trying to rename the temporary AOF file: %s", strerror(errno)); close(newfd); if (oldfd != -1) close(oldfd); goto cleanup; } if (server.aof_fd == -1) { /* AOF disabled, we don't need to set the AOF file descriptor * to this new file, so we can close it. */ close(newfd); } else { /* AOF enabled, replace the old fd with the new one. */ oldfd = server.aof_fd; server.aof_fd = newfd; if (server.aof_fsync == AOF_FSYNC_ALWAYS) aof_fsync(newfd); else if (server.aof_fsync == AOF_FSYNC_EVERYSEC) aof_background_fsync(newfd); server.aof_selected_db = -1; /* Make sure SELECT is re-issued */ aofUpdateCurrentSize(); server.aof_rewrite_base_size = server.aof_current_size; /* Clear regular AOF buffer since its contents was just written to * the new AOF from the background rewrite buffer. */ sdsfree(server.aof_buf); server.aof_buf = sdsempty(); } server.aof_lastbgrewrite_status = REDIS_OK; redisLog(REDIS_NOTICE, "Background AOF rewrite finished successfully"); /* Change state from WAIT_REWRITE to ON if needed */ if (server.aof_state == REDIS_AOF_WAIT_REWRITE) server.aof_state = REDIS_AOF_ON; /* Asynchronously close the overwritten AOF. */ if (oldfd != -1) bioCreateBackgroundJob(REDIS_BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL); redisLog(REDIS_VERBOSE, "Background AOF rewrite signal handler took %lldus", ustime()-now); } else if (!bysignal && exitcode != 0) { server.aof_lastbgrewrite_status = REDIS_ERR; redisLog(REDIS_WARNING, "Background AOF rewrite terminated with error"); } else { server.aof_lastbgrewrite_status = REDIS_ERR; redisLog(REDIS_WARNING, "Background AOF rewrite terminated by signal %d", bysignal); } cleanup: aofRewriteBufferReset(); aofRemoveTempFile(server.aof_child_pid); server.aof_child_pid = -1; server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start; server.aof_rewrite_time_start = -1; /* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */ if (server.aof_state == REDIS_AOF_WAIT_REWRITE) server.aof_rewrite_scheduled = 1; }
static void test_splinlock_speed() { spinlock_init(&simple_spin); ticketlock_init(&simple_ticket); pthread_t threads[10]; printf("spinlock speed test (lock):\n"); for (int num = 1; num <= 16; num *= 2) { printf("%2d threads: ", num); fflush(stdout); spinlock_lock(&simple_spin); for (int t = 0; t < num; ++t) { pthread_create(&threads[t], 0, simple_spinlock, 0); } int64_t start = ustime(); spinlock_unlock(&simple_spin); for (int t = 0; t < num; ++t) { pthread_join(threads[t], 0); } int64_t end = ustime(); printf("%6ld milliseconds\n", (end - start) / 1000); } printf("spinlock speed test (yieldock):\n"); for (int num = 1; num <= 16; num *= 2) { printf("%2d threads: ", num); fflush(stdout); spinlock_lock(&simple_spin); for (int t = 0; t < num; ++t) { pthread_create(&threads[t], 0, simple_spinlock_yield, 0); } int64_t start = ustime(); spinlock_unlock(&simple_spin); for (int t = 0; t < num; ++t) { pthread_join(threads[t], 0); } int64_t end = ustime(); printf("%6ld milliseconds\n", (end - start) / 1000); } printf("ticketlock speed test (lock):\n"); for (int num = 1; num <= 8; num *= 2) { printf("%2d threads: ", num); fflush(stdout); ticketlock_lock(&simple_ticket); for (int t = 0; t < num; ++t) { pthread_create(&threads[t], 0, simple_ticketlock, 0); } int64_t start = ustime(); ticketlock_unlock(&simple_ticket); for (int t = 0; t < num; ++t) { pthread_join(threads[t], 0); } int64_t end = ustime(); printf("%6ld milliseconds\n", (end - start) / 1000); } printf("ticketlock speed test (yieldlock):\n"); for (int num = 1; num <= 8; num *= 2) { printf("%2d threads: ", num); fflush(stdout); ticketlock_lock(&simple_ticket); for (int t = 0; t < num; ++t) { pthread_create(&threads[t], 0, simple_ticketlock_yield, 0); } int64_t start = ustime(); ticketlock_unlock(&simple_ticket); for (int t = 0; t < num; ++t) { pthread_join(threads[t], 0); } int64_t end = ustime(); printf("%6ld milliseconds\n", (end - start) / 1000); } }
/* if copy on write active, then ensure there is a copy of the value that is safe to modify or delete, and update DB dict entry to refer to this value*/ robj *cowEnsureWriteCopy(redisDb *db, robj *key, robj *val) { long long sttime; if (server.isBackgroundSaving == 0 || server.cowDictCopied == NULL) { /* no copy needed */ return val; } else { int added = 0; sds keyname; robj *newval = NULL; sttime = ustime(); /* first ensure DB dict readonly copy exists */ cowLock(); if (server.cowSaveDbExt[db->id].dictArray == NULL) { /* make clone with modified cow destructors for db dict */ server.cowSaveDbExt[db->id].dictArray = copyReadonly_dictobj(server.db[db->id].dict, &server.cowSaveDbExt[db->id]); /* migrate iterator */ roDBMigrateIterator(server.db[db->id].dict, server.cowSaveDbExt[db->id].dictArray); } cowUnlock(); if (val == NULL || key == NULL) { return NULL; } if (dictFind(server.cowDictCopied, (sds)key->ptr) != NULL) { /* already copied */ return val; } /* need to duplicate object, add key to cowDictCopied, add original to deferred delete list, and update db entry */ cowLock(); switch (val->type) { case REDIS_STRING: /* updates always duplicate, original uses defered delete destructor */ break; case REDIS_LIST: newval = cowListCopy(val); break; case REDIS_SET: newval = cowSetCopy(val); break; case REDIS_ZSET: newval = cowZSetCopy(val); break; case REDIS_HASH: newval = cowHashCopy(val); break; default: break; } cowUnlock(); if (newval == NULL) { /* no duplicate needed. return original */ return val; } /* add key to copied dictionary to avoid extra copies */ keyname = sdsdup((sds)key->ptr); dictAdd(server.cowDictCopied, keyname, NULL); /* rewitten iterators are added to converted for lookup * during save. For other objects, replace DB entry */ if (newval->encoding == REDIS_ENCODING_HTARRAY || newval->encoding == REDIS_ENCODING_LINKEDLISTARRAY || newval->encoding == REDIS_ENCODING_HTZARRAY) { cowLock(); /* add value to converted dictionary for iterator lookup */ dictAdd(server.cowDictConverted, keyname, newval); /* migrate current iterator */ if (newval->encoding == REDIS_ENCODING_HTARRAY) { roDictMigrateIterator((dict *)val->ptr, (cowDictArray *)newval->ptr); } else if (newval->encoding == REDIS_ENCODING_LINKEDLISTARRAY) { roListMigrateIterator((list *)val->ptr, (cowListArray *)newval->ptr); } else if (newval->encoding == REDIS_ENCODING_HTZARRAY) { roZDictMigrateIterator((dict *)val->ptr, (cowDictZArray *)newval->ptr); } cowUnlock(); } else { /* replace active DB entry */ dbOverwrite(db, key, newval); val = newval; } redisLog(REDIS_NOTICE, "elapsed COW time %d", (unsigned int)(ustime() - sttime)); return val; } }
int triggle_event(struct redisClient *c,sds funcname) { redisSrand48(0); server.lua_random_dirty = 0; server.lua_write_dirty = 0; //redisLog(REDIS_NOTICE,"step into stack: %d",lua_gettop(server.lua)); lua_getglobal(server.lua,funcname); if (lua_isnil(server.lua,1)) { lua_pop(server.lua,1); /* remove the nil from the stack */ redisLog(REDIS_NOTICE,"no funcname triggle_scipts in lua"); struct dictEntry *de = dictFind(server.bridge_db.triggle_scipts[c->db->id],funcname); if(de) { struct bridge_db_triggle_t * tmptrg=dictGetVal(de); if (luatriggleCreateFunction(server.lua,funcname,tmptrg->lua_scripts) == REDIS_ERR) return -1; /* Now the following is guaranteed to return non nil */ lua_getglobal(server.lua, funcname); redisAssert(!lua_isnil(server.lua,1)); } else { redisLog(REDIS_WARNING,"triggle not found"); return -1; } } luaTriggleSetGlobalArray(server.lua,"KEYS",c->argv,c->argc); // redisLog(REDIS_NOTICE,"stack: %d",lua_gettop(server.lua)); /* for(int i=0;i<c->argc;i++){ redisLog(REDIS_NOTICE,"%s",c->argv[i]->ptr); } */ /* Select the right DB in the context of the Lua client */ selectDb(server.lua_client,c->db->id); server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0) { lua_sethook(server.lua,luaMaskCountHook,LUA_MASKCOUNT,100000); server.lua_time_start = ustime()/1000; } else { lua_sethook(server.lua,luaMaskCountHook,0,0); } /* At this point whatever this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ if (lua_pcall(server.lua,0,1,0)) { selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ redisLog(REDIS_WARNING,"Error running script (call to %s): %s\n", (char*)funcname, lua_tostring(server.lua,-1)); lua_pop(server.lua,1); lua_gc(server.lua,LUA_GCCOLLECT,0); return -1; } selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ // luaReplyToRedisReply(c,server.lua); lua_pop(server.lua,1); server.lua_timedout = 0; server.lua_caller = NULL; lua_gc(server.lua,LUA_GCSTEP,1); // redisLog(REDIS_NOTICE,"after stack: %d",lua_gettop(server.lua)); //for slaves // return 0; }
int triggle_expire_event(redisDb *db,sds funcname,robj *key) { redisSrand48(0); server.lua_random_dirty = 0; server.lua_write_dirty = 0; lua_getglobal(server.lua,funcname); if (lua_isnil(server.lua,1)) { lua_pop(server.lua,1); /* remove the nil from the stack */ redisLog(REDIS_NOTICE,"no funcname triggle_scipts in lua"); struct dictEntry *de = dictFind(server.bridge_db.triggle_scipts[db->id],funcname); if(de) { struct bridge_db_triggle_t * tmptrg=dictGetVal(de); if (luatriggleCreateFunction(server.lua,funcname,tmptrg->lua_scripts) == REDIS_ERR) return -1; /* Now the following is guaranteed to return non nil */ lua_getglobal(server.lua, funcname); redisAssert(!lua_isnil(server.lua,1)); } else { redisLog(REDIS_WARNING,"triggle not found"); return -1; } } luaTriggleSetGlobalArray(server.lua,"KEYS",&key,1); server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0) { lua_sethook(server.lua,luaMaskCountHook,LUA_MASKCOUNT,100000); server.lua_time_start = ustime()/1000; } else { lua_sethook(server.lua,luaMaskCountHook,0,0); } /* At this point whatever this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ if (lua_pcall(server.lua,0,1,0)) { //selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ redisLog(REDIS_WARNING,"exec the script error:%s",lua_tostring(server.lua,-1)); lua_pop(server.lua,1); lua_gc(server.lua,LUA_GCCOLLECT,0); return -1; } //selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ server.lua_timedout = 0; server.lua_caller = NULL; lua_pop(server.lua,1); lua_gc(server.lua,LUA_GCSTEP,1); return 0; }
void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0; /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. * * 在每次执行 EVAL 时重置随机 seed ,从而保证可以生成相同的随机序列。 */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. * * 用两个变量,对命令进行检查 * 确保在调用随机命令之后,再调用写命令将出现错误 */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; /* Get the number of arguments that are keys */ // 获取输入键的数量 if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; // 对键的正确性做一个快速检查 if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ // 组合出函数的名字,例如 f_282297a0228f48cd3fc6a55de6316f31422f5d17 funcname[0] = 'f'; funcname[1] = '_'; if (!evalsha) { /* Hash the code if this is an EVAL call */ // 如果执行的是 EVAL 命令,那么计算脚本的 SHA1 校验和 sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { // 如果执行的是 EVALSHA 命令,直接使用传入的 SHA1 值 /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } /* Try to lookup the Lua function */ // 按名查找函数 lua_getglobal(lua, funcname); if (lua_isnil(lua,1)) { // 没找到脚本相应的脚本 lua_pop(lua,1); /* remove the nil from the stack */ /* Function not defined... let's define it if we have the * body of the funciton. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { // 如果执行的是 EVALSHA ,返回脚本未找到错误 addReply(c, shared.noscripterr); return; } // 如果执行的是 EVAL ,那么创建并执行新函数,然后将代码添加到脚本字典中 if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) return; /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,1)); } /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ // 设置 KEYS 和 ARGV 全局变量到 Lua 环境 luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys); /* Select the right DB in the context of the Lua client */ // 为 Lua 所属的(伪)客户端设置数据库 selectDb(server.lua_client,c->db->id); /* Set an 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. * * 只在开启了时间限制选项时使用钩子,因为它会拖慢脚本的运行速度。 */ // 调用客户端 server.lua_caller = c; // 脚本开始时间 server.lua_time_start = ustime()/1000; // 是否杀死脚本 server.lua_kill = 0; // 只在开启时间限制时使用钩子 if (server.lua_time_limit > 0 && server.masterhost == NULL) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } /* At this point whatever this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ // 执行脚本(所属的函数) if (lua_pcall(lua,0,1,0)) { // 以下是脚本执行出错的代码。。。 // 删除钩子 if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ // 脚本执行已超时 if (server.lua_timedout) { // 清除超时 FLAG server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ // 将超时钩子里删除的读事件重新加上 aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } // 清空调用者 server.lua_caller = NULL; // 更新目标数据库 selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); // 弹出函数 lua_pop(lua,1); // 执行完整的废料回首循环(full garbage-collection cycle) lua_gc(lua,LUA_GCCOLLECT,0); // 返回 return; } // 以下是脚本执行成功时执行的代码。。。 // 删除钩子 if (delhook) lua_sethook(lua,luaMaskCountHook,0,0); /* Disable hook */ // 清空超时 FLAG server.lua_timedout = 0; // 清空调用者 server.lua_caller = NULL; // 更新 DB selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ // 将 Lua 回复转换成 Redis 回复 luaReplyToRedisReply(c,lua); // 执行 1 步渐进式 GC lua_gc(lua,LUA_GCSTEP,1); /* If we have slaves attached we want to replicate this command as * EVAL instead of EVALSHA. We do this also in the AOF as currently there * is no easy way to propagate a command in a different way in the AOF * and in the replication link. * * 如果有附属节点,那么使用 EVAL 而不是 EVALSHA 来传播脚本 * 因为目前还没有代码可以检测脚本是否已经传送到附属节点中 * * IMPROVEMENT POSSIBLE: * 1) Replicate this command as EVALSHA in the AOF. * 2) Remember what slave already received a given script, and replicate * the EVALSHA against this slaves when possible. */ // 如果执行的是 EVALSHA 命令 if (evalsha) { // 取出脚本代码体(body) robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); redisAssertWithInfo(c,NULL,script != NULL); // 重写客户端命令为 EVAL rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); } }
void update() { this->t = ustime(); }
uint64_t tick() { uint64_t ret = this->t; this->t = ustime(); return this->t - ret; }
void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0, err; /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; /* Get the number of arguments that are keys */ if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ funcname[0] = 'f'; funcname[1] = '_'; if (!evalsha) { /* Hash the code if this is an EVAL call */ sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } /* Push the pcall error handler function on the stack. */ lua_getglobal(lua, "__redis__err__handler"); /* Try to lookup the Lua function */ lua_getglobal(lua, funcname); if (lua_isnil(lua,-1)) { lua_pop(lua,1); /* remove the nil from the stack */ /* Function not defined... let's define it if we have the * body of the function. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { lua_pop(lua,1); /* remove the error handler from the stack. */ addReply(c, shared.noscripterr); return; } if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) return; /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,-1)); } /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys); /* Select the right DB in the context of the Lua client */ selectDb(server.lua_client,c->db->id); /* Set an 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. */ server.lua_caller = c; server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0 && server.masterhost == NULL) { lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } /* At this point whether this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ 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 (server.lua_timedout) { server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } server.lua_caller = NULL; selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ lua_gc(lua,LUA_GCSTEP,1); if (err) { addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); lua_pop(lua,1); /* Consume the Lua reply. */ } else { /* On success convert the Lua return value into Redis protocol, and * send it to * the client. */ luaReplyToRedisReply(c,lua); } /* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless * we are sure that the script was already in the context of all the * attached slaves *and* the current AOF file if enabled. * * To do so we use a cache of SHA1s of scripts that we already propagated * as full EVAL, that's called the Replication Script Cache. * * For repliation, everytime a new slave attaches to the master, we need to * flush our cache of scripts that can be replicated as EVALSHA, while * for AOF we need to do so every time we rewrite the AOF file. */ if (evalsha) { if (!replicationScriptCacheExists(c->argv[1]->ptr)) { /* This script is not in our script cache, replicate it as * EVAL, then add it into the script cache, as from now on * slaves and AOF know about it. */ robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); replicationScriptCacheAdd(c->argv[1]->ptr); redisAssertWithInfo(c,NULL,script != NULL); rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); } } }
/* Return the UNIX time in milliseconds */ long long mstime(void) { return ustime()/1000; }
int main(int argc, char *argv[]) { printf("Second:%d, Millisecond:%lld, Microsecond:%lld\n", time(NULL), mstime(), ustime()); exit(0); }
void evalGenericCommand(redisClient *c, int evalsha) { lua_State *lua = server.lua; char funcname[43]; long long numkeys; int delhook = 0, err; // 随机数的种子,在产生哈希值的时候会用到 /* We want the same PRNG sequence at every call so that our PRNG is * not affected by external state. */ redisSrand48(0); /* We set this flag to zero to remember that so far no random command * was called. This way we can allow the user to call commands like * SRANDMEMBER or RANDOMKEY from Lua scripts as far as no write command * is called (otherwise the replication and AOF would end with non * deterministic sequences). * * Thanks to this flag we'll raise an error every time a write command * is called after a random command was used. */ server.lua_random_dirty = 0; server.lua_write_dirty = 0; // 检查参数的有效性 /* Get the number of arguments that are keys */ if (getLongLongFromObjectOrReply(c,c->argv[2],&numkeys,NULL) != REDIS_OK) return; if (numkeys > (c->argc - 3)) { addReplyError(c,"Number of keys can't be greater than number of args"); return; } // 函数名以 f_ 开头 /* We obtain the script SHA1, then check if this function is already * defined into the Lua state */ funcname[0] = 'f'; funcname[1] = '_'; // 如果没有哈希值,需要计算 lua 脚本的哈希值 if (!evalsha) { // 计算哈希值,会放入到 SHA1 -> lua_script 哈希表中 // c->argv[1]->ptr 是用户指定的 lua 脚本 // sha1hex() 产生的哈希值存在 funcname 中 /* Hash the code if this is an EVAL call */ sha1hex(funcname+2,c->argv[1]->ptr,sdslen(c->argv[1]->ptr)); } else { // 用户自己指定了哈希值 /* We already have the SHA if it is a EVALSHA */ int j; char *sha = c->argv[1]->ptr; for (j = 0; j < 40; j++) funcname[j+2] = tolower(sha[j]); funcname[42] = '\0'; } // 将错误处理函数入栈 // lua_getglobal() 会将读取指定的全局变量,且将其入栈 /* Push the pcall error handler function on the stack. */ lua_getglobal(lua, "__redis__err__handler"); /* Try to lookup the Lua function */ // 在 lua 中查找是否注册了此函数。这一句尝试将 funcname 入栈 lua_getglobal(lua, funcname); if (lua_isnil(lua,-1)) { // funcname 在 lua 中不存在 // 将 nil 出栈 lua_pop(lua,1); /* remove the nil from the stack */ // 已经确定 funcname 在 lua 中没有定义,需要创建 /* Function not defined... let's define it if we have the * body of the function. If this is an EVALSHA call we can just * return an error. */ if (evalsha) { lua_pop(lua,1); /* remove the error handler from the stack. */ addReply(c, shared.noscripterr); return; } // 创建 lua 函数 funcname // c->argv[1] 指向用户指定的 lua 脚本 if (luaCreateFunction(c,lua,funcname,c->argv[1]) == REDIS_ERR) { lua_pop(lua,1); /* remove the error handler from the stack. */ /* The error is sent to the client by luaCreateFunction() * itself when it returns REDIS_ERR. */ return; } // 现在 lua 中已经有 funcname 这个全局变量了,将其读取并入栈, // 准备调用 /* Now the following is guaranteed to return non nil */ lua_getglobal(lua, funcname); redisAssert(!lua_isnil(lua,-1)); } // 设置参数,包括键和值 /* Populate the argv and keys table accordingly to the arguments that * EVAL received. */ luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys); luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys); // 选择数据集,lua_client 有专用的数据集 /* Select the right DB in the context of the Lua client */ selectDb(server.lua_client,c->db->id); // 设置超时回调函数,以在 lua 脚本执行过长时间的时候停止脚本的运行 /* 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. */ server.lua_caller = c; server.lua_time_start = ustime()/1000; server.lua_kill = 0; if (server.lua_time_limit > 0 && server.masterhost == NULL) { // 当 lua 解释器执行了 100000,luaMaskCountHook() 会被调用 lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000); delhook = 1; } // 现在,我们确定函数已经注册成功了.可以直接调用 lua 脚本 /* At this point whether this script was never seen before or if it was * already defined, we can call it. We have zero arguments and expect * a single return value. */ 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 */ // 如果已经超时了,说明 lua 脚本已在超时后背 SCRPIT KILL 终结了 // 恢复监听发送 lua 脚本命令的客户端 if (server.lua_timedout) { server.lua_timedout = 0; /* Restore the readable handler that was unregistered when the * script timeout was detected. */ aeCreateFileEvent(server.el,c->fd,AE_READABLE, readQueryFromClient,c); } // lua_caller 置空 server.lua_caller = NULL; // 执行 lua 脚本用的是 lua 脚本执行专用的数据集。现在恢复原有的数据集 selectDb(c,server.lua_client->db->id); /* set DB ID from Lua client */ // Garbage collection 垃圾回收 lua_gc(lua,LUA_GCSTEP,1); // 处理执行 lua 脚本的错误 if (err) { // 告知客户端 addReplyErrorFormat(c,"Error running script (call to %s): %s\n", funcname, lua_tostring(lua,-1)); lua_pop(lua,2); /* Consume the Lua reply and remove error handler. */ // 成功了 } else { /* On success convert the Lua return value into Redis protocol, and * send it to * the client. */ luaReplyToRedisReply(c,lua); /* Convert and consume the reply. */ lua_pop(lua,1); /* Remove the error handler. */ } // 将 lua 脚本发布到主从复制上,并写入 AOF 文件 /* EVALSHA should be propagated to Slave and AOF file as full EVAL, unless * we are sure that the script was already in the context of all the * attached slaves *and* the current AOF file if enabled. * * To do so we use a cache of SHA1s of scripts that we already propagated * as full EVAL, that's called the Replication Script Cache. * * For repliation, everytime a new slave attaches to the master, we need to * flush our cache of scripts that can be replicated as EVALSHA, while * for AOF we need to do so every time we rewrite the AOF file. */ if (evalsha) { if (!replicationScriptCacheExists(c->argv[1]->ptr)) { /* This script is not in our script cache, replicate it as * EVAL, then add it into the script cache, as from now on * slaves and AOF know about it. */ // 从 server.lua_scripts 获取 lua 脚本 // c->argv[1]->ptr 是 SHA1 robj *script = dictFetchValue(server.lua_scripts,c->argv[1]->ptr); // 添加到主从复制专用的脚本缓存中 replicationScriptCacheAdd(c->argv[1]->ptr); redisAssertWithInfo(c,NULL,script != NULL); // 重写命令 // 参数 1 为:EVAL // 参数 2 为:lua_script // 如此一来在执行 AOF 持久化和主从复制的时候,lua 脚本就能得到传播 rewriteClientCommandArgument(c,0, resetRefCount(createStringObject("EVAL",4))); rewriteClientCommandArgument(c,1,script); } } }