整體流程的介紹
memcached的整體結構採用的是多線程框架,這也是爲什麼memcached中很多鎖的原因,我個人對這一點不是很喜歡,一是因爲看起來很複雜,二是感覺性能也會因此而受到影響;memcached的多線程框架採用的是一對多的策略,其中主線程主要用來監聽新的網絡連接,工作線程用來處理請求,主線程和工作線程是通過管道pipe來實現的。
在每個請求過程中,請求以及響應的過程都是通過一個狀態機來實現的(這裏面應該強調下,由於網絡部分不是我想學習的重點,但是還是應該說明memcached的網絡部分仍然是一個不錯的開源代碼學習,這裏面涉及的協議類型,網絡數據的讀取和發送等等,都是很好的參考材料)。
同時,還應該說明下memcached所有狀態的更新都是通過libevent的事件模型來驅動的,這個通過與前面提到的狀態機結合顯得更加有意義;每個工作線程都會有自己的事件驅動。
運轉流程圖
整體流程細分
下面主要是結合代碼仔細介紹下整體的流程:
監聽新請求
在main函數中,會調用server_sockets函數來初始化監聽端口,設置監聽socket,同時通過conn_new函數創建一個listen_conn_add連接, 其初始狀態是conn_listening, 事件驅動是main_base, 並且是永久設置,用於一直監聽新的連接。
main函數中:
errno = 0;
if (settings.port && server_sockets(settings.port, tcp_transport,
portnumber_file)) { //main函數中調用設置
vperror("failed to listen on TCP port %d", settings.port);
exit(EX_OSERR);
}
server_socket中:
if (IS_UDP(transport)) {
int c;
for (c = 0; c < settings.num_threads_per_udp; c++) {
/* Allocate one UDP file descriptor per worker thread;
* this allows "stats conns" to separately list multiple
* parallel UDP requests in progress.
*
* The dispatch code round-robins new connection requests
* among threads, so this is guaranteed to assign one
* FD to each thread.
*/
int per_thread_fd = c ? dup(sfd) : sfd;
dispatch_conn_new(per_thread_fd, conn_read,
EV_READ | EV_PERSIST,
UDP_READ_BUFFER_SIZE, transport);
}
} else {
if (!(listen_conn_add = conn_new(sfd, conn_listening,
EV_READ | EV_PERSIST, 1,
transport, main_base))) {
fprintf(stderr, "failed to create listening connection\n");
exit(EXIT_FAILURE);
}
listen_conn_add->next = listen_conn;
listen_conn = listen_conn_add;
}
分配新連接
在conn_new函數中,主要是獲取一個連接,然後對conn結構體進行初始化,初始化完成後,註冊監聽事件,事件回調函數爲event_handler函數。
conn *conn_new(const int sfd, enum conn_states init_state,
const int event_flags,
const int read_buffer_size, enum network_transport transport,
struct event_base *base) {
conn *c;
assert(sfd >= 0 && sfd < max_fds);
c = conns[sfd]; //從連接池獲取,沒有則新建
if (NULL == c) {
if (!(c = (conn *)calloc(1, sizeof(conn)))) {
STATS_LOCK();
stats.malloc_fails++;
STATS_UNLOCK();
fprintf(stderr, "Failed to allocate connection object\n");
return NULL;
}
MEMCACHED_CONN_CREATE(c);
//以下是初始化conn結構體
c->rbuf = c->wbuf = 0;
c->ilist = 0;
c->suffixlist = 0;
c->iov = 0;
c->msglist = 0;
c->hdrbuf = 0;
c->rsize = read_buffer_size;
c->wsize = DATA_BUFFER_SIZE;
c->isize = ITEM_LIST_INITIAL;
c->suffixsize = SUFFIX_LIST_INITIAL;
c->iovsize = IOV_LIST_INITIAL;
c->msgsize = MSG_LIST_INITIAL;
c->hdrsize = 0;
c->rbuf = (char *)malloc((size_t)c->rsize);
c->wbuf = (char *)malloc((size_t)c->wsize);
c->ilist = (item **)malloc(sizeof(item *) * c->isize);
c->suffixlist = (char **)malloc(sizeof(char *) * c->suffixsize);
c->iov = (struct iovec *)malloc(sizeof(struct iovec) * c->iovsize);
c->msglist = (struct msghdr *)malloc(sizeof(struct msghdr) * c->msgsize);
if (c->rbuf == 0 || c->wbuf == 0 || c->ilist == 0 || c->iov == 0 ||
c->msglist == 0 || c->suffixlist == 0) {
conn_free(c);
STATS_LOCK();
stats.malloc_fails++;
STATS_UNLOCK();
fprintf(stderr, "Failed to allocate buffers for connection\n");
return NULL;
}
STATS_LOCK();
stats.conn_structs++;
STATS_UNLOCK();
c->sfd = sfd;
conns[sfd] = c;
}
c->transport = transport;
c->protocol = settings.binding_protocol;
/* unix socket mode doesn't need this, so zeroed out. but why
* is this done for every command? presumably for UDP
* mode. */
if (!settings.socketpath) {
c->request_addr_size = sizeof(c->request_addr);
} else {
c->request_addr_size = 0;
}
if (transport == tcp_transport && init_state == conn_new_cmd) { //這裏面是工作線程處理新連接的初始狀態
if (getpeername(sfd, (struct sockaddr *) &c->request_addr,
&c->request_addr_size)) {
perror("getpeername");
memset(&c->request_addr, 0, sizeof(c->request_addr));
}
}
if (settings.verbose > 1) {
if (init_state == conn_listening) { //主線程初始狀態
fprintf(stderr, "<%d server listening (%s)\n", sfd,
prot_text(c->protocol));
} else if (IS_UDP(transport)) {
fprintf(stderr, "<%d server listening (udp)\n", sfd);
} else if (c->protocol == negotiating_prot) {
fprintf(stderr, "<%d new auto-negotiating client connection\n",
sfd);
} else if (c->protocol == ascii_prot) {
fprintf(stderr, "<%d new ascii client connection.\n", sfd);
} else if (c->protocol == binary_prot) {
fprintf(stderr, "<%d new binary client connection.\n", sfd);
} else {
fprintf(stderr, "<%d new unknown (%d) client connection\n",
sfd, c->protocol);
assert(false);
}
}
c->state = init_state;
c->rlbytes = 0;
c->cmd = -1;
c->rbytes = c->wbytes = 0;
c->wcurr = c->wbuf;
c->rcurr = c->rbuf;
c->ritem = 0;
c->icurr = c->ilist;
c->suffixcurr = c->suffixlist;
c->ileft = 0;
c->suffixleft = 0;
c->iovused = 0;
c->msgcurr = 0;
c->msgused = 0;
c->authenticated = false;
c->write_and_go = init_state;
c->write_and_free = 0;
c->item = 0;
c->noreply = false;
event_set(&c->event, sfd, event_flags, event_handler, (void *)c); //註冊時間,回調函數是event_handler
event_base_set(base, &c->event);
c->ev_flags = event_flags;
if (event_add(&c->event, 0) == -1) {
perror("event_add");
return NULL;
}
STATS_LOCK();
stats.curr_conns++;
stats.total_conns++;
STATS_UNLOCK();
MEMCACHED_CONN_ALLOCATE(c->sfd);
return c;
}
在event_handler函數中,主要是調用drive_machine函數進入網絡連接狀態機
void event_handler(const int fd, const short which, void *arg) {
conn *c;
c = (conn *)arg;
assert(c != NULL);
c->which = which;
/* sanity */
if (fd != c->sfd) {
if (settings.verbose > 0)
fprintf(stderr, "Catastrophic: event fd doesn't match conn fd!\n");
conn_close(c);
return;
}
drive_machine(c);
/* wait for next event */
return;
}
在drive_machine函數中,在這裏會根據當前連接的狀態進行對應的處理,前面提過當前的初始狀態是conn_listening,先分析這個狀態,後面處理的命令到時再分析;連接狀態是conn_listening時,首先調用accept函數接受一個新連接並獲取連接socket描述符,排除異常問題後,會調用dispatch_conn_new函數將該新連接分配到對應線程上去
case conn_listening:
addrlen = sizeof(addr);
#ifdef HAVE_ACCEPT4
if (use_accept4) {
sfd = accept4(c->sfd, (struct sockaddr *)&addr, &addrlen, SOCK_NONBLOCK);
} else {
sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen);
}
#else
sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen); //從監聽連接接受新請求
#endif
if (sfd == -1) {
if (use_accept4 && errno == ENOSYS) {
use_accept4 = 0;
continue;
}
perror(use_accept4 ? "accept4()" : "accept()");
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* these are transient, so don't log anything */
stop = true;
} else if (errno == EMFILE) {
if (settings.verbose > 0)
fprintf(stderr, "Too many open connections\n");
accept_new_conns(false);
stop = true;
} else {
perror("accept()");
stop = true;
}
break;
}
if (!use_accept4) {
if (fcntl(sfd, F_SETFL, fcntl(sfd, F_GETFL) | O_NONBLOCK) < 0) {
perror("setting O_NONBLOCK");
close(sfd);
break;
}
}
if (settings.maxconns_fast &&
stats.curr_conns + stats.reserved_fds >= settings.maxconns - 1) {
str = "ERROR Too many open connections\r\n";
res = write(sfd, str, strlen(str));
close(sfd);
STATS_LOCK();
stats.rejected_conns++;
STATS_UNLOCK();
} else {
dispatch_conn_new(sfd, conn_new_cmd, EV_READ | EV_PERSIST,
DATA_BUFFER_SIZE, tcp_transport); //分配新連接,初始狀態改爲conn_new_cmd
}
stop = true;
break;
在dispatch_conn_new函數中,會調用cqi_new新建一個conn結構,並使用簡單的輪詢策略分配一個線程給該連接,並將連接狀態conn_new_cmd寫到conn結構中去,然後將該結構放到該線程的new_conn_queue中去,並通過該線程的管道發送一個’c’字符通知線程接收該連接
void dispatch_conn_new(int sfd, enum conn_states init_state, int event_flags,
int read_buffer_size, enum network_transport transport) {
CQ_ITEM *item = cqi_new(); //這個item不是之前說的數據item,是指連接隊列的一個conn結構
char buf[1];
if (item == NULL) {
close(sfd);
/* given that malloc failed this may also fail, but let's try */
fprintf(stderr, "Failed to allocate memory for connection object\n");
return ;
}
//下面輪詢選擇一個線程用來處理新連接
int tid = (last_thread + 1) % settings.num_threads;
LIBEVENT_THREAD *thread = threads + tid;
last_thread = tid;
item->sfd = sfd;
item->init_state = init_state;
item->event_flags = event_flags;
item->read_buffer_size = read_buffer_size;
item->transport = transport;
cq_push(thread->new_conn_queue, item); //將該item加入到該線程的新連接隊列中去
MEMCACHED_CONN_DISPATCH(sfd, thread->thread_id);
buf[0] = 'c'; //下面是通過管道發送一個‘c’字符給對應的工作線程
if (write(thread->notify_send_fd, buf, 1) != 1) {
perror("Writing to thread notify pipe");
}
}
由於每個線程在創建的時候都會給自己的事件驅動註冊一個讀取管道的事件,當調度線程發送一個c字符時,對應線程的事件會被喚醒,並執行回調函數thread_libevent_process;在thread_libevent_process函數中,會從該線程的new_conn_queue獲取剛纔添加的那個conn結構,根據結構內容再次調用conn_new函數,這樣就會再次註冊了監聽事件,同樣回調函數是event_handler,不同的是此時的初始狀態是conn_new_cmd
static void thread_libevent_process(int fd, short which, void *arg) {
LIBEVENT_THREAD *me = arg;
CQ_ITEM *item;
char buf[1];
if (read(fd, buf, 1) != 1)
if (settings.verbose > 0)
fprintf(stderr, "Can't read from libevent pipe\n");
switch (buf[0]) {
case 'c':
item = cq_pop(me->new_conn_queue); //從新連接隊列中提取出新連接
if (NULL != item) {
conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
item->read_buffer_size, item->transport, me->base); //再次調用conn_new函數
if (c == NULL) {
if (IS_UDP(item->transport)) {
fprintf(stderr, "Can't listen for events on UDP socket\n");
exit(1);
} else {
if (settings.verbose > 0) {
fprintf(stderr, "Can't listen for events on fd %d\n",
item->sfd);
}
close(item->sfd);
}
} else {
c->thread = me;
}
cqi_free(item); //釋放連接到連接池
}
break;
/* we were told to pause and report in */
case 'p':
register_thread_initialized();
break;
}
}
處理請求
當連接狀態是conn_new_cmd時, 首先檢查當前請求次數是否超標,如果沒有超標則調用reset_cmd_handler函數,超標的話會調用update_event函數,這部分後面再說,先按照正常流程進行;在reset_cmd_handler函數時,新命令清空當前item,並調用conn_shrink函數對當前連接中的buf進行縮減,這時候如果讀取的數據還有未解析的,設置連接狀態爲conn_parse_cmd,否則設置爲conn_waiting;
case conn_new_cmd:
/* Only process nreqs at a time to avoid starving other
connections */
--nreqs;
if (nreqs >= 0) {
reset_cmd_handler(c);
} else {
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.conn_yields++;
pthread_mutex_unlock(&c->thread->stats.mutex);
if (c->rbytes > 0) {
/* We have already read in data into the input buffer,
so libevent will most likely not signal read events
on the socket (unless more data is available. As a
hack we should just put in a request to write data,
because that should be possible ;-)
*/
if (!update_event(c, EV_WRITE | EV_PERSIST)) {
if (settings.verbose > 0)
fprintf(stderr, "Couldn't update event\n");
conn_set_state(c, conn_closing);
break;
}
}
stop = true;
}
break;
static void reset_cmd_handler(conn *c) {
c->cmd = -1;
c->substate = bin_no_state;
if(c->item != NULL) {
item_remove(c->item);
c->item = NULL;
}
conn_shrink(c);
if (c->rbytes > 0) {
conn_set_state(c, conn_parse_cmd);
} else {
conn_set_state(c, conn_waiting);
}
}
連接狀態是conn_parse_cmd時,會調用try_read_command函數來解析讀取內容,如果數據不足,會修改連接狀態爲conn_waiting,繼續等待讀取數據;
try_read_command函數中,首先根據連接使用的協議解析數據,根據使用的協議類型不同分別調用dispatch_bin_command或者process_command來處理命令,什麼協議不影響本質處理,這裏按照process_command來繼續;
static int try_read_command(conn *c) {
assert(c != NULL);
assert(c->rcurr <= (c->rbuf + c->rsize));
assert(c->rbytes > 0);
if (c->protocol == negotiating_prot || c->transport == udp_transport) {
if ((unsigned char)c->rbuf[0] == (unsigned char)PROTOCOL_BINARY_REQ) {
c->protocol = binary_prot;
} else {
c->protocol = ascii_prot;
}
if (settings.verbose > 1) {
fprintf(stderr, "%d: Client using the %s protocol\n", c->sfd,
prot_text(c->protocol));
}
}
if (c->protocol == binary_prot) {
/* Do we have the complete packet header? */
if (c->rbytes < sizeof(c->binary_header)) {
/* need more data! */
return 0;
} else {
#ifdef NEED_ALIGN
if (((long)(c->rcurr)) % 8 != 0) {
/* must realign input buffer */
memmove(c->rbuf, c->rcurr, c->rbytes);
c->rcurr = c->rbuf;
if (settings.verbose > 1) {
fprintf(stderr, "%d: Realign input buffer\n", c->sfd);
}
}
#endif
protocol_binary_request_header* req;
req = (protocol_binary_request_header*)c->rcurr;
if (settings.verbose > 1) {
/* Dump the packet before we convert it to host order */
int ii;
fprintf(stderr, "<%d Read binary protocol data:", c->sfd);
for (ii = 0; ii < sizeof(req->bytes); ++ii) {
if (ii % 4 == 0) {
fprintf(stderr, "\n<%d ", c->sfd);
}
fprintf(stderr, " 0x%02x", req->bytes[ii]);
}
fprintf(stderr, "\n");
}
c->binary_header = *req;
c->binary_header.request.keylen = ntohs(req->request.keylen);
c->binary_header.request.bodylen = ntohl(req->request.bodylen);
c->binary_header.request.cas = ntohll(req->request.cas);
if (c->binary_header.request.magic != PROTOCOL_BINARY_REQ) {
if (settings.verbose) {
fprintf(stderr, "Invalid magic: %x\n",
c->binary_header.request.magic);
}
conn_set_state(c, conn_closing);
return -1;
}
c->msgcurr = 0;
c->msgused = 0;
c->iovused = 0;
if (add_msghdr(c) != 0) {
out_of_memory(c,
"SERVER_ERROR Out of memory allocating headers");
return 0;
}
c->cmd = c->binary_header.request.opcode;
c->keylen = c->binary_header.request.keylen;
c->opaque = c->binary_header.request.opaque;
/* clear the returned cas value */
c->cas = 0;
dispatch_bin_command(c);
c->rbytes -= sizeof(c->binary_header);
c->rcurr += sizeof(c->binary_header);
}
} else {
char *el, *cont;
if (c->rbytes == 0)
return 0;
el = memchr(c->rcurr, '\n', c->rbytes);
if (!el) {
if (c->rbytes > 1024) {
/*
* We didn't have a '\n' in the first k. This _has_ to be a
* large multiget, if not we should just nuke the connection.
*/
char *ptr = c->rcurr;
while (*ptr == ' ') { /* ignore leading whitespaces */
++ptr;
}
if (ptr - c->rcurr > 100 ||
(strncmp(ptr, "get ", 4) && strncmp(ptr, "gets ", 5))) {
conn_set_state(c, conn_closing);
return 1;
}
}
return 0;
}
cont = el + 1;
if ((el - c->rcurr) > 1 && *(el - 1) == '\r') {
el--;
}
*el = '\0';
assert(cont <= (c->rcurr + c->rbytes));
c->last_cmd_time = current_time;
process_command(c, c->rcurr); //處理
c->rbytes -= (cont - c->rcurr);
c->rcurr = cont;
assert(c->rcurr <= (c->rbuf + c->rsize));
}
return 1;
}
處理命令
在process_command函數中,會根據命令的不同調用不同的處理函數,對應關係如下:
get <—–> process_get_command
bget <—–> process_get_command
add <—–> process_update_command
set <—–> process_update_command
replace <—–> process_update_command
prepend <—–> process_update_command
append <—–> process_update_command
cas <—–> process_update_command
incr <—–> process_arithmetic_command
gets <—–> process_get_command
decr <—–> process_arithmetic_command
delete <—–> process_delete_command
touch <—–> process_touch_command
stats <—–> process_stat
flush_all <—->
version <—–> 返回版本信息
quit <—–> 退出,關閉連接
slabs + reassign <—–> 重新分配slabs
slabs + automove <—–> 移動內存
lru_crawler + crawl <—-> LRU操作
static void process_command(conn *c, char *command) {
token_t tokens[MAX_TOKENS];
size_t ntokens;
int comm;
assert(c != NULL);
MEMCACHED_PROCESS_COMMAND_START(c->sfd, c->rcurr, c->rbytes);
if (settings.verbose > 1)
fprintf(stderr, "<%d %s\n", c->sfd, command);
/*
* for commands set/add/replace, we build an item and read the data
* directly into it, then continue in nread_complete().
*/
c->msgcurr = 0;
c->msgused = 0;
c->iovused = 0;
if (add_msghdr(c) != 0) {
out_of_memory(c, "SERVER_ERROR out of memory preparing response");
return;
}
ntokens = tokenize_command(command, tokens, MAX_TOKENS);
if (ntokens >= 3 &&
((strcmp(tokens[COMMAND_TOKEN].value, "get") == 0) ||
(strcmp(tokens[COMMAND_TOKEN].value, "bget") == 0))) {
process_get_command(c, tokens, ntokens, false);
} else if ((ntokens == 6 || ntokens == 7) &&
((strcmp(tokens[COMMAND_TOKEN].value, "add") == 0 && (comm = NREAD_ADD)) ||
(strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET)) ||
(strcmp(tokens[COMMAND_TOKEN].value, "replace") == 0 && (comm = NREAD_REPLACE)) ||
(strcmp(tokens[COMMAND_TOKEN].value, "prepend") == 0 && (comm = NREAD_PREPEND)) ||
(strcmp(tokens[COMMAND_TOKEN].value, "append") == 0 && (comm = NREAD_APPEND)) )) {
process_update_command(c, tokens, ntokens, comm, false);
} else if ((ntokens == 7 || ntokens == 8) && (strcmp(tokens[COMMAND_TOKEN].value, "cas") == 0 && (comm = NREAD_CAS))) {
process_update_command(c, tokens, ntokens, comm, true);
} else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "incr") == 0)) {
process_arithmetic_command(c, tokens, ntokens, 1);
} else if (ntokens >= 3 && (strcmp(tokens[COMMAND_TOKEN].value, "gets") == 0)) {
process_get_command(c, tokens, ntokens, true);
} else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0)) {
process_arithmetic_command(c, tokens, ntokens, 0);
} else if (ntokens >= 3 && ntokens <= 5 && (strcmp(tokens[COMMAND_TOKEN].value, "delete") == 0)) {
process_delete_command(c, tokens, ntokens);
} else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "touch") == 0)) {
process_touch_command(c, tokens, ntokens);
} else if (ntokens >= 2 && (strcmp(tokens[COMMAND_TOKEN].value, "stats") == 0)) {
process_stat(c, tokens, ntokens);
} else if (ntokens >= 2 && ntokens <= 4 && (strcmp(tokens[COMMAND_TOKEN].value, "flush_all") == 0)) {
time_t exptime = 0;
rel_time_t new_oldest = 0;
set_noreply_maybe(c, tokens, ntokens);
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.flush_cmds++;
pthread_mutex_unlock(&c->thread->stats.mutex);
if (!settings.flush_enabled) {
// flush_all is not allowed but we log it on stats
out_string(c, "CLIENT_ERROR flush_all not allowed");
return;
}
if (ntokens != (c->noreply ? 3 : 2)) {
exptime = strtol(tokens[1].value, NULL, 10);
if(errno == ERANGE) {
out_string(c, "CLIENT_ERROR bad command line format");
return;
}
}
/*
If exptime is zero realtime() would return zero too, and
realtime(exptime) - 1 would overflow to the max unsigned
value. So we process exptime == 0 the same way we do when
no delay is given at all.
*/
if (exptime > 0) {
new_oldest = realtime(exptime);
} else { /* exptime == 0 */
new_oldest = current_time;
}
if (settings.use_cas) {
settings.oldest_live = new_oldest - 1;
if (settings.oldest_live <= current_time)
settings.oldest_cas = get_cas_id();
} else {
settings.oldest_live = new_oldest;
}
out_string(c, "OK");
return;
} else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "version") == 0)) {
out_string(c, "VERSION " VERSION);
} else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "quit") == 0)) {
conn_set_state(c, conn_closing);
} else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "shutdown") == 0)) {
if (settings.shutdown_command) {
conn_set_state(c, conn_closing);
raise(SIGINT);
} else {
out_string(c, "ERROR: shutdown not enabled");
}
} else if (ntokens > 1 && strcmp(tokens[COMMAND_TOKEN].value, "slabs") == 0) {
if (ntokens == 5 && strcmp(tokens[COMMAND_TOKEN + 1].value, "reassign") == 0) {
int src, dst, rv;
if (settings.slab_reassign == false) {
out_string(c, "CLIENT_ERROR slab reassignment disabled");
return;
}
src = strtol(tokens[2].value, NULL, 10);
dst = strtol(tokens[3].value, NULL, 10);
if (errno == ERANGE) {
out_string(c, "CLIENT_ERROR bad command line format");
return;
}
rv = slabs_reassign(src, dst);
switch (rv) {
case REASSIGN_OK:
out_string(c, "OK");
break;
case REASSIGN_RUNNING:
out_string(c, "BUSY currently processing reassign request");
break;
case REASSIGN_BADCLASS:
out_string(c, "BADCLASS invalid src or dst class id");
break;
case REASSIGN_NOSPARE:
out_string(c, "NOSPARE source class has no spare pages");
break;
case REASSIGN_SRC_DST_SAME:
out_string(c, "SAME src and dst class are identical");
break;
}
return;
} else if (ntokens == 4 &&
(strcmp(tokens[COMMAND_TOKEN + 1].value, "automove") == 0)) {
process_slabs_automove_command(c, tokens, ntokens);
} else {
out_string(c, "ERROR");
}
} else if (ntokens > 1 && strcmp(tokens[COMMAND_TOKEN].value, "lru_crawler") == 0) {
if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN + 1].value, "crawl") == 0) {
int rv;
if (settings.lru_crawler == false) {
out_string(c, "CLIENT_ERROR lru crawler disabled");
return;
}
rv = lru_crawler_crawl(tokens[2].value);
switch(rv) {
case CRAWLER_OK:
out_string(c, "OK");
break;
case CRAWLER_RUNNING:
out_string(c, "BUSY currently processing crawler request");
break;
case CRAWLER_BADCLASS:
out_string(c, "BADCLASS invalid class id");
break;
case CRAWLER_NOTSTARTED:
out_string(c, "NOTSTARTED no items to crawl");
break;
}
return;
} else if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN + 1].value, "tocrawl") == 0) {
uint32_t tocrawl;
if (!safe_strtoul(tokens[2].value, &tocrawl)) {
out_string(c, "CLIENT_ERROR bad command line format");
return;
}
settings.lru_crawler_tocrawl = tocrawl;
out_string(c, "OK");
return;
} else if (ntokens == 4 && strcmp(tokens[COMMAND_TOKEN + 1].value, "sleep") == 0) {
uint32_t tosleep;
if (!safe_strtoul(tokens[2].value, &tosleep)) {
out_string(c, "CLIENT_ERROR bad command line format");
return;
}
if (tosleep > 1000000) {
out_string(c, "CLIENT_ERROR sleep must be one second or less");
return;
}
settings.lru_crawler_sleep = tosleep;
out_string(c, "OK");
return;
} else if (ntokens == 3) {
if ((strcmp(tokens[COMMAND_TOKEN + 1].value, "enable") == 0)) {
if (start_item_crawler_thread() == 0) {
out_string(c, "OK");
} else {
out_string(c, "ERROR failed to start lru crawler thread");
}
} else if ((strcmp(tokens[COMMAND_TOKEN + 1].value, "disable") == 0)) {
if (stop_item_crawler_thread() == 0) {
out_string(c, "OK");
} else {
out_string(c, "ERROR failed to stop lru crawler thread");
}
} else {
out_string(c, "ERROR");
}
return;
} else {
out_string(c, "ERROR");
}
} else if ((ntokens == 3 || ntokens == 4) && (strcmp(tokens[COMMAND_TOKEN].value, "verbosity") == 0)) {
process_verbosity_command(c, tokens, ntokens);
} else {
out_string(c, "ERROR");
}
return;
}