| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include "lmserver.h" |
| #include "srilm.h" |
| #include <sys/stat.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <signal.h> |
| #include <sys/resource.h> |
| #include <sys/uio.h> |
|
|
| |
| |
| #ifndef _P1003_1B_VISIBLE |
| #define _P1003_1B_VISIBLE |
| #endif |
| |
| #ifndef __need_IOV_MAX |
| #define __need_IOV_MAX |
| #endif |
| #include <pwd.h> |
| #include <sys/mman.h> |
| #include <fcntl.h> |
| #include <netinet/tcp.h> |
| #include <arpa/inet.h> |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <time.h> |
| #include <assert.h> |
| #include <limits.h> |
|
|
| #ifdef HAVE_MALLOC_H |
| |
| #ifndef __OpenBSD__ |
| #include <malloc.h> |
| #endif |
| #endif |
|
|
| |
| #ifndef IOV_MAX |
| #if defined(__FreeBSD__) || defined(__APPLE__) |
| # define IOV_MAX 1024 |
| #endif |
| #endif |
|
|
| |
| |
| |
| static void drive_machine(conn *c); |
| static int new_socket(struct addrinfo *ai); |
| static int server_socket(const int port, const bool is_udp); |
| static int try_read_command(conn *c); |
| static int try_read_network(conn *c); |
| static int try_read_udp(conn *c); |
|
|
| |
| static void stats_reset(void); |
| static void stats_init(void); |
|
|
| |
| static void settings_init(void); |
|
|
| |
| static void event_handler(const int fd, const short which, void *arg); |
| static void conn_close(conn *c); |
| static void conn_init(void); |
| static void accept_new_conns(const bool do_accept); |
| static bool update_event(conn *c, const int new_flags); |
| static void complete_nread(conn *c); |
| static void process_command(conn *c, char *command); |
| static int transmit(conn *c); |
| static int ensure_iov_space(conn *c); |
| static int add_iov(conn *c, const void *buf, int len); |
| static int add_msghdr(conn *c); |
|
|
| |
| static void set_current_time(void); |
| |
| |
|
|
| static void conn_free(conn *c); |
|
|
| |
| struct stats stats; |
| struct settings settings; |
|
|
| |
| static item **todelete = NULL; |
| static int delcurr; |
| static int deltotal; |
| static conn *listen_conn = NULL; |
| static struct event_base *main_base; |
|
|
| #define TRANSMIT_COMPLETE 0 |
| #define TRANSMIT_INCOMPLETE 1 |
| #define TRANSMIT_SOFT_ERROR 2 |
| #define TRANSMIT_HARD_ERROR 3 |
|
|
| static int *buckets = 0; |
|
|
| #define REALTIME_MAXDELTA 60*60*24*30 |
| |
| |
| |
| |
| |
| static rel_time_t realtime(const time_t exptime) { |
| |
|
|
| if (exptime == 0) return 0; |
|
|
| if (exptime > REALTIME_MAXDELTA) { |
| |
| |
| |
| |
| |
| |
| if (exptime <= stats.started) |
| return (rel_time_t)1; |
| return (rel_time_t)(exptime - stats.started); |
| } else { |
| return (rel_time_t)(exptime + current_time); |
| } |
| } |
|
|
| static void stats_init(void) { |
| stats.curr_items = stats.total_items = stats.curr_conns = stats.total_conns = stats.conn_structs = 0; |
| stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = stats.evictions = 0; |
| stats.curr_bytes = stats.bytes_read = stats.bytes_written = 0; |
|
|
| |
| |
| |
| |
| stats.started = time(0) - 2; |
| } |
|
|
| static void stats_reset(void) { |
| STATS_LOCK(); |
| stats.total_items = stats.total_conns = 0; |
| stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = stats.evictions = 0; |
| stats.bytes_read = stats.bytes_written = 0; |
| STATS_UNLOCK(); |
| } |
|
|
| static void settings_init(void) { |
| settings.srilm = NULL; |
| settings.srilm_order = 3; |
| settings.access=0700; |
| settings.port = 11211; |
| settings.udpport = 0; |
| |
| settings.inter = NULL; |
| settings.maxbytes = 64 * 1024 * 1024; |
| settings.maxconns = 1024; |
| settings.verbose = 0; |
| settings.oldest_live = 0; |
| settings.evict_to_free = 1; |
| settings.socketpath = NULL; |
| settings.managed = false; |
| settings.factor = 1.25; |
| settings.chunk_size = 48; |
| #ifdef USE_THREADS |
| settings.num_threads = 4; |
| #else |
| settings.num_threads = 1; |
| #endif |
| settings.detail_enabled = 0; |
| } |
|
|
| |
| |
| static bool item_delete_lock_over (item *it) { |
| assert(it->it_flags & ITEM_DELETED); |
| return (current_time >= it->exptime); |
| } |
|
|
| |
| |
| |
| |
| |
| static int add_msghdr(conn *c) |
| { |
| struct msghdr *msg; |
|
|
| assert(c != NULL); |
|
|
| if (c->msgsize == c->msgused) { |
| msg = realloc(c->msglist, c->msgsize * 2 * sizeof(struct msghdr)); |
| if (! msg) |
| return -1; |
| c->msglist = msg; |
| c->msgsize *= 2; |
| } |
|
|
| msg = c->msglist + c->msgused; |
|
|
| |
| |
| memset(msg, 0, sizeof(struct msghdr)); |
|
|
| msg->msg_iov = &c->iov[c->iovused]; |
|
|
| if (c->request_addr_size > 0) { |
| msg->msg_name = &c->request_addr; |
| msg->msg_namelen = c->request_addr_size; |
| } |
|
|
| c->msgbytes = 0; |
| c->msgused++; |
|
|
| if (c->udp) { |
| |
| return add_iov(c, NULL, UDP_HEADER_SIZE); |
| } |
|
|
| return 0; |
| } |
|
|
|
|
| |
| |
| |
|
|
| static conn **freeconns; |
| static int freetotal; |
| static int freecurr; |
|
|
|
|
| static void conn_init(void) { |
| freetotal = 200; |
| freecurr = 0; |
| if ((freeconns = (conn **)malloc(sizeof(conn *) * freetotal)) == NULL) { |
| fprintf(stderr, "malloc()\n"); |
| } |
| return; |
| } |
|
|
| |
| |
| |
| |
| conn *do_conn_from_freelist() { |
| conn *c; |
|
|
| if (freecurr > 0) { |
| c = freeconns[--freecurr]; |
| } else { |
| c = NULL; |
| } |
|
|
| return c; |
| } |
|
|
| |
| |
| |
| |
| bool do_conn_add_to_freelist(conn *c) { |
| if (freecurr < freetotal) { |
| freeconns[freecurr++] = c; |
| return false; |
| } else { |
| |
| conn **new_freeconns = realloc(freeconns, sizeof(conn *) * freetotal * 2); |
| if (new_freeconns) { |
| freetotal *= 2; |
| freeconns = new_freeconns; |
| freeconns[freecurr++] = c; |
| return false; |
| } |
| } |
| return true; |
| } |
|
|
| conn *conn_new(const int sfd, const int init_state, const int event_flags, |
| const int read_buffer_size, const bool is_udp, struct event_base *base) { |
| conn *c = conn_from_freelist(); |
|
|
| if (NULL == c) { |
| if (!(c = (conn *)calloc(1, sizeof(conn)))) { |
| fprintf(stderr, "calloc()\n"); |
| return NULL; |
| } |
|
|
| 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); |
| fprintf(stderr, "malloc()\n"); |
| return NULL; |
| } |
|
|
| STATS_LOCK(); |
| stats.conn_structs++; |
| STATS_UNLOCK(); |
| } |
|
|
| if (settings.verbose > 1) { |
| if (init_state == conn_listening) |
| fprintf(stderr, "<%d server listening\n", sfd); |
| else if (is_udp) |
| fprintf(stderr, "<%d server listening (udp)\n", sfd); |
| else |
| fprintf(stderr, "<%d new client connection\n", sfd); |
| } |
|
|
| c->sfd = sfd; |
| c->udp = is_udp; |
| c->state = init_state; |
| c->rlbytes = 0; |
| 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->write_and_go = conn_read; |
| c->write_and_free = 0; |
| c->item = 0; |
| c->bucket = -1; |
| c->gen = 0; |
|
|
| c->noreply = false; |
|
|
| event_set(&c->event, sfd, event_flags, event_handler, (void *)c); |
| event_base_set(base, &c->event); |
| c->ev_flags = event_flags; |
|
|
| if (event_add(&c->event, 0) == -1) { |
| if (conn_add_to_freelist(c)) { |
| conn_free(c); |
| } |
| perror("event_add"); |
| return NULL; |
| } |
|
|
| STATS_LOCK(); |
| stats.curr_conns++; |
| stats.total_conns++; |
| STATS_UNLOCK(); |
|
|
| return c; |
| } |
|
|
| static void conn_cleanup(conn *c) { |
| assert(c != NULL); |
|
|
| if (c->write_and_free) { |
| free(c->write_and_free); |
| c->write_and_free = 0; |
| } |
| } |
|
|
| |
| |
| |
| void conn_free(conn *c) { |
| if (c) { |
| if (c->hdrbuf) |
| free(c->hdrbuf); |
| if (c->msglist) |
| free(c->msglist); |
| if (c->rbuf) |
| free(c->rbuf); |
| if (c->wbuf) |
| free(c->wbuf); |
| if (c->ilist) |
| free(c->ilist); |
| if (c->suffixlist) |
| free(c->suffixlist); |
| if (c->iov) |
| free(c->iov); |
| free(c); |
| } |
| } |
|
|
| static void conn_close(conn *c) { |
| assert(c != NULL); |
|
|
| |
| event_del(&c->event); |
|
|
| if (settings.verbose > 1) |
| fprintf(stderr, "<%d connection closed.\n", c->sfd); |
|
|
| close(c->sfd); |
| accept_new_conns(true); |
| conn_cleanup(c); |
|
|
| |
| if (c->rsize > READ_BUFFER_HIGHWAT || conn_add_to_freelist(c)) { |
| conn_free(c); |
| } |
|
|
| STATS_LOCK(); |
| stats.curr_conns--; |
| STATS_UNLOCK(); |
|
|
| return; |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static void conn_shrink(conn *c) { |
| assert(c != NULL); |
|
|
| if (c->udp) |
| return; |
|
|
| if (c->rsize > READ_BUFFER_HIGHWAT && c->rbytes < DATA_BUFFER_SIZE) { |
| char *newbuf; |
|
|
| if (c->rcurr != c->rbuf) |
| memmove(c->rbuf, c->rcurr, (size_t)c->rbytes); |
|
|
| newbuf = (char *)realloc((void *)c->rbuf, DATA_BUFFER_SIZE); |
|
|
| if (newbuf) { |
| c->rbuf = newbuf; |
| c->rsize = DATA_BUFFER_SIZE; |
| } |
| |
| c->rcurr = c->rbuf; |
| } |
|
|
| if (c->isize > ITEM_LIST_HIGHWAT) { |
| item **newbuf = (item**) realloc((void *)c->ilist, ITEM_LIST_INITIAL * sizeof(c->ilist[0])); |
| if (newbuf) { |
| c->ilist = newbuf; |
| c->isize = ITEM_LIST_INITIAL; |
| } |
| |
| } |
|
|
| if (c->msgsize > MSG_LIST_HIGHWAT) { |
| struct msghdr *newbuf = (struct msghdr *) realloc((void *)c->msglist, MSG_LIST_INITIAL * sizeof(c->msglist[0])); |
| if (newbuf) { |
| c->msglist = newbuf; |
| c->msgsize = MSG_LIST_INITIAL; |
| } |
| |
| } |
|
|
| if (c->iovsize > IOV_LIST_HIGHWAT) { |
| struct iovec *newbuf = (struct iovec *) realloc((void *)c->iov, IOV_LIST_INITIAL * sizeof(c->iov[0])); |
| if (newbuf) { |
| c->iov = newbuf; |
| c->iovsize = IOV_LIST_INITIAL; |
| } |
| |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| static void conn_set_state(conn *c, int state) { |
| assert(c != NULL); |
|
|
| if (state != c->state) { |
| if (state == conn_read) { |
| conn_shrink(c); |
| |
| } |
| c->state = state; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static int ensure_iov_space(conn *c) { |
| assert(c != NULL); |
|
|
| if (c->iovused >= c->iovsize) { |
| int i, iovnum; |
| struct iovec *new_iov = (struct iovec *)realloc(c->iov, |
| (c->iovsize * 2) * sizeof(struct iovec)); |
| if (! new_iov) |
| return -1; |
| c->iov = new_iov; |
| c->iovsize *= 2; |
|
|
| |
| for (i = 0, iovnum = 0; i < c->msgused; i++) { |
| c->msglist[i].msg_iov = &c->iov[iovnum]; |
| iovnum += c->msglist[i].msg_iovlen; |
| } |
| } |
|
|
| return 0; |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| static int add_iov(conn *c, const void *buf, int len) { |
| struct msghdr *m; |
| int leftover; |
| bool limit_to_mtu; |
|
|
| assert(c != NULL); |
|
|
| do { |
| m = &c->msglist[c->msgused - 1]; |
|
|
| |
| |
| |
| |
| limit_to_mtu = c->udp || (1 == c->msgused); |
|
|
| |
| if (m->msg_iovlen == IOV_MAX || |
| (limit_to_mtu && c->msgbytes >= UDP_MAX_PAYLOAD_SIZE)) { |
| add_msghdr(c); |
| m = &c->msglist[c->msgused - 1]; |
| } |
|
|
| if (ensure_iov_space(c) != 0) |
| return -1; |
|
|
| |
| if (limit_to_mtu && len + c->msgbytes > UDP_MAX_PAYLOAD_SIZE) { |
| leftover = len + c->msgbytes - UDP_MAX_PAYLOAD_SIZE; |
| len -= leftover; |
| } else { |
| leftover = 0; |
| } |
|
|
| m = &c->msglist[c->msgused - 1]; |
| m->msg_iov[m->msg_iovlen].iov_base = (void *)buf; |
| m->msg_iov[m->msg_iovlen].iov_len = len; |
|
|
| c->msgbytes += len; |
| c->iovused++; |
| m->msg_iovlen++; |
|
|
| buf = ((char *)buf) + len; |
| len = leftover; |
| } while (leftover > 0); |
|
|
| return 0; |
| } |
|
|
|
|
| |
| |
| |
| static int build_udp_headers(conn *c) { |
| int i; |
| unsigned char *hdr; |
|
|
| assert(c != NULL); |
|
|
| if (c->msgused > c->hdrsize) { |
| void *new_hdrbuf; |
| if (c->hdrbuf) |
| new_hdrbuf = realloc(c->hdrbuf, c->msgused * 2 * UDP_HEADER_SIZE); |
| else |
| new_hdrbuf = malloc(c->msgused * 2 * UDP_HEADER_SIZE); |
| if (! new_hdrbuf) |
| return -1; |
| c->hdrbuf = (unsigned char *)new_hdrbuf; |
| c->hdrsize = c->msgused * 2; |
| } |
|
|
| hdr = c->hdrbuf; |
| for (i = 0; i < c->msgused; i++) { |
| c->msglist[i].msg_iov[0].iov_base = hdr; |
| c->msglist[i].msg_iov[0].iov_len = UDP_HEADER_SIZE; |
| *hdr++ = c->request_id / 256; |
| *hdr++ = c->request_id % 256; |
| *hdr++ = i / 256; |
| *hdr++ = i % 256; |
| *hdr++ = c->msgused / 256; |
| *hdr++ = c->msgused % 256; |
| *hdr++ = 0; |
| *hdr++ = 0; |
| assert((void *) hdr == (void *)c->msglist[i].msg_iov[0].iov_base + UDP_HEADER_SIZE); |
| } |
|
|
| return 0; |
| } |
|
|
|
|
| static void out_string(conn *c, const char *str) { |
| size_t len; |
|
|
| assert(c != NULL); |
|
|
| if (c->noreply) { |
| if (settings.verbose > 1) |
| fprintf(stderr, ">%d NOREPLY %s\n", c->sfd, str); |
| c->noreply = false; |
| conn_set_state(c, conn_read); |
| return; |
| } |
|
|
| if (settings.verbose > 1) |
| fprintf(stderr, ">%d %s\n", c->sfd, str); |
|
|
| len = strlen(str); |
| if ((len + 2) > c->wsize) { |
| |
| str = "SERVER_ERROR output line too long"; |
| len = strlen(str); |
| } |
|
|
| memcpy(c->wbuf, str, len); |
| memcpy(c->wbuf + len, "\r\n", 2); |
| c->wbytes = len + 2; |
| c->wcurr = c->wbuf; |
|
|
| conn_set_state(c, conn_write); |
| c->write_and_go = conn_read; |
| return; |
| } |
|
|
| typedef struct token_s { |
| char *value; |
| size_t length; |
| } token_t; |
|
|
| #define COMMAND_TOKEN 0 |
| #define SUBCOMMAND_TOKEN 1 |
| #define KEY_TOKEN 1 |
| #define KEY_MAX_LENGTH 250 |
|
|
| #define MAX_TOKENS 8 |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static size_t tokenize_command(char *command, token_t *tokens, const size_t max_tokens) { |
| char *s, *e; |
| size_t ntokens = 0; |
|
|
| assert(command != NULL && tokens != NULL && max_tokens > 1); |
|
|
| for (s = e = command; ntokens < max_tokens - 1; ++e) { |
| if (*e == ' ') { |
| if (s != e) { |
| tokens[ntokens].value = s; |
| tokens[ntokens].length = e - s; |
| ntokens++; |
| *e = '\0'; |
| } |
| s = e + 1; |
| } |
| else if (*e == '\0') { |
| if (s != e) { |
| tokens[ntokens].value = s; |
| tokens[ntokens].length = e - s; |
| ntokens++; |
| } |
|
|
| break; |
| } |
| } |
|
|
| |
| |
| |
| |
| tokens[ntokens].value = *e == '\0' ? NULL : e; |
| tokens[ntokens].length = 0; |
| ntokens++; |
|
|
| return ntokens; |
| } |
|
|
| |
| static void write_and_free(conn *c, char *buf, int bytes) { |
| if (buf) { |
| c->write_and_free = buf; |
| c->wcurr = buf; |
| c->wbytes = bytes; |
| conn_set_state(c, conn_write); |
| c->write_and_go = conn_read; |
| } else { |
| out_string(c, "SERVER_ERROR out of memory writing stats"); |
| } |
| } |
|
|
| static inline void set_noreply_maybe(conn *c, token_t *tokens, size_t ntokens) |
| { |
| int noreply_index = ntokens - 2; |
|
|
| |
| |
| |
| |
| |
| |
| |
| if (tokens[noreply_index].value |
| && strcmp(tokens[noreply_index].value, "noreply") == 0) { |
| c->noreply = true; |
| } |
| } |
|
|
| inline static void process_stats_detail(conn *c, const char *command) { |
| assert(c != NULL); |
|
|
| if (strcmp(command, "on") == 0) { |
| settings.detail_enabled = 1; |
| out_string(c, "OK"); |
| } |
| else if (strcmp(command, "off") == 0) { |
| settings.detail_enabled = 0; |
| out_string(c, "OK"); |
| } else { |
| out_string(c, "CLIENT_ERROR usage: stats detail on|off|dump"); |
| } |
| } |
|
|
| static void process_stat(conn *c, token_t *tokens, const size_t ntokens) { |
| rel_time_t now = current_time; |
| char *command; |
| char *subcommand; |
|
|
| assert(c != NULL); |
|
|
| if(ntokens < 2) { |
| out_string(c, "CLIENT_ERROR bad command line"); |
| return; |
| } |
|
|
| command = tokens[COMMAND_TOKEN].value; |
|
|
| if (ntokens == 2 && strcmp(command, "stats") == 0) { |
| char temp[1024]; |
| pid_t pid = getpid(); |
| char *pos = temp; |
|
|
| #ifndef WIN32 |
| struct rusage usage; |
| getrusage(RUSAGE_SELF, &usage); |
| #endif |
|
|
| STATS_LOCK(); |
| pos += sprintf(pos, "STAT pid %u\r\n", pid); |
| pos += sprintf(pos, "STAT uptime %u\r\n", now); |
| pos += sprintf(pos, "STAT time %ld\r\n", now + stats.started); |
| pos += sprintf(pos, "STAT version " VERSION "\r\n"); |
| pos += sprintf(pos, "STAT pointer_size %d\r\n", 8 * sizeof(void *)); |
| #ifndef WIN32 |
| pos += sprintf(pos, "STAT rusage_user %ld.%06ld\r\n", usage.ru_utime.tv_sec, usage.ru_utime.tv_usec); |
| pos += sprintf(pos, "STAT rusage_system %ld.%06ld\r\n", usage.ru_stime.tv_sec, usage.ru_stime.tv_usec); |
| #endif |
| pos += sprintf(pos, "STAT curr_items %u\r\n", stats.curr_items); |
| pos += sprintf(pos, "STAT total_items %u\r\n", stats.total_items); |
| pos += sprintf(pos, "STAT bytes %llu\r\n", stats.curr_bytes); |
| pos += sprintf(pos, "STAT curr_connections %u\r\n", stats.curr_conns - 1); |
| pos += sprintf(pos, "STAT total_connections %u\r\n", stats.total_conns); |
| pos += sprintf(pos, "STAT connection_structures %u\r\n", stats.conn_structs); |
| pos += sprintf(pos, "STAT cmd_get %llu\r\n", stats.get_cmds); |
| pos += sprintf(pos, "STAT cmd_set %llu\r\n", stats.set_cmds); |
| pos += sprintf(pos, "STAT get_hits %llu\r\n", stats.get_hits); |
| pos += sprintf(pos, "STAT get_misses %llu\r\n", stats.get_misses); |
| pos += sprintf(pos, "STAT evictions %llu\r\n", stats.evictions); |
| pos += sprintf(pos, "STAT bytes_read %llu\r\n", stats.bytes_read); |
| pos += sprintf(pos, "STAT bytes_written %llu\r\n", stats.bytes_written); |
| pos += sprintf(pos, "STAT limit_maxbytes %llu\r\n", (uint64_t) settings.maxbytes); |
| pos += sprintf(pos, "STAT threads %u\r\n", settings.num_threads); |
| pos += sprintf(pos, "END"); |
| STATS_UNLOCK(); |
| out_string(c, temp); |
| return; |
| } |
|
|
| subcommand = tokens[SUBCOMMAND_TOKEN].value; |
|
|
| if (strcmp(subcommand, "reset") == 0) { |
| stats_reset(); |
| out_string(c, "RESET"); |
| return; |
| } |
|
|
| #ifdef HAVE_MALLOC_H |
| #ifdef HAVE_STRUCT_MALLINFO |
| if (strcmp(subcommand, "malloc") == 0) { |
| char temp[512]; |
| struct mallinfo info; |
| char *pos = temp; |
|
|
| info = mallinfo(); |
| pos += sprintf(pos, "STAT arena_size %d\r\n", info.arena); |
| pos += sprintf(pos, "STAT free_chunks %d\r\n", info.ordblks); |
| pos += sprintf(pos, "STAT fastbin_blocks %d\r\n", info.smblks); |
| pos += sprintf(pos, "STAT mmapped_regions %d\r\n", info.hblks); |
| pos += sprintf(pos, "STAT mmapped_space %d\r\n", info.hblkhd); |
| pos += sprintf(pos, "STAT max_total_alloc %d\r\n", info.usmblks); |
| pos += sprintf(pos, "STAT fastbin_space %d\r\n", info.fsmblks); |
| pos += sprintf(pos, "STAT total_alloc %d\r\n", info.uordblks); |
| pos += sprintf(pos, "STAT total_free %d\r\n", info.fordblks); |
| pos += sprintf(pos, "STAT releasable_space %d\r\nEND", info.keepcost); |
| out_string(c, temp); |
| return; |
| } |
| #endif |
| #endif |
| out_string(c, "ERROR"); |
| } |
|
|
| static inline void process_srilm_command(conn *c, token_t *tokens, size_t ntokens) { |
| int context[6]; |
| int i = 1; |
| int j = ntokens - 3; |
| while (tokens[i].length) { |
| context[i-1] = srilm_getvoc(tokens[i].value); |
| ++i; |
| } |
| float p = -999.0f; |
| if (context[0] != -1) { |
| context[i-1] = -1; |
| p = srilm_wordprob(context[0], &context[1]); |
| } |
|
|
| memcpy(c->wbuf, &p, sizeof(float)); |
| memcpy(c->wbuf + sizeof(float), "\r\n", 2); |
| c->wbytes = sizeof(float) + 2; |
| c->wcurr = c->wbuf; |
|
|
| conn_set_state(c, conn_write); |
| c->write_and_go = conn_read; |
| } |
|
|
| static void process_command(conn *c, char *command) { |
|
|
| token_t tokens[MAX_TOKENS]; |
| size_t ntokens; |
| int comm; |
|
|
| assert(c != NULL); |
|
|
| if (settings.verbose > 1) |
| fprintf(stderr, "<%d %s\n", c->sfd, command); |
|
|
| |
| |
| |
| |
|
|
| c->msgcurr = 0; |
| c->msgused = 0; |
| c->iovused = 0; |
| if (add_msghdr(c) != 0) { |
| out_string(c, "SERVER_ERROR out of memory preparing response"); |
| return; |
| } |
|
|
| ntokens = tokenize_command(command, tokens, MAX_TOKENS); |
| if (ntokens >1 && |
| strcmp(tokens[COMMAND_TOKEN].value, "prob") == 0) { |
| process_srilm_command(c, tokens, ntokens); |
| } else if (ntokens >= 2 && (strcmp(tokens[COMMAND_TOKEN].value, "stats") == 0)) { |
|
|
| process_stat(c, tokens, ntokens); |
|
|
| } 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 { |
| out_string(c, "ERROR"); |
| } |
| return; |
| } |
|
|
| |
| |
| |
| static int try_read_command(conn *c) { |
| char *el, *cont; |
|
|
| assert(c != NULL); |
| assert(c->rcurr <= (c->rbuf + c->rsize)); |
|
|
| if (c->rbytes == 0) |
| return 0; |
| el = memchr(c->rcurr, '\n', c->rbytes); |
| if (!el) |
| return 0; |
| cont = el + 1; |
| if ((el - c->rcurr) > 1 && *(el - 1) == '\r') { |
| el--; |
| } |
| *el = '\0'; |
|
|
| assert(cont <= (c->rcurr + c->rbytes)); |
|
|
| process_command(c, c->rcurr); |
|
|
| c->rbytes -= (cont - c->rcurr); |
| c->rcurr = cont; |
|
|
| assert(c->rcurr <= (c->rbuf + c->rsize)); |
|
|
| return 1; |
| } |
|
|
| |
| |
| |
| |
| static int try_read_udp(conn *c) { |
| int res; |
|
|
| assert(c != NULL); |
|
|
| c->request_addr_size = sizeof(c->request_addr); |
| res = recvfrom(c->sfd, c->rbuf, c->rsize, |
| 0, &c->request_addr, &c->request_addr_size); |
| if (res > 8) { |
| unsigned char *buf = (unsigned char *)c->rbuf; |
| STATS_LOCK(); |
| stats.bytes_read += res; |
| STATS_UNLOCK(); |
|
|
| |
| c->request_id = buf[0] * 256 + buf[1]; |
|
|
| |
| if (buf[4] != 0 || buf[5] != 1) { |
| out_string(c, "SERVER_ERROR multi-packet request not supported"); |
| return 0; |
| } |
|
|
| |
| res -= 8; |
| memmove(c->rbuf, c->rbuf + 8, res); |
|
|
| c->rbytes += res; |
| c->rcurr = c->rbuf; |
| return 1; |
| } |
| return 0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| static int try_read_network(conn *c) { |
| int gotdata = 0; |
| int res; |
|
|
| assert(c != NULL); |
|
|
| if (c->rcurr != c->rbuf) { |
| if (c->rbytes != 0) |
| memmove(c->rbuf, c->rcurr, c->rbytes); |
| c->rcurr = c->rbuf; |
| } |
|
|
| while (1) { |
| if (c->rbytes >= c->rsize) { |
| char *new_rbuf = realloc(c->rbuf, c->rsize * 2); |
| if (!new_rbuf) { |
| if (settings.verbose > 0) |
| fprintf(stderr, "Couldn't realloc input buffer\n"); |
| c->rbytes = 0; |
| out_string(c, "SERVER_ERROR out of memory reading request"); |
| c->write_and_go = conn_closing; |
| return 1; |
| } |
| c->rcurr = c->rbuf = new_rbuf; |
| c->rsize *= 2; |
| } |
|
|
| |
| |
| |
| if (!settings.socketpath) { |
| c->request_addr_size = sizeof(c->request_addr); |
| } else { |
| c->request_addr_size = 0; |
| } |
|
|
| int avail = c->rsize - c->rbytes; |
| res = read(c->sfd, c->rbuf + c->rbytes, avail); |
| if (res > 0) { |
| STATS_LOCK(); |
| stats.bytes_read += res; |
| STATS_UNLOCK(); |
| gotdata = 1; |
| c->rbytes += res; |
| if (res == avail) { |
| continue; |
| } else { |
| break; |
| } |
| } |
| if (res == 0) { |
| |
| conn_set_state(c, conn_closing); |
| return 1; |
| } |
| if (res == -1) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) break; |
| |
| conn_set_state(c, conn_closing); |
| return 1; |
| } |
| } |
| return gotdata; |
| } |
|
|
| static bool update_event(conn *c, const int new_flags) { |
| assert(c != NULL); |
|
|
| struct event_base *base = c->event.ev_base; |
| if (c->ev_flags == new_flags) |
| return true; |
| if (event_del(&c->event) == -1) return false; |
| event_set(&c->event, c->sfd, new_flags, event_handler, (void *)c); |
| event_base_set(base, &c->event); |
| c->ev_flags = new_flags; |
| if (event_add(&c->event, 0) == -1) return false; |
| return true; |
| } |
|
|
| |
| |
| |
| void accept_new_conns(const bool do_accept) { |
| conn *next; |
|
|
| if (! is_listen_thread()) |
| return; |
|
|
| for (next = listen_conn; next; next = next->next) { |
| if (do_accept) { |
| update_event(next, EV_READ | EV_PERSIST); |
| if (listen(next->sfd, 1024) != 0) { |
| perror("listen"); |
| } |
| } |
| else { |
| update_event(next, 0); |
| if (listen(next->sfd, 0) != 0) { |
| perror("listen"); |
| } |
| } |
| } |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int transmit(conn *c) { |
| assert(c != NULL); |
|
|
| if (c->msgcurr < c->msgused && |
| c->msglist[c->msgcurr].msg_iovlen == 0) { |
| |
| c->msgcurr++; |
| } |
| if (c->msgcurr < c->msgused) { |
| ssize_t res; |
| struct msghdr *m = &c->msglist[c->msgcurr]; |
|
|
| res = sendmsg(c->sfd, m, 0); |
| if (res > 0) { |
| STATS_LOCK(); |
| stats.bytes_written += res; |
| STATS_UNLOCK(); |
|
|
| |
| |
| while (m->msg_iovlen > 0 && res >= m->msg_iov->iov_len) { |
| res -= m->msg_iov->iov_len; |
| m->msg_iovlen--; |
| m->msg_iov++; |
| } |
|
|
| |
| |
| if (res > 0) { |
| m->msg_iov->iov_base += res; |
| m->msg_iov->iov_len -= res; |
| } |
| return TRANSMIT_INCOMPLETE; |
| } |
| if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { |
| 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); |
| return TRANSMIT_HARD_ERROR; |
| } |
| return TRANSMIT_SOFT_ERROR; |
| } |
| |
| |
| if (settings.verbose > 0) |
| perror("Failed to write, and not due to blocking"); |
|
|
| if (c->udp) |
| conn_set_state(c, conn_read); |
| else |
| conn_set_state(c, conn_closing); |
| return TRANSMIT_HARD_ERROR; |
| } else { |
| return TRANSMIT_COMPLETE; |
| } |
| } |
|
|
| static void drive_machine(conn *c) { |
| bool stop = false; |
| int sfd, flags = 1; |
| socklen_t addrlen; |
| struct sockaddr_storage addr; |
| int res; |
|
|
| assert(c != NULL); |
|
|
| while (!stop) { |
|
|
| switch(c->state) { |
| case conn_listening: |
| addrlen = sizeof(addr); |
| if ((sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)) == -1) { |
| if (errno == EAGAIN || errno == EWOULDBLOCK) { |
| |
| 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 ((flags = fcntl(sfd, F_GETFL, 0)) < 0 || |
| fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) { |
| perror("setting O_NONBLOCK"); |
| close(sfd); |
| break; |
| } |
| dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST, |
| DATA_BUFFER_SIZE, false); |
| break; |
|
|
| case conn_read: |
| if (try_read_command(c) != 0) { |
| continue; |
| } |
| if ((c->udp ? try_read_udp(c) : try_read_network(c)) != 0) { |
| continue; |
| } |
| |
| if (!update_event(c, EV_READ | EV_PERSIST)) { |
| if (settings.verbose > 0) |
| fprintf(stderr, "Couldn't update event\n"); |
| conn_set_state(c, conn_closing); |
| break; |
| } |
| stop = true; |
| break; |
|
|
| case conn_nread: |
| assert(!"nread should not be possible"); |
| break; |
|
|
| case conn_swallow: |
| |
| if (c->sbytes == 0) { |
| conn_set_state(c, conn_read); |
| break; |
| } |
|
|
| |
| if (c->rbytes > 0) { |
| int tocopy = c->rbytes > c->sbytes ? c->sbytes : c->rbytes; |
| c->sbytes -= tocopy; |
| c->rcurr += tocopy; |
| c->rbytes -= tocopy; |
| break; |
| } |
|
|
| |
| res = read(c->sfd, c->rbuf, c->rsize > c->sbytes ? c->sbytes : c->rsize); |
| if (res > 0) { |
| STATS_LOCK(); |
| stats.bytes_read += res; |
| STATS_UNLOCK(); |
| c->sbytes -= res; |
| break; |
| } |
| if (res == 0) { |
| conn_set_state(c, conn_closing); |
| break; |
| } |
| if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { |
| if (!update_event(c, EV_READ | EV_PERSIST)) { |
| if (settings.verbose > 0) |
| fprintf(stderr, "Couldn't update event\n"); |
| conn_set_state(c, conn_closing); |
| break; |
| } |
| stop = true; |
| break; |
| } |
| |
| if (settings.verbose > 0) |
| fprintf(stderr, "Failed to read, and not due to blocking\n"); |
| conn_set_state(c, conn_closing); |
| break; |
|
|
| case conn_write: |
| |
| |
| |
| |
| |
| if (c->iovused == 0 || (c->udp && c->iovused == 1)) { |
| if (add_iov(c, c->wcurr, c->wbytes) != 0 || |
| (c->udp && build_udp_headers(c) != 0)) { |
| if (settings.verbose > 0) |
| fprintf(stderr, "Couldn't build response\n"); |
| conn_set_state(c, conn_closing); |
| break; |
| } |
| } |
|
|
| |
|
|
| case conn_mwrite: |
| switch (transmit(c)) { |
| case TRANSMIT_COMPLETE: |
| if (c->state == conn_write) { |
| if (c->write_and_free) { |
| free(c->write_and_free); |
| c->write_and_free = 0; |
| } |
| conn_set_state(c, c->write_and_go); |
| } else { |
| if (settings.verbose > 0) |
| fprintf(stderr, "Unexpected state %d\n", c->state); |
| conn_set_state(c, conn_closing); |
| } |
| break; |
|
|
| case TRANSMIT_INCOMPLETE: |
| case TRANSMIT_HARD_ERROR: |
| break; |
|
|
| case TRANSMIT_SOFT_ERROR: |
| stop = true; |
| break; |
| } |
| break; |
|
|
| case conn_closing: |
| if (c->udp) |
| conn_cleanup(c); |
| else |
| conn_close(c); |
| stop = true; |
| break; |
| } |
| } |
|
|
| return; |
| } |
|
|
| void event_handler(const int fd, const short which, void *arg) { |
| conn *c; |
|
|
| c = (conn *)arg; |
| assert(c != NULL); |
|
|
| c->which = which; |
|
|
| |
| 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); |
|
|
| |
| return; |
| } |
|
|
| static int new_socket(struct addrinfo *ai) { |
| int sfd; |
| int flags; |
|
|
| if ((sfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) { |
| perror("socket()"); |
| return -1; |
| } |
|
|
| if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 || |
| fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) { |
| perror("setting O_NONBLOCK"); |
| close(sfd); |
| return -1; |
| } |
| return sfd; |
| } |
|
|
|
|
| |
| |
| |
| static void maximize_sndbuf(const int sfd) { |
| socklen_t intsize = sizeof(int); |
| int last_good = 0; |
| int min, max, avg; |
| int old_size; |
|
|
| |
| if (getsockopt(sfd, SOL_SOCKET, SO_SNDBUF, &old_size, &intsize) != 0) { |
| if (settings.verbose > 0) |
| perror("getsockopt(SO_SNDBUF)"); |
| return; |
| } |
|
|
| |
| min = old_size; |
| max = MAX_SENDBUF_SIZE; |
|
|
| while (min <= max) { |
| avg = ((unsigned int)(min + max)) / 2; |
| if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUF, (void *)&avg, intsize) == 0) { |
| last_good = avg; |
| min = avg + 1; |
| } else { |
| max = avg - 1; |
| } |
| } |
|
|
| if (settings.verbose > 1) |
| fprintf(stderr, "<%d send buffer was %d, now %d\n", sfd, old_size, last_good); |
| } |
|
|
| static int server_socket(const int port, const bool is_udp) { |
| int sfd; |
| struct linger ling = {0, 0}; |
| struct addrinfo *ai; |
| struct addrinfo *next; |
| struct addrinfo hints; |
| char port_buf[NI_MAXSERV]; |
| int error; |
| int success = 0; |
|
|
| int flags =1; |
|
|
| |
| |
| |
| |
| memset(&hints, 0, sizeof (hints)); |
| hints.ai_flags = AI_PASSIVE|AI_ADDRCONFIG; |
| if (is_udp) |
| { |
| hints.ai_protocol = IPPROTO_UDP; |
| hints.ai_socktype = SOCK_DGRAM; |
| hints.ai_family = AF_INET; |
| } else { |
| hints.ai_family = AF_UNSPEC; |
| hints.ai_protocol = IPPROTO_TCP; |
| hints.ai_socktype = SOCK_STREAM; |
| } |
|
|
| snprintf(port_buf, NI_MAXSERV, "%d", port); |
| error= getaddrinfo(settings.inter, port_buf, &hints, &ai); |
| if (error != 0) { |
| if (error != EAI_SYSTEM) |
| fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error)); |
| else |
| perror("getaddrinfo()"); |
|
|
| return 1; |
| } |
|
|
| for (next= ai; next; next= next->ai_next) { |
| conn *listen_conn_add; |
| if ((sfd = new_socket(next)) == -1) { |
| freeaddrinfo(ai); |
| return 1; |
| } |
|
|
| setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags)); |
| if (is_udp) { |
| maximize_sndbuf(sfd); |
| } else { |
| setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags)); |
| setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling)); |
| setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags)); |
| } |
|
|
| if (bind(sfd, next->ai_addr, next->ai_addrlen) == -1) { |
| if (errno != EADDRINUSE) { |
| perror("bind()"); |
| close(sfd); |
| freeaddrinfo(ai); |
| return 1; |
| } |
| close(sfd); |
| continue; |
| } else { |
| success++; |
| if (!is_udp && listen(sfd, 1024) == -1) { |
| perror("listen()"); |
| close(sfd); |
| freeaddrinfo(ai); |
| return 1; |
| } |
| } |
|
|
| if (is_udp) |
| { |
| int c; |
|
|
| for (c = 0; c < settings.num_threads; c++) { |
| |
| dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST, |
| UDP_READ_BUFFER_SIZE, 1); |
| } |
| } else { |
| if (!(listen_conn_add = conn_new(sfd, conn_listening, |
| EV_READ | EV_PERSIST, 1, false, main_base))) { |
| fprintf(stderr, "failed to create listening connection\n"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| listen_conn_add->next = listen_conn; |
| listen_conn = listen_conn_add; |
| } |
| } |
|
|
| freeaddrinfo(ai); |
|
|
| |
| return success == 0; |
| } |
|
|
| static int new_socket_unix(void) { |
| int sfd; |
| int flags; |
|
|
| if ((sfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { |
| perror("socket()"); |
| return -1; |
| } |
|
|
| if ((flags = fcntl(sfd, F_GETFL, 0)) < 0 || |
| fcntl(sfd, F_SETFL, flags | O_NONBLOCK) < 0) { |
| perror("setting O_NONBLOCK"); |
| close(sfd); |
| return -1; |
| } |
| return sfd; |
| } |
|
|
| static int server_socket_unix(const char *path, int access_mask) { |
| int sfd; |
| struct linger ling = {0, 0}; |
| struct sockaddr_un addr; |
| struct stat tstat; |
| int flags =1; |
| int old_umask; |
|
|
| if (!path) { |
| return 1; |
| } |
|
|
| if ((sfd = new_socket_unix()) == -1) { |
| return 1; |
| } |
|
|
| |
| |
| |
| if (lstat(path, &tstat) == 0) { |
| if (S_ISSOCK(tstat.st_mode)) |
| unlink(path); |
| } |
|
|
| setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags)); |
| setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags)); |
| setsockopt(sfd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling)); |
|
|
| |
| |
| |
| |
| memset(&addr, 0, sizeof(addr)); |
|
|
| addr.sun_family = AF_UNIX; |
| strcpy(addr.sun_path, path); |
| old_umask=umask( ~(access_mask&0777)); |
| if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { |
| perror("bind()"); |
| close(sfd); |
| umask(old_umask); |
| return 1; |
| } |
| umask(old_umask); |
| if (listen(sfd, 1024) == -1) { |
| perror("listen()"); |
| close(sfd); |
| return 1; |
| } |
| if (!(listen_conn = conn_new(sfd, conn_listening, |
| EV_READ | EV_PERSIST, 1, false, main_base))) { |
| fprintf(stderr, "failed to create listening connection\n"); |
| exit(EXIT_FAILURE); |
| } |
|
|
| return 0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| volatile rel_time_t current_time; |
| static struct event clockevent; |
|
|
| |
| static void set_current_time(void) { |
| struct timeval timer; |
|
|
| gettimeofday(&timer, NULL); |
| current_time = (rel_time_t) (timer.tv_sec - stats.started); |
| } |
|
|
| static void clock_handler(const int fd, const short which, void *arg) { |
| struct timeval t = {.tv_sec = 1, .tv_usec = 0}; |
| static bool initialized = false; |
|
|
| if (initialized) { |
| |
| evtimer_del(&clockevent); |
| } else { |
| initialized = true; |
| } |
|
|
| evtimer_set(&clockevent, clock_handler, 0); |
| event_base_set(main_base, &clockevent); |
| evtimer_add(&clockevent, &t); |
|
|
| set_current_time(); |
| } |
|
|
| static void usage(void) { |
| printf(PACKAGE " " VERSION "\n"); |
| printf("-p <num> TCP port number to listen on (default: 11211)\n" |
| "-U <num> UDP port number to listen on (default: 0, off)\n" |
| "-s <file> unix socket path to listen on (disables network support)\n" |
| "-a <mask> access mask for unix socket, in octal (default 0700)\n" |
| "-l <ip_addr> interface to listen on, default is INDRR_ANY\n" |
| "-d run as a daemon\n" |
| "-r maximize core file limit\n" |
| "-u <username> assume identity of <username> (only when run as root)\n" |
| "-m <num> max memory to use for items in megabytes, default is 64 MB\n" |
| "-M return error on memory exhausted (rather than removing items)\n" |
| "-c <num> max simultaneous connections, default is 1024\n" |
| "-k lock down all paged memory. Note that there is a\n" |
| " limit on how much memory you may lock. Trying to\n" |
| " allocate more than that would fail, so be sure you\n" |
| " set the limit correctly for the user you started\n" |
| " the daemon with (not for -u <username> user;\n" |
| " under sh this is done with 'ulimit -S -l NUM_KB').\n" |
| "-v verbose (print errors/warnings while in event loop)\n" |
| "-vv very verbose (also print client commands/reponses)\n" |
| "-h print this help and exit\n" |
| "-i print memcached and libevent license\n" |
| "-b run a managed instanced (mnemonic: buckets)\n" |
| "-P <file> save PID in <file>, only used with -d option\n" |
| "-f <factor> chunk size growth factor, default 1.25\n" |
| "-n <bytes> minimum space allocated for key+value+flags, default 48\n" |
|
|
| #if defined(HAVE_GETPAGESIZES) && defined(HAVE_MEMCNTL) |
| "-L Try to use large memory pages (if available). Increasing\n" |
| " the memory page size could reduce the number of TLB misses\n" |
| " and improve the performance. In order to get large pages\n" |
| " from the OS, memcached will allocate the total item-cache\n" |
| " in one large chunk.\n" |
| #endif |
| ); |
|
|
| #ifdef USE_THREADS |
| printf("-t <num> number of threads to use, default 4\n"); |
| #endif |
| return; |
| } |
|
|
| static void usage_license(void) { |
| printf(PACKAGE " " VERSION "\n\n"); |
| printf( |
| "Copyright (c) 2003, Danga Interactive, Inc. <http://www.danga.com/>\n" |
| "All rights reserved.\n" |
| "\n" |
| "Redistribution and use in source and binary forms, with or without\n" |
| "modification, are permitted provided that the following conditions are\n" |
| "met:\n" |
| "\n" |
| " * Redistributions of source code must retain the above copyright\n" |
| "notice, this list of conditions and the following disclaimer.\n" |
| "\n" |
| " * Redistributions in binary form must reproduce the above\n" |
| "copyright notice, this list of conditions and the following disclaimer\n" |
| "in the documentation and/or other materials provided with the\n" |
| "distribution.\n" |
| "\n" |
| " * Neither the name of the Danga Interactive nor the names of its\n" |
| "contributors may be used to endorse or promote products derived from\n" |
| "this software without specific prior written permission.\n" |
| "\n" |
| "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n" |
| "\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n" |
| "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n" |
| "A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n" |
| "OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n" |
| "SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n" |
| "LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n" |
| "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n" |
| "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" |
| "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n" |
| "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" |
| "\n" |
| "\n" |
| "This product includes software developed by Niels Provos.\n" |
| "\n" |
| "[ libevent ]\n" |
| "\n" |
| "Copyright 2000-2003 Niels Provos <provos@citi.umich.edu>\n" |
| "All rights reserved.\n" |
| "\n" |
| "Redistribution and use in source and binary forms, with or without\n" |
| "modification, are permitted provided that the following conditions\n" |
| "are met:\n" |
| "1. Redistributions of source code must retain the above copyright\n" |
| " notice, this list of conditions and the following disclaimer.\n" |
| "2. Redistributions in binary form must reproduce the above copyright\n" |
| " notice, this list of conditions and the following disclaimer in the\n" |
| " documentation and/or other materials provided with the distribution.\n" |
| "3. All advertising materials mentioning features or use of this software\n" |
| " must display the following acknowledgement:\n" |
| " This product includes software developed by Niels Provos.\n" |
| "4. The name of the author may not be used to endorse or promote products\n" |
| " derived from this software without specific prior written permission.\n" |
| "\n" |
| "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" |
| "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n" |
| "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n" |
| "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n" |
| "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n" |
| "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n" |
| "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n" |
| "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" |
| "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\n" |
| "THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" |
| ); |
|
|
| return; |
| } |
|
|
| static void save_pid(const pid_t pid, const char *pid_file) { |
| FILE *fp; |
| if (pid_file == NULL) |
| return; |
|
|
| if ((fp = fopen(pid_file, "w")) == NULL) { |
| fprintf(stderr, "Could not open the pid file %s for writing\n", pid_file); |
| return; |
| } |
|
|
| fprintf(fp,"%ld\n", (long)pid); |
| if (fclose(fp) == -1) { |
| fprintf(stderr, "Could not close the pid file %s.\n", pid_file); |
| return; |
| } |
| } |
|
|
| static void remove_pidfile(const char *pid_file) { |
| if (pid_file == NULL) |
| return; |
|
|
| if (unlink(pid_file) != 0) { |
| fprintf(stderr, "Could not remove the pid file %s.\n", pid_file); |
| } |
|
|
| } |
|
|
|
|
| static void sig_handler(const int sig) { |
| printf("SIGINT handled.\n"); |
| exit(EXIT_SUCCESS); |
| } |
|
|
| #if defined(HAVE_GETPAGESIZES) && defined(HAVE_MEMCNTL) |
| |
| |
| |
| |
| int enable_large_pages(void) { |
| int ret = -1; |
| size_t sizes[32]; |
| int avail = getpagesizes(sizes, 32); |
| if (avail != -1) { |
| size_t max = sizes[0]; |
| struct memcntl_mha arg = {0}; |
| int ii; |
|
|
| for (ii = 1; ii < avail; ++ii) { |
| if (max < sizes[ii]) { |
| max = sizes[ii]; |
| } |
| } |
|
|
| arg.mha_flags = 0; |
| arg.mha_pagesize = max; |
| arg.mha_cmd = MHA_MAPSIZE_BSSBRK; |
|
|
| if (memcntl(0, 0, MC_HAT_ADVISE, (caddr_t)&arg, 0, 0) == -1) { |
| fprintf(stderr, "Failed to set large pages: %s\n", |
| strerror(errno)); |
| fprintf(stderr, "Will use default page size\n"); |
| } else { |
| ret = 0; |
| } |
| } else { |
| fprintf(stderr, "Failed to get supported pagesizes: %s\n", |
| strerror(errno)); |
| fprintf(stderr, "Will use default page size\n"); |
| } |
|
|
| return ret; |
| } |
| #endif |
|
|
| int main (int argc, char **argv) { |
| int c; |
| int x; |
| bool lock_memory = false; |
| bool daemonize = false; |
| bool preallocate = false; |
| int maxcore = 0; |
| char *username = NULL; |
| char *pid_file = NULL; |
| struct passwd *pw; |
| struct sigaction sa; |
| struct rlimit rlim; |
| |
| static int *l_socket = NULL; |
|
|
| |
| static int *u_socket = NULL; |
| static int u_socket_count = 0; |
|
|
| |
| signal(SIGINT, sig_handler); |
|
|
| |
| settings_init(); |
|
|
| |
| setbuf(stderr, NULL); |
|
|
| |
| while ((c = getopt(argc, argv, "x:o:a:bp:s:U:m:Mc:khirvdl:u:P:f:s:n:t:L")) != -1) { |
| switch (c) { |
| case 'x': |
| settings.srilm = optarg; |
| break; |
| case 'o': |
| settings.srilm_order = atoi(optarg); |
| break; |
| case 'a': |
| |
| settings.access= strtol(optarg,NULL,8); |
| break; |
|
|
| case 'U': |
| settings.udpport = atoi(optarg); |
| break; |
| case 'b': |
| settings.managed = true; |
| break; |
| case 'p': |
| settings.port = atoi(optarg); |
| break; |
| case 's': |
| settings.socketpath = optarg; |
| break; |
| case 'm': |
| settings.maxbytes = ((size_t)atoi(optarg)) * 1024 * 1024; |
| break; |
| case 'M': |
| settings.evict_to_free = 0; |
| break; |
| case 'c': |
| settings.maxconns = atoi(optarg); |
| break; |
| case 'h': |
| usage(); |
| exit(EXIT_SUCCESS); |
| case 'i': |
| usage_license(); |
| exit(EXIT_SUCCESS); |
| case 'k': |
| lock_memory = true; |
| break; |
| case 'v': |
| settings.verbose++; |
| break; |
| case 'l': |
| settings.inter= strdup(optarg); |
| break; |
| case 'd': |
| daemonize = true; |
| break; |
| case 'r': |
| maxcore = 1; |
| break; |
| case 'u': |
| username = optarg; |
| break; |
| case 'P': |
| pid_file = optarg; |
| break; |
| case 'f': |
| settings.factor = atof(optarg); |
| if (settings.factor <= 1.0) { |
| fprintf(stderr, "Factor must be greater than 1\n"); |
| return 1; |
| } |
| break; |
| case 'n': |
| settings.chunk_size = atoi(optarg); |
| if (settings.chunk_size == 0) { |
| fprintf(stderr, "Chunk size must be greater than 0\n"); |
| return 1; |
| } |
| break; |
| case 't': |
| settings.num_threads = atoi(optarg); |
| if (settings.num_threads == 0) { |
| fprintf(stderr, "Number of threads must be greater than 0\n"); |
| return 1; |
| } |
| break; |
| #if defined(HAVE_GETPAGESIZES) && defined(HAVE_MEMCNTL) |
| case 'L' : |
| if (enable_large_pages() == 0) { |
| preallocate = true; |
| } |
| break; |
| #endif |
| default: |
| fprintf(stderr, "Illegal argument \"%c\"\n", c); |
| return 1; |
| } |
| } |
|
|
| if (maxcore != 0) { |
| struct rlimit rlim_new; |
| |
| |
| |
| |
| if (getrlimit(RLIMIT_CORE, &rlim) == 0) { |
| rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY; |
| if (setrlimit(RLIMIT_CORE, &rlim_new)!= 0) { |
| |
| rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max; |
| (void)setrlimit(RLIMIT_CORE, &rlim_new); |
| } |
| } |
| |
| |
| |
| |
| |
|
|
| if ((getrlimit(RLIMIT_CORE, &rlim) != 0) || rlim.rlim_cur == 0) { |
| fprintf(stderr, "failed to ensure corefile creation\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
|
|
| |
| |
| |
| |
|
|
| if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { |
| fprintf(stderr, "failed to getrlimit number of files\n"); |
| exit(EXIT_FAILURE); |
| } else { |
| int maxfiles = settings.maxconns; |
| if (rlim.rlim_cur < maxfiles) |
| rlim.rlim_cur = maxfiles + 3; |
| if (rlim.rlim_max < rlim.rlim_cur) |
| rlim.rlim_max = rlim.rlim_cur; |
| if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) { |
| fprintf(stderr, "failed to set rlimit for open files. Try running as root or requesting smaller maxconns value.\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
|
|
| |
| |
| if (daemonize) { |
| int res; |
| res = daemon(maxcore, settings.verbose); |
| if (res == -1) { |
| fprintf(stderr, "failed to daemon() in order to daemonize\n"); |
| return 1; |
| } |
| } |
|
|
| |
| if (lock_memory) { |
| #ifdef HAVE_MLOCKALL |
| int res = mlockall(MCL_CURRENT | MCL_FUTURE); |
| if (res != 0) { |
| fprintf(stderr, "warning: -k invalid, mlockall() failed: %s\n", |
| strerror(errno)); |
| } |
| #else |
| fprintf(stderr, "warning: -k invalid, mlockall() not supported on this platform. proceeding without.\n"); |
| #endif |
| } |
|
|
| |
| if (getuid() == 0 || geteuid() == 0) { |
| if (username == 0 || *username == '\0') { |
| fprintf(stderr, "can't run as root without the -u switch\n"); |
| return 1; |
| } |
| if ((pw = getpwnam(username)) == 0) { |
| fprintf(stderr, "can't find the user %s to switch to\n", username); |
| return 1; |
| } |
| if (setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) { |
| fprintf(stderr, "failed to assume identity of user %s\n", username); |
| return 1; |
| } |
| } |
|
|
| |
| main_base = event_init(); |
|
|
| |
| stats_init(); |
| conn_init(); |
| if (!settings.srilm) { |
| fprintf(stderr, "please specify a LM file with -x\n"); |
| exit(EXIT_FAILURE); |
| } |
| srilm_init(settings.srilm, settings.srilm_order); |
|
|
| |
| if (settings.managed) { |
| buckets = malloc(sizeof(int) * MAX_BUCKETS); |
| if (buckets == 0) { |
| fprintf(stderr, "failed to allocate the bucket array"); |
| exit(EXIT_FAILURE); |
| } |
| memset(buckets, 0, sizeof(int) * MAX_BUCKETS); |
| } |
|
|
| |
| |
| |
| |
| sa.sa_handler = SIG_IGN; |
| sa.sa_flags = 0; |
| if (sigemptyset(&sa.sa_mask) == -1 || |
| sigaction(SIGPIPE, &sa, 0) == -1) { |
| perror("failed to ignore SIGPIPE; sigaction"); |
| exit(EXIT_FAILURE); |
| } |
| |
| thread_init(settings.num_threads, main_base); |
| |
| |
| if (daemonize) |
| save_pid(getpid(), pid_file); |
| |
| clock_handler(0, 0, 0); |
|
|
| |
| if (settings.socketpath != NULL) { |
| if (server_socket_unix(settings.socketpath,settings.access)) { |
| fprintf(stderr, "failed to listen\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
|
|
| |
| if (settings.socketpath == NULL) { |
| int udp_port; |
|
|
| if (server_socket(settings.port, 0)) { |
| fprintf(stderr, "failed to listen\n"); |
| exit(EXIT_FAILURE); |
| } |
| |
| |
| |
| |
| |
| |
| udp_port = settings.udpport ? settings.udpport : settings.port; |
|
|
| |
| if (server_socket(udp_port, 1)) { |
| fprintf(stderr, "failed to listen on UDP port %d\n", settings.udpport); |
| exit(EXIT_FAILURE); |
| } |
| } |
|
|
| |
| event_base_loop(main_base, 0); |
| |
| if (daemonize) |
| remove_pidfile(pid_file); |
| |
| if (settings.inter) |
| free(settings.inter); |
| if (l_socket) |
| free(l_socket); |
| if (u_socket) |
| free(u_socket); |
|
|
| return 0; |
| } |
|
|