| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | #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; |
| | } |
| |
|