|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#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 <[email protected]>\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; |
|
} |
|
|