nutcracker-0.4.1+dfsg/0000775000000000000000000000000012542173515013301 5ustar rootrootnutcracker-0.4.1+dfsg/m4/0000775000000000000000000000000012542173515013621 5ustar rootrootnutcracker-0.4.1+dfsg/m4/.gitignore0000664000000000000000000000005712542173515015613 0ustar rootroot# Ignore everything * # Except me !.gitignore nutcracker-0.4.1+dfsg/src/0000775000000000000000000000000012542173515014070 5ustar rootrootnutcracker-0.4.1+dfsg/src/nc_stats.h0000664000000000000000000002366012542173515016066 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_STATS_H_ #define _NC_STATS_H_ #include #define STATS_POOL_CODEC(ACTION) \ /* client behavior */ \ ACTION( client_eof, STATS_COUNTER, "# eof on client connections") \ ACTION( client_err, STATS_COUNTER, "# errors on client connections") \ ACTION( client_connections, STATS_GAUGE, "# active client connections") \ /* pool behavior */ \ ACTION( server_ejects, STATS_COUNTER, "# times backend server was ejected") \ /* forwarder behavior */ \ ACTION( forward_error, STATS_COUNTER, "# times we encountered a forwarding error") \ ACTION( fragments, STATS_COUNTER, "# fragments created from a multi-vector request") \ #define STATS_SERVER_CODEC(ACTION) \ /* server behavior */ \ ACTION( server_eof, STATS_COUNTER, "# eof on server connections") \ ACTION( server_err, STATS_COUNTER, "# errors on server connections") \ ACTION( server_timedout, STATS_COUNTER, "# timeouts on server connections") \ ACTION( server_connections, STATS_GAUGE, "# active server connections") \ ACTION( server_ejected_at, STATS_TIMESTAMP, "timestamp when server was ejected in usec since epoch") \ /* data behavior */ \ ACTION( requests, STATS_COUNTER, "# requests") \ ACTION( request_bytes, STATS_COUNTER, "total request bytes") \ ACTION( responses, STATS_COUNTER, "# responses") \ ACTION( response_bytes, STATS_COUNTER, "total response bytes") \ ACTION( in_queue, STATS_GAUGE, "# requests in incoming queue") \ ACTION( in_queue_bytes, STATS_GAUGE, "current request bytes in incoming queue") \ ACTION( out_queue, STATS_GAUGE, "# requests in outgoing queue") \ ACTION( out_queue_bytes, STATS_GAUGE, "current request bytes in outgoing queue") \ #define STATS_ADDR "0.0.0.0" #define STATS_PORT 22222 #define STATS_INTERVAL (30 * 1000) /* in msec */ typedef enum stats_type { STATS_INVALID, STATS_COUNTER, /* monotonic accumulator */ STATS_GAUGE, /* non-monotonic accumulator */ STATS_TIMESTAMP, /* monotonic timestamp (in nsec) */ STATS_SENTINEL } stats_type_t; struct stats_metric { stats_type_t type; /* type */ struct string name; /* name (ref) */ union { int64_t counter; /* accumulating counter */ int64_t timestamp; /* monotonic timestamp */ } value; }; struct stats_server { struct string name; /* server name (ref) */ struct array metric; /* stats_metric[] for server codec */ }; struct stats_pool { struct string name; /* pool name (ref) */ struct array metric; /* stats_metric[] for pool codec */ struct array server; /* stats_server[] */ }; struct stats_buffer { size_t len; /* buffer length */ uint8_t *data; /* buffer data */ size_t size; /* buffer alloc size */ }; struct stats { uint16_t port; /* stats monitoring port */ int interval; /* stats aggregation interval */ struct string addr; /* stats monitoring address */ int64_t start_ts; /* start timestamp of nutcracker */ struct stats_buffer buf; /* output buffer */ struct array current; /* stats_pool[] (a) */ struct array shadow; /* stats_pool[] (b) */ struct array sum; /* stats_pool[] (c = a + b) */ pthread_t tid; /* stats aggregator thread */ int sd; /* stats descriptor */ struct string service_str; /* service string */ struct string service; /* service */ struct string source_str; /* source string */ struct string source; /* source */ struct string version_str; /* version string */ struct string version; /* version */ struct string uptime_str; /* uptime string */ struct string timestamp_str; /* timestamp string */ struct string ntotal_conn_str; /* total connections string */ struct string ncurr_conn_str; /* curr connections string */ volatile int aggregate; /* shadow (b) aggregate? */ volatile int updated; /* current (a) updated? */ }; #define DEFINE_ACTION(_name, _type, _desc) STATS_POOL_##_name, typedef enum stats_pool_field { STATS_POOL_CODEC(DEFINE_ACTION) STATS_POOL_NFIELD } stats_pool_field_t; #undef DEFINE_ACTION #define DEFINE_ACTION(_name, _type, _desc) STATS_SERVER_##_name, typedef enum stats_server_field { STATS_SERVER_CODEC(DEFINE_ACTION) STATS_SERVER_NFIELD } stats_server_field_t; #undef DEFINE_ACTION #if defined NC_STATS && NC_STATS == 1 #define stats_pool_incr(_ctx, _pool, _name) do { \ _stats_pool_incr(_ctx, _pool, STATS_POOL_##_name); \ } while (0) #define stats_pool_decr(_ctx, _pool, _name) do { \ _stats_pool_decr(_ctx, _pool, STATS_POOL_##_name); \ } while (0) #define stats_pool_incr_by(_ctx, _pool, _name, _val) do { \ _stats_pool_incr_by(_ctx, _pool, STATS_POOL_##_name, _val); \ } while (0) #define stats_pool_decr_by(_ctx, _pool, _name, _val) do { \ _stats_pool_decr_by(_ctx, _pool, STATS_POOL_##_name, _val); \ } while (0) #define stats_pool_set_ts(_ctx, _pool, _name, _val) do { \ _stats_pool_set_ts(_ctx, _pool, STATS_POOL_##_name, _val); \ } while (0) #define stats_server_incr(_ctx, _server, _name) do { \ _stats_server_incr(_ctx, _server, STATS_SERVER_##_name); \ } while (0) #define stats_server_decr(_ctx, _server, _name) do { \ _stats_server_decr(_ctx, _server, STATS_SERVER_##_name); \ } while (0) #define stats_server_incr_by(_ctx, _server, _name, _val) do { \ _stats_server_incr_by(_ctx, _server, STATS_SERVER_##_name, _val); \ } while (0) #define stats_server_decr_by(_ctx, _server, _name, _val) do { \ _stats_server_decr_by(_ctx, _server, STATS_SERVER_##_name, _val); \ } while (0) #define stats_server_set_ts(_ctx, _server, _name, _val) do { \ _stats_server_set_ts(_ctx, _server, STATS_SERVER_##_name, _val); \ } while (0) #else #define stats_pool_incr(_ctx, _pool, _name) #define stats_pool_decr(_ctx, _pool, _name) #define stats_pool_incr_by(_ctx, _pool, _name, _val) #define stats_pool_decr_by(_ctx, _pool, _name, _val) #define stats_server_incr(_ctx, _server, _name) #define stats_server_decr(_ctx, _server, _name) #define stats_server_incr_by(_ctx, _server, _name, _val) #define stats_server_decr_by(_ctx, _server, _name, _val) #endif #define stats_enabled NC_STATS void stats_describe(void); void _stats_pool_incr(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx); void _stats_pool_decr(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx); void _stats_pool_incr_by(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val); void _stats_pool_decr_by(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val); void _stats_pool_set_ts(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val); void _stats_server_incr(struct context *ctx, struct server *server, stats_server_field_t fidx); void _stats_server_decr(struct context *ctx, struct server *server, stats_server_field_t fidx); void _stats_server_incr_by(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val); void _stats_server_decr_by(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val); void _stats_server_set_ts(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val); struct stats *stats_create(uint16_t stats_port, char *stats_ip, int stats_interval, char *source, struct array *server_pool); void stats_destroy(struct stats *stats); void stats_swap(struct stats *stats); #endif nutcracker-0.4.1+dfsg/src/nc_server.c0000664000000000000000000005545212542173515016235 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include static void server_resolve(struct server *server, struct conn *conn) { rstatus_t status; status = nc_resolve(&server->addrstr, server->port, &server->info); if (status != NC_OK) { conn->err = EHOSTDOWN; conn->done = 1; return; } conn->family = server->info.family; conn->addrlen = server->info.addrlen; conn->addr = (struct sockaddr *)&server->info.addr; } void server_ref(struct conn *conn, void *owner) { struct server *server = owner; ASSERT(!conn->client && !conn->proxy); ASSERT(conn->owner == NULL); server_resolve(server, conn); server->ns_conn_q++; TAILQ_INSERT_TAIL(&server->s_conn_q, conn, conn_tqe); conn->owner = owner; log_debug(LOG_VVERB, "ref conn %p owner %p into '%.*s", conn, server, server->pname.len, server->pname.data); } void server_unref(struct conn *conn) { struct server *server; ASSERT(!conn->client && !conn->proxy); ASSERT(conn->owner != NULL); server = conn->owner; conn->owner = NULL; ASSERT(server->ns_conn_q != 0); server->ns_conn_q--; TAILQ_REMOVE(&server->s_conn_q, conn, conn_tqe); log_debug(LOG_VVERB, "unref conn %p owner %p from '%.*s'", conn, server, server->pname.len, server->pname.data); } int server_timeout(struct conn *conn) { struct server *server; struct server_pool *pool; ASSERT(!conn->client && !conn->proxy); server = conn->owner; pool = server->owner; return pool->timeout; } bool server_active(struct conn *conn) { ASSERT(!conn->client && !conn->proxy); if (!TAILQ_EMPTY(&conn->imsg_q)) { log_debug(LOG_VVERB, "s %d is active", conn->sd); return true; } if (!TAILQ_EMPTY(&conn->omsg_q)) { log_debug(LOG_VVERB, "s %d is active", conn->sd); return true; } if (conn->rmsg != NULL) { log_debug(LOG_VVERB, "s %d is active", conn->sd); return true; } if (conn->smsg != NULL) { log_debug(LOG_VVERB, "s %d is active", conn->sd); return true; } log_debug(LOG_VVERB, "s %d is inactive", conn->sd); return false; } static rstatus_t server_each_set_owner(void *elem, void *data) { struct server *s = elem; struct server_pool *sp = data; s->owner = sp; return NC_OK; } rstatus_t server_init(struct array *server, struct array *conf_server, struct server_pool *sp) { rstatus_t status; uint32_t nserver; nserver = array_n(conf_server); ASSERT(nserver != 0); ASSERT(array_n(server) == 0); status = array_init(server, nserver, sizeof(struct server)); if (status != NC_OK) { return status; } /* transform conf server to server */ status = array_each(conf_server, conf_server_each_transform, server); if (status != NC_OK) { server_deinit(server); return status; } ASSERT(array_n(server) == nserver); /* set server owner */ status = array_each(server, server_each_set_owner, sp); if (status != NC_OK) { server_deinit(server); return status; } log_debug(LOG_DEBUG, "init %"PRIu32" servers in pool %"PRIu32" '%.*s'", nserver, sp->idx, sp->name.len, sp->name.data); return NC_OK; } void server_deinit(struct array *server) { uint32_t i, nserver; for (i = 0, nserver = array_n(server); i < nserver; i++) { struct server *s; s = array_pop(server); ASSERT(TAILQ_EMPTY(&s->s_conn_q) && s->ns_conn_q == 0); } array_deinit(server); } struct conn * server_conn(struct server *server) { struct server_pool *pool; struct conn *conn; pool = server->owner; /* * FIXME: handle multiple server connections per server and do load * balancing on it. Support multiple algorithms for * 'server_connections:' > 0 key */ if (server->ns_conn_q < pool->server_connections) { return conn_get(server, false, pool->redis); } ASSERT(server->ns_conn_q == pool->server_connections); /* * Pick a server connection from the head of the queue and insert * it back into the tail of queue to maintain the lru order */ conn = TAILQ_FIRST(&server->s_conn_q); ASSERT(!conn->client && !conn->proxy); TAILQ_REMOVE(&server->s_conn_q, conn, conn_tqe); TAILQ_INSERT_TAIL(&server->s_conn_q, conn, conn_tqe); return conn; } static rstatus_t server_each_preconnect(void *elem, void *data) { rstatus_t status; struct server *server; struct server_pool *pool; struct conn *conn; server = elem; pool = server->owner; conn = server_conn(server); if (conn == NULL) { return NC_ENOMEM; } status = server_connect(pool->ctx, server, conn); if (status != NC_OK) { log_warn("connect to server '%.*s' failed, ignored: %s", server->pname.len, server->pname.data, strerror(errno)); server_close(pool->ctx, conn); } return NC_OK; } static rstatus_t server_each_disconnect(void *elem, void *data) { struct server *server; struct server_pool *pool; server = elem; pool = server->owner; while (!TAILQ_EMPTY(&server->s_conn_q)) { struct conn *conn; ASSERT(server->ns_conn_q > 0); conn = TAILQ_FIRST(&server->s_conn_q); conn->close(pool->ctx, conn); } return NC_OK; } static void server_failure(struct context *ctx, struct server *server) { struct server_pool *pool = server->owner; int64_t now, next; rstatus_t status; if (!pool->auto_eject_hosts) { return; } server->failure_count++; log_debug(LOG_VERB, "server '%.*s' failure count %"PRIu32" limit %"PRIu32, server->pname.len, server->pname.data, server->failure_count, pool->server_failure_limit); if (server->failure_count < pool->server_failure_limit) { return; } now = nc_usec_now(); if (now < 0) { return; } stats_server_set_ts(ctx, server, server_ejected_at, now); next = now + pool->server_retry_timeout; log_debug(LOG_INFO, "update pool %"PRIu32" '%.*s' to delete server '%.*s' " "for next %"PRIu32" secs", pool->idx, pool->name.len, pool->name.data, server->pname.len, server->pname.data, pool->server_retry_timeout / 1000 / 1000); stats_pool_incr(ctx, pool, server_ejects); server->failure_count = 0; server->next_retry = next; status = server_pool_run(pool); if (status != NC_OK) { log_error("updating pool %"PRIu32" '%.*s' failed: %s", pool->idx, pool->name.len, pool->name.data, strerror(errno)); } } static void server_close_stats(struct context *ctx, struct server *server, err_t err, unsigned eof, unsigned connected) { if (connected) { stats_server_decr(ctx, server, server_connections); } if (eof) { stats_server_incr(ctx, server, server_eof); return; } switch (err) { case ETIMEDOUT: stats_server_incr(ctx, server, server_timedout); break; case EPIPE: case ECONNRESET: case ECONNABORTED: case ECONNREFUSED: case ENOTCONN: case ENETDOWN: case ENETUNREACH: case EHOSTDOWN: case EHOSTUNREACH: default: stats_server_incr(ctx, server, server_err); break; } } void server_close(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg, *nmsg; /* current and next message */ struct conn *c_conn; /* peer client connection */ ASSERT(!conn->client && !conn->proxy); server_close_stats(ctx, conn->owner, conn->err, conn->eof, conn->connected); conn->connected = false; if (conn->sd < 0) { server_failure(ctx, conn->owner); conn->unref(conn); conn_put(conn); return; } for (msg = TAILQ_FIRST(&conn->imsg_q); msg != NULL; msg = nmsg) { nmsg = TAILQ_NEXT(msg, s_tqe); /* dequeue the message (request) from server inq */ conn->dequeue_inq(ctx, conn, msg); /* * Don't send any error response, if * 1. request is tagged as noreply or, * 2. client has already closed its connection */ if (msg->swallow || msg->noreply) { log_debug(LOG_INFO, "close s %d swallow req %"PRIu64" len %"PRIu32 " type %d", conn->sd, msg->id, msg->mlen, msg->type); req_put(msg); } else { c_conn = msg->owner; ASSERT(c_conn->client && !c_conn->proxy); msg->done = 1; msg->error = 1; msg->err = conn->err; if (msg->frag_owner != NULL) { msg->frag_owner->nfrag_done++; } if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) { event_add_out(ctx->evb, msg->owner); } log_debug(LOG_INFO, "close s %d schedule error for req %"PRIu64" " "len %"PRIu32" type %d from c %d%c %s", conn->sd, msg->id, msg->mlen, msg->type, c_conn->sd, conn->err ? ':' : ' ', conn->err ? strerror(conn->err): " "); } } ASSERT(TAILQ_EMPTY(&conn->imsg_q)); for (msg = TAILQ_FIRST(&conn->omsg_q); msg != NULL; msg = nmsg) { nmsg = TAILQ_NEXT(msg, s_tqe); /* dequeue the message (request) from server outq */ conn->dequeue_outq(ctx, conn, msg); if (msg->swallow) { log_debug(LOG_INFO, "close s %d swallow req %"PRIu64" len %"PRIu32 " type %d", conn->sd, msg->id, msg->mlen, msg->type); req_put(msg); } else { c_conn = msg->owner; ASSERT(c_conn->client && !c_conn->proxy); msg->done = 1; msg->error = 1; msg->err = conn->err; if (msg->frag_owner != NULL) { msg->frag_owner->nfrag_done++; } if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) { event_add_out(ctx->evb, msg->owner); } log_debug(LOG_INFO, "close s %d schedule error for req %"PRIu64" " "len %"PRIu32" type %d from c %d%c %s", conn->sd, msg->id, msg->mlen, msg->type, c_conn->sd, conn->err ? ':' : ' ', conn->err ? strerror(conn->err): " "); } } ASSERT(TAILQ_EMPTY(&conn->omsg_q)); msg = conn->rmsg; if (msg != NULL) { conn->rmsg = NULL; ASSERT(!msg->request); ASSERT(msg->peer == NULL); rsp_put(msg); log_debug(LOG_INFO, "close s %d discarding rsp %"PRIu64" len %"PRIu32" " "in error", conn->sd, msg->id, msg->mlen); } ASSERT(conn->smsg == NULL); server_failure(ctx, conn->owner); conn->unref(conn); status = close(conn->sd); if (status < 0) { log_error("close s %d failed, ignored: %s", conn->sd, strerror(errno)); } conn->sd = -1; conn_put(conn); } rstatus_t server_connect(struct context *ctx, struct server *server, struct conn *conn) { rstatus_t status; ASSERT(!conn->client && !conn->proxy); if (conn->err) { ASSERT(conn->done && conn->sd < 0); errno = conn->err; return NC_ERROR; } if (conn->sd > 0) { /* already connected on server connection */ return NC_OK; } log_debug(LOG_VVERB, "connect to server '%.*s'", server->pname.len, server->pname.data); conn->sd = socket(conn->family, SOCK_STREAM, 0); if (conn->sd < 0) { log_error("socket for server '%.*s' failed: %s", server->pname.len, server->pname.data, strerror(errno)); status = NC_ERROR; goto error; } status = nc_set_nonblocking(conn->sd); if (status != NC_OK) { log_error("set nonblock on s %d for server '%.*s' failed: %s", conn->sd, server->pname.len, server->pname.data, strerror(errno)); goto error; } if (server->pname.data[0] != '/') { status = nc_set_tcpnodelay(conn->sd); if (status != NC_OK) { log_warn("set tcpnodelay on s %d for server '%.*s' failed, ignored: %s", conn->sd, server->pname.len, server->pname.data, strerror(errno)); } } status = event_add_conn(ctx->evb, conn); if (status != NC_OK) { log_error("event add conn s %d for server '%.*s' failed: %s", conn->sd, server->pname.len, server->pname.data, strerror(errno)); goto error; } ASSERT(!conn->connecting && !conn->connected); status = connect(conn->sd, conn->addr, conn->addrlen); if (status != NC_OK) { if (errno == EINPROGRESS) { conn->connecting = 1; log_debug(LOG_DEBUG, "connecting on s %d to server '%.*s'", conn->sd, server->pname.len, server->pname.data); return NC_OK; } log_error("connect on s %d to server '%.*s' failed: %s", conn->sd, server->pname.len, server->pname.data, strerror(errno)); goto error; } ASSERT(!conn->connecting); conn->connected = 1; log_debug(LOG_INFO, "connected on s %d to server '%.*s'", conn->sd, server->pname.len, server->pname.data); return NC_OK; error: conn->err = errno; return status; } void server_connected(struct context *ctx, struct conn *conn) { struct server *server = conn->owner; ASSERT(!conn->client && !conn->proxy); ASSERT(conn->connecting && !conn->connected); stats_server_incr(ctx, server, server_connections); conn->connecting = 0; conn->connected = 1; conn->post_connect(ctx, conn, server); log_debug(LOG_INFO, "connected on s %d to server '%.*s'", conn->sd, server->pname.len, server->pname.data); } void server_ok(struct context *ctx, struct conn *conn) { struct server *server = conn->owner; ASSERT(!conn->client && !conn->proxy); ASSERT(conn->connected); if (server->failure_count != 0) { log_debug(LOG_VERB, "reset server '%.*s' failure count from %"PRIu32 " to 0", server->pname.len, server->pname.data, server->failure_count); server->failure_count = 0; server->next_retry = 0LL; } } static rstatus_t server_pool_update(struct server_pool *pool) { rstatus_t status; int64_t now; uint32_t pnlive_server; /* prev # live server */ if (!pool->auto_eject_hosts) { return NC_OK; } if (pool->next_rebuild == 0LL) { return NC_OK; } now = nc_usec_now(); if (now < 0) { return NC_ERROR; } if (now <= pool->next_rebuild) { if (pool->nlive_server == 0) { errno = ECONNREFUSED; return NC_ERROR; } return NC_OK; } pnlive_server = pool->nlive_server; status = server_pool_run(pool); if (status != NC_OK) { log_error("updating pool %"PRIu32" with dist %d failed: %s", pool->idx, pool->dist_type, strerror(errno)); return status; } log_debug(LOG_INFO, "update pool %"PRIu32" '%.*s' to add %"PRIu32" servers", pool->idx, pool->name.len, pool->name.data, pool->nlive_server - pnlive_server); return NC_OK; } static uint32_t server_pool_hash(struct server_pool *pool, uint8_t *key, uint32_t keylen) { ASSERT(array_n(&pool->server) != 0); ASSERT(key != NULL); if (array_n(&pool->server) == 1) { return 0; } if (keylen == 0) { return 0; } return pool->key_hash((char *)key, keylen); } uint32_t server_pool_idx(struct server_pool *pool, uint8_t *key, uint32_t keylen) { uint32_t hash, idx; ASSERT(array_n(&pool->server) != 0); ASSERT(key != NULL); /* * If hash_tag: is configured for this server pool, we use the part of * the key within the hash tag as an input to the distributor. Otherwise * we use the full key */ if (!string_empty(&pool->hash_tag)) { struct string *tag = &pool->hash_tag; uint8_t *tag_start, *tag_end; tag_start = nc_strchr(key, key + keylen, tag->data[0]); if (tag_start != NULL) { tag_end = nc_strchr(tag_start + 1, key + keylen, tag->data[1]); if ((tag_end != NULL) && (tag_end - tag_start > 1)) { key = tag_start + 1; keylen = (uint32_t)(tag_end - key); } } } switch (pool->dist_type) { case DIST_KETAMA: hash = server_pool_hash(pool, key, keylen); idx = ketama_dispatch(pool->continuum, pool->ncontinuum, hash); break; case DIST_MODULA: hash = server_pool_hash(pool, key, keylen); idx = modula_dispatch(pool->continuum, pool->ncontinuum, hash); break; case DIST_RANDOM: idx = random_dispatch(pool->continuum, pool->ncontinuum, 0); break; default: NOT_REACHED(); return 0; } ASSERT(idx < array_n(&pool->server)); return idx; } static struct server * server_pool_server(struct server_pool *pool, uint8_t *key, uint32_t keylen) { struct server *server; uint32_t idx; idx = server_pool_idx(pool, key, keylen); server = array_get(&pool->server, idx); log_debug(LOG_VERB, "key '%.*s' on dist %d maps to server '%.*s'", keylen, key, pool->dist_type, server->pname.len, server->pname.data); return server; } struct conn * server_pool_conn(struct context *ctx, struct server_pool *pool, uint8_t *key, uint32_t keylen) { rstatus_t status; struct server *server; struct conn *conn; status = server_pool_update(pool); if (status != NC_OK) { return NULL; } /* from a given {key, keylen} pick a server from pool */ server = server_pool_server(pool, key, keylen); if (server == NULL) { return NULL; } /* pick a connection to a given server */ conn = server_conn(server); if (conn == NULL) { return NULL; } status = server_connect(ctx, server, conn); if (status != NC_OK) { server_close(ctx, conn); return NULL; } return conn; } static rstatus_t server_pool_each_preconnect(void *elem, void *data) { rstatus_t status; struct server_pool *sp = elem; if (!sp->preconnect) { return NC_OK; } status = array_each(&sp->server, server_each_preconnect, NULL); if (status != NC_OK) { return status; } return NC_OK; } rstatus_t server_pool_preconnect(struct context *ctx) { rstatus_t status; status = array_each(&ctx->pool, server_pool_each_preconnect, NULL); if (status != NC_OK) { return status; } return NC_OK; } static rstatus_t server_pool_each_disconnect(void *elem, void *data) { rstatus_t status; struct server_pool *sp = elem; status = array_each(&sp->server, server_each_disconnect, NULL); if (status != NC_OK) { return status; } return NC_OK; } void server_pool_disconnect(struct context *ctx) { array_each(&ctx->pool, server_pool_each_disconnect, NULL); } static rstatus_t server_pool_each_set_owner(void *elem, void *data) { struct server_pool *sp = elem; struct context *ctx = data; sp->ctx = ctx; return NC_OK; } static rstatus_t server_pool_each_calc_connections(void *elem, void *data) { struct server_pool *sp = elem; struct context *ctx = data; ctx->max_nsconn += sp->server_connections * array_n(&sp->server); ctx->max_nsconn += 1; /* pool listening socket */ return NC_OK; } rstatus_t server_pool_run(struct server_pool *pool) { ASSERT(array_n(&pool->server) != 0); switch (pool->dist_type) { case DIST_KETAMA: return ketama_update(pool); case DIST_MODULA: return modula_update(pool); case DIST_RANDOM: return random_update(pool); default: NOT_REACHED(); return NC_ERROR; } return NC_OK; } static rstatus_t server_pool_each_run(void *elem, void *data) { return server_pool_run(elem); } rstatus_t server_pool_init(struct array *server_pool, struct array *conf_pool, struct context *ctx) { rstatus_t status; uint32_t npool; npool = array_n(conf_pool); ASSERT(npool != 0); ASSERT(array_n(server_pool) == 0); status = array_init(server_pool, npool, sizeof(struct server_pool)); if (status != NC_OK) { return status; } /* transform conf pool to server pool */ status = array_each(conf_pool, conf_pool_each_transform, server_pool); if (status != NC_OK) { server_pool_deinit(server_pool); return status; } ASSERT(array_n(server_pool) == npool); /* set ctx as the server pool owner */ status = array_each(server_pool, server_pool_each_set_owner, ctx); if (status != NC_OK) { server_pool_deinit(server_pool); return status; } /* compute max server connections */ ctx->max_nsconn = 0; status = array_each(server_pool, server_pool_each_calc_connections, ctx); if (status != NC_OK) { server_pool_deinit(server_pool); return status; } /* update server pool continuum */ status = array_each(server_pool, server_pool_each_run, NULL); if (status != NC_OK) { server_pool_deinit(server_pool); return status; } log_debug(LOG_DEBUG, "init %"PRIu32" pools", npool); return NC_OK; } void server_pool_deinit(struct array *server_pool) { uint32_t i, npool; for (i = 0, npool = array_n(server_pool); i < npool; i++) { struct server_pool *sp; sp = array_pop(server_pool); ASSERT(sp->p_conn == NULL); ASSERT(TAILQ_EMPTY(&sp->c_conn_q) && sp->nc_conn_q == 0); if (sp->continuum != NULL) { nc_free(sp->continuum); sp->ncontinuum = 0; sp->nserver_continuum = 0; sp->nlive_server = 0; } server_deinit(&sp->server); log_debug(LOG_DEBUG, "deinit pool %"PRIu32" '%.*s'", sp->idx, sp->name.len, sp->name.data); } array_deinit(server_pool); log_debug(LOG_DEBUG, "deinit %"PRIu32" pools", npool); } nutcracker-0.4.1+dfsg/src/nc_string.h0000664000000000000000000001135512542173515016234 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_STRING_H_ #define _NC_STRING_H_ #include #include #include #include struct string { uint32_t len; /* string length */ uint8_t *data; /* string data */ }; #define string(_str) { sizeof(_str) - 1, (uint8_t *)(_str) } #define null_string { 0, NULL } #define string_set_text(_str, _text) do { \ (_str)->len = (uint32_t)(sizeof(_text) - 1);\ (_str)->data = (uint8_t *)(_text); \ } while (0); #define string_set_raw(_str, _raw) do { \ (_str)->len = (uint32_t)(nc_strlen(_raw)); \ (_str)->data = (uint8_t *)(_raw); \ } while (0); void string_init(struct string *str); void string_deinit(struct string *str); bool string_empty(const struct string *str); rstatus_t string_duplicate(struct string *dst, const struct string *src); rstatus_t string_copy(struct string *dst, const uint8_t *src, uint32_t srclen); int string_compare(const struct string *s1, const struct string *s2); /* * Wrapper around common routines for manipulating C character * strings */ #define nc_memcpy(_d, _c, _n) \ memcpy(_d, _c, (size_t)(_n)) #define nc_memmove(_d, _c, _n) \ memmove(_d, _c, (size_t)(_n)) #define nc_memchr(_d, _c, _n) \ memchr(_d, _c, (size_t)(_n)) #define nc_strlen(_s) \ strlen((char *)(_s)) #define nc_strncmp(_s1, _s2, _n) \ strncmp((char *)(_s1), (char *)(_s2), (size_t)(_n)) #define nc_strchr(_p, _l, _c) \ _nc_strchr((uint8_t *)(_p), (uint8_t *)(_l), (uint8_t)(_c)) #define nc_strrchr(_p, _s, _c) \ _nc_strrchr((uint8_t *)(_p),(uint8_t *)(_s), (uint8_t)(_c)) #define nc_strndup(_s, _n) \ (uint8_t *)strndup((char *)(_s), (size_t)(_n)); /* * snprintf(s, n, ...) will write at most n - 1 of the characters printed into * the output string; the nth character then gets the terminating `\0'; if * the return value is greater than or equal to the n argument, the string * was too short and some of the printed characters were discarded; the output * is always null-terminated. * * Note that, the return value of snprintf() is always the number of characters * that would be printed into the output string, assuming n were limited not * including the trailing `\0' used to end output. * * scnprintf(s, n, ...) is same as snprintf() except, it returns the number * of characters printed into the output string not including the trailing '\0' */ #define nc_snprintf(_s, _n, ...) \ snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) #define nc_scnprintf(_s, _n, ...) \ _scnprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) #define nc_vsnprintf(_s, _n, _f, _a) \ vsnprintf((char *)(_s), (size_t)(_n), _f, _a) #define nc_vscnprintf(_s, _n, _f, _a) \ _vscnprintf((char *)(_s), (size_t)(_n), _f, _a) #define nc_strftime(_s, _n, fmt, tm) \ (int)strftime((char *)(_s), (size_t)(_n), fmt, tm) /* * A (very) limited version of snprintf * @param to Destination buffer * @param n Size of destination buffer * @param fmt printf() style format string * @returns Number of bytes written, including terminating '\0' * Supports 'd' 'i' 'u' 'x' 'p' 's' conversion * Supports 'l' and 'll' modifiers for integral types * Does not support any width/precision * Implemented with simplicity, and async-signal-safety in mind */ int _safe_vsnprintf(char *to, size_t size, const char *format, va_list ap); int _safe_snprintf(char *to, size_t n, const char *fmt, ...); #define nc_safe_snprintf(_s, _n, ...) \ _safe_snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) #define nc_safe_vsnprintf(_s, _n, _f, _a) \ _safe_vsnprintf((char *)(_s), (size_t)(_n), _f, _a) static inline uint8_t * _nc_strchr(uint8_t *p, uint8_t *last, uint8_t c) { while (p < last) { if (*p == c) { return p; } p++; } return NULL; } static inline uint8_t * _nc_strrchr(uint8_t *p, uint8_t *start, uint8_t c) { while (p >= start) { if (*p == c) { return p; } p--; } return NULL; } #endif nutcracker-0.4.1+dfsg/src/nc_proxy.c0000664000000000000000000002472112542173515016103 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include void proxy_ref(struct conn *conn, void *owner) { struct server_pool *pool = owner; ASSERT(!conn->client && conn->proxy); ASSERT(conn->owner == NULL); conn->family = pool->info.family; conn->addrlen = pool->info.addrlen; conn->addr = (struct sockaddr *)&pool->info.addr; pool->p_conn = conn; /* owner of the proxy connection is the server pool */ conn->owner = owner; log_debug(LOG_VVERB, "ref conn %p owner %p into pool %"PRIu32"", conn, pool, pool->idx); } void proxy_unref(struct conn *conn) { struct server_pool *pool; ASSERT(!conn->client && conn->proxy); ASSERT(conn->owner != NULL); pool = conn->owner; conn->owner = NULL; pool->p_conn = NULL; log_debug(LOG_VVERB, "unref conn %p owner %p from pool %"PRIu32"", conn, pool, pool->idx); } void proxy_close(struct context *ctx, struct conn *conn) { rstatus_t status; ASSERT(!conn->client && conn->proxy); if (conn->sd < 0) { conn->unref(conn); conn_put(conn); return; } ASSERT(conn->rmsg == NULL); ASSERT(conn->smsg == NULL); ASSERT(TAILQ_EMPTY(&conn->imsg_q)); ASSERT(TAILQ_EMPTY(&conn->omsg_q)); conn->unref(conn); status = close(conn->sd); if (status < 0) { log_error("close p %d failed, ignored: %s", conn->sd, strerror(errno)); } conn->sd = -1; conn_put(conn); } static rstatus_t proxy_reuse(struct conn *p) { rstatus_t status; struct sockaddr_un *un; switch (p->family) { case AF_INET: case AF_INET6: status = nc_set_reuseaddr(p->sd); break; case AF_UNIX: /* * bind() will fail if the pathname already exist. So, we call unlink() * to delete the pathname, in case it already exists. If it does not * exist, unlink() returns error, which we ignore */ un = (struct sockaddr_un *) p->addr; unlink(un->sun_path); status = NC_OK; break; default: NOT_REACHED(); status = NC_ERROR; } return status; } static rstatus_t proxy_listen(struct context *ctx, struct conn *p) { rstatus_t status; struct server_pool *pool = p->owner; ASSERT(p->proxy); p->sd = socket(p->family, SOCK_STREAM, 0); if (p->sd < 0) { log_error("socket failed: %s", strerror(errno)); return NC_ERROR; } status = proxy_reuse(p); if (status < 0) { log_error("reuse of addr '%.*s' for listening on p %d failed: %s", pool->addrstr.len, pool->addrstr.data, p->sd, strerror(errno)); return NC_ERROR; } status = bind(p->sd, p->addr, p->addrlen); if (status < 0) { log_error("bind on p %d to addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } if (p->family == AF_UNIX && pool->perm) { struct sockaddr_un *un = (struct sockaddr_un *)p->addr; status = chmod(un->sun_path, pool->perm); if (status < 0) { log_error("chmod on p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } } status = listen(p->sd, pool->backlog); if (status < 0) { log_error("listen on p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } status = nc_set_nonblocking(p->sd); if (status < 0) { log_error("set nonblock on p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } status = event_add_conn(ctx->evb, p); if (status < 0) { log_error("event add conn p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } status = event_del_out(ctx->evb, p); if (status < 0) { log_error("event del out p %d on addr '%.*s' failed: %s", p->sd, pool->addrstr.len, pool->addrstr.data, strerror(errno)); return NC_ERROR; } return NC_OK; } rstatus_t proxy_each_init(void *elem, void *data) { rstatus_t status; struct server_pool *pool = elem; struct conn *p; p = conn_get_proxy(pool); if (p == NULL) { return NC_ENOMEM; } status = proxy_listen(pool->ctx, p); if (status != NC_OK) { p->close(pool->ctx, p); return status; } log_debug(LOG_NOTICE, "p %d listening on '%.*s' in %s pool %"PRIu32" '%.*s'" " with %"PRIu32" servers", p->sd, pool->addrstr.len, pool->addrstr.data, pool->redis ? "redis" : "memcache", pool->idx, pool->name.len, pool->name.data, array_n(&pool->server)); return NC_OK; } rstatus_t proxy_init(struct context *ctx) { rstatus_t status; ASSERT(array_n(&ctx->pool) != 0); status = array_each(&ctx->pool, proxy_each_init, NULL); if (status != NC_OK) { proxy_deinit(ctx); return status; } log_debug(LOG_VVERB, "init proxy with %"PRIu32" pools", array_n(&ctx->pool)); return NC_OK; } rstatus_t proxy_each_deinit(void *elem, void *data) { struct server_pool *pool = elem; struct conn *p; p = pool->p_conn; if (p != NULL) { p->close(pool->ctx, p); } return NC_OK; } void proxy_deinit(struct context *ctx) { rstatus_t status; ASSERT(array_n(&ctx->pool) != 0); status = array_each(&ctx->pool, proxy_each_deinit, NULL); if (status != NC_OK) { return; } log_debug(LOG_VVERB, "deinit proxy with %"PRIu32" pools", array_n(&ctx->pool)); } static rstatus_t proxy_accept(struct context *ctx, struct conn *p) { rstatus_t status; struct conn *c; int sd; ASSERT(p->proxy && !p->client); ASSERT(p->sd > 0); ASSERT(p->recv_active && p->recv_ready); for (;;) { sd = accept(p->sd, NULL, NULL); if (sd < 0) { if (errno == EINTR) { log_debug(LOG_VERB, "accept on p %d not ready - eintr", p->sd); continue; } if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ECONNABORTED) { log_debug(LOG_VERB, "accept on p %d not ready - eagain", p->sd); p->recv_ready = 0; return NC_OK; } /* * Workaround of https://github.com/twitter/twemproxy/issues/97 * * We should never reach here because the check for conn_ncurr_cconn() * against ctx->max_ncconn should catch this earlier in the cycle. * If we reach here ignore EMFILE/ENFILE, return NC_OK will enable * the server continue to run instead of close the server socket * * The right solution however, is on EMFILE/ENFILE to mask out IN * event on the proxy and mask it back in when some existing * connections gets closed */ if (errno == EMFILE || errno == ENFILE) { log_debug(LOG_CRIT, "accept on p %d with max fds %"PRIu32" " "used connections %"PRIu32" max client connections %"PRIu32" " "curr client connections %"PRIu32" failed: %s", p->sd, ctx->max_nfd, conn_ncurr_conn(), ctx->max_ncconn, conn_ncurr_cconn(), strerror(errno)); p->recv_ready = 0; return NC_OK; } log_error("accept on p %d failed: %s", p->sd, strerror(errno)); return NC_ERROR; } break; } if (conn_ncurr_cconn() >= ctx->max_ncconn) { log_debug(LOG_CRIT, "client connections %"PRIu32" exceed limit %"PRIu32, conn_ncurr_cconn(), ctx->max_ncconn); status = close(sd); if (status < 0) { log_error("close c %d failed, ignored: %s", sd, strerror(errno)); } return NC_OK; } c = conn_get(p->owner, true, p->redis); if (c == NULL) { log_error("get conn for c %d from p %d failed: %s", sd, p->sd, strerror(errno)); status = close(sd); if (status < 0) { log_error("close c %d failed, ignored: %s", sd, strerror(errno)); } return NC_ENOMEM; } c->sd = sd; stats_pool_incr(ctx, c->owner, client_connections); status = nc_set_nonblocking(c->sd); if (status < 0) { log_error("set nonblock on c %d from p %d failed: %s", c->sd, p->sd, strerror(errno)); c->close(ctx, c); return status; } if (p->family == AF_INET || p->family == AF_INET6) { status = nc_set_tcpnodelay(c->sd); if (status < 0) { log_warn("set tcpnodelay on c %d from p %d failed, ignored: %s", c->sd, p->sd, strerror(errno)); } } status = event_add_conn(ctx->evb, c); if (status < 0) { log_error("event add conn from p %d failed: %s", p->sd, strerror(errno)); c->close(ctx, c); return status; } log_debug(LOG_NOTICE, "accepted c %d on p %d from '%s'", c->sd, p->sd, nc_unresolve_peer_desc(c->sd)); return NC_OK; } rstatus_t proxy_recv(struct context *ctx, struct conn *conn) { rstatus_t status; ASSERT(conn->proxy && !conn->client); ASSERT(conn->recv_active); conn->recv_ready = 1; do { status = proxy_accept(ctx, conn); if (status != NC_OK) { return status; } } while (conn->recv_ready); return NC_OK; } nutcracker-0.4.1+dfsg/src/nc_response.c0000664000000000000000000002360512542173515016560 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include struct msg * rsp_get(struct conn *conn) { struct msg *msg; ASSERT(!conn->client && !conn->proxy); msg = msg_get(conn, false, conn->redis); if (msg == NULL) { conn->err = errno; } return msg; } void rsp_put(struct msg *msg) { ASSERT(!msg->request); ASSERT(msg->peer == NULL); msg_put(msg); } static struct msg * rsp_make_error(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg *pmsg; /* peer message (response) */ struct msg *cmsg, *nmsg; /* current and next message (request) */ uint64_t id; err_t err; ASSERT(conn->client && !conn->proxy); ASSERT(msg->request && req_error(conn, msg)); ASSERT(msg->owner == conn); id = msg->frag_id; if (id != 0) { for (err = 0, cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = nmsg) { nmsg = TAILQ_NEXT(cmsg, c_tqe); /* dequeue request (error fragment) from client outq */ conn->dequeue_outq(ctx, conn, cmsg); if (err == 0 && cmsg->err != 0) { err = cmsg->err; } req_put(cmsg); } } else { err = msg->err; } pmsg = msg->peer; if (pmsg != NULL) { ASSERT(!pmsg->request && pmsg->peer == msg); msg->peer = NULL; pmsg->peer = NULL; rsp_put(pmsg); } return msg_get_error(conn->redis, err); } struct msg * rsp_recv_next(struct context *ctx, struct conn *conn, bool alloc) { struct msg *msg; ASSERT(!conn->client && !conn->proxy); if (conn->eof) { msg = conn->rmsg; /* server sent eof before sending the entire request */ if (msg != NULL) { conn->rmsg = NULL; ASSERT(msg->peer == NULL); ASSERT(!msg->request); log_error("eof s %d discarding incomplete rsp %"PRIu64" len " "%"PRIu32"", conn->sd, msg->id, msg->mlen); rsp_put(msg); } /* * We treat TCP half-close from a server different from how we treat * those from a client. On a FIN from a server, we close the connection * immediately by sending the second FIN even if there were outstanding * or pending requests. This is actually a tricky part in the FA, as * we don't expect this to happen unless the server is misbehaving or * it crashes */ conn->done = 1; log_error("s %d active %d is done", conn->sd, conn->active(conn)); return NULL; } msg = conn->rmsg; if (msg != NULL) { ASSERT(!msg->request); return msg; } if (!alloc) { return NULL; } msg = rsp_get(conn); if (msg != NULL) { conn->rmsg = msg; } return msg; } static bool rsp_filter(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg *pmsg; ASSERT(!conn->client && !conn->proxy); if (msg_empty(msg)) { ASSERT(conn->rmsg == NULL); log_debug(LOG_VERB, "filter empty rsp %"PRIu64" on s %d", msg->id, conn->sd); rsp_put(msg); return true; } pmsg = TAILQ_FIRST(&conn->omsg_q); if (pmsg == NULL) { log_debug(LOG_ERR, "filter stray rsp %"PRIu64" len %"PRIu32" on s %d", msg->id, msg->mlen, conn->sd); rsp_put(msg); /* * Memcached server can respond with an error response before it has * received the entire request. This is most commonly seen for set * requests that exceed item_size_max. IMO, this behavior of memcached * is incorrect. The right behavior for update requests that are over * item_size_max would be to either: * - close the connection Or, * - read the entire item_size_max data and then send CLIENT_ERROR * * We handle this stray packet scenario in nutcracker by closing the * server connection which would end up sending SERVER_ERROR to all * clients that have requests pending on this server connection. The * fix is aggressive, but not doing so would lead to clients getting * out of sync with the server and as a result clients end up getting * responses that don't correspond to the right request. * * See: https://github.com/twitter/twemproxy/issues/149 */ conn->err = EINVAL; conn->done = 1; return true; } ASSERT(pmsg->peer == NULL); ASSERT(pmsg->request && !pmsg->done); /* * If the response from a server suggests a protocol level transient * failure, close the server connection and send back a generic error * response to the client. * * If auto_eject_host is enabled, this will also update the failure_count * and eject the server if it exceeds the failure_limit */ if (msg->failure(msg)) { log_debug(LOG_INFO, "server failure rsp %"PRIu64" len %"PRIu32" " "type %d on s %d", msg->id, msg->mlen, msg->type, conn->sd); rsp_put(msg); conn->err = EINVAL; conn->done = 1; return true; } if (pmsg->swallow) { conn->swallow_msg(conn, pmsg, msg); conn->dequeue_outq(ctx, conn, pmsg); pmsg->done = 1; log_debug(LOG_INFO, "swallow rsp %"PRIu64" len %"PRIu32" of req " "%"PRIu64" on s %d", msg->id, msg->mlen, pmsg->id, conn->sd); rsp_put(msg); req_put(pmsg); return true; } return false; } static void rsp_forward_stats(struct context *ctx, struct server *server, struct msg *msg, uint32_t msgsize) { ASSERT(!msg->request); stats_server_incr(ctx, server, responses); stats_server_incr_by(ctx, server, response_bytes, msgsize); } static void rsp_forward(struct context *ctx, struct conn *s_conn, struct msg *msg) { rstatus_t status; struct msg *pmsg; struct conn *c_conn; uint32_t msgsize; ASSERT(!s_conn->client && !s_conn->proxy); msgsize = msg->mlen; /* response from server implies that server is ok and heartbeating */ server_ok(ctx, s_conn); /* dequeue peer message (request) from server */ pmsg = TAILQ_FIRST(&s_conn->omsg_q); ASSERT(pmsg != NULL && pmsg->peer == NULL); ASSERT(pmsg->request && !pmsg->done); s_conn->dequeue_outq(ctx, s_conn, pmsg); pmsg->done = 1; /* establish msg <-> pmsg (response <-> request) link */ pmsg->peer = msg; msg->peer = pmsg; msg->pre_coalesce(msg); c_conn = pmsg->owner; ASSERT(c_conn->client && !c_conn->proxy); if (req_done(c_conn, TAILQ_FIRST(&c_conn->omsg_q))) { status = event_add_out(ctx->evb, c_conn); if (status != NC_OK) { c_conn->err = errno; } } rsp_forward_stats(ctx, s_conn->owner, msg, msgsize); } void rsp_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg) { ASSERT(!conn->client && !conn->proxy); ASSERT(msg != NULL && conn->rmsg == msg); ASSERT(!msg->request); ASSERT(msg->owner == conn); ASSERT(nmsg == NULL || !nmsg->request); /* enqueue next message (response), if any */ conn->rmsg = nmsg; if (rsp_filter(ctx, conn, msg)) { return; } rsp_forward(ctx, conn, msg); } struct msg * rsp_send_next(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg, *pmsg; /* response and it's peer request */ ASSERT(conn->client && !conn->proxy); pmsg = TAILQ_FIRST(&conn->omsg_q); if (pmsg == NULL || !req_done(conn, pmsg)) { /* nothing is outstanding, initiate close? */ if (pmsg == NULL && conn->eof) { conn->done = 1; log_debug(LOG_INFO, "c %d is done", conn->sd); } status = event_del_out(ctx->evb, conn); if (status != NC_OK) { conn->err = errno; } return NULL; } msg = conn->smsg; if (msg != NULL) { ASSERT(!msg->request && msg->peer != NULL); ASSERT(req_done(conn, msg->peer)); pmsg = TAILQ_NEXT(msg->peer, c_tqe); } if (pmsg == NULL || !req_done(conn, pmsg)) { conn->smsg = NULL; return NULL; } ASSERT(pmsg->request && !pmsg->swallow); if (req_error(conn, pmsg)) { msg = rsp_make_error(ctx, conn, pmsg); if (msg == NULL) { conn->err = errno; return NULL; } msg->peer = pmsg; pmsg->peer = msg; stats_pool_incr(ctx, conn->owner, forward_error); } else { msg = pmsg->peer; } ASSERT(!msg->request); conn->smsg = msg; log_debug(LOG_VVERB, "send next rsp %"PRIu64" on c %d", msg->id, conn->sd); return msg; } void rsp_send_done(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg *pmsg; /* peer message (request) */ ASSERT(conn->client && !conn->proxy); ASSERT(conn->smsg == NULL); log_debug(LOG_VVERB, "send done rsp %"PRIu64" on c %d", msg->id, conn->sd); pmsg = msg->peer; ASSERT(!msg->request && pmsg->request); ASSERT(pmsg->peer == msg); ASSERT(pmsg->done && !pmsg->swallow); /* dequeue request from client outq */ conn->dequeue_outq(ctx, conn, pmsg); req_put(pmsg); } nutcracker-0.4.1+dfsg/src/nc_signal.c0000664000000000000000000000601312542173515016171 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include static struct signal signals[] = { { SIGUSR1, "SIGUSR1", 0, signal_handler }, { SIGUSR2, "SIGUSR2", 0, signal_handler }, { SIGTTIN, "SIGTTIN", 0, signal_handler }, { SIGTTOU, "SIGTTOU", 0, signal_handler }, { SIGHUP, "SIGHUP", 0, signal_handler }, { SIGINT, "SIGINT", 0, signal_handler }, { SIGSEGV, "SIGSEGV", (int)SA_RESETHAND, signal_handler }, { SIGPIPE, "SIGPIPE", 0, SIG_IGN }, { 0, NULL, 0, NULL } }; rstatus_t signal_init(void) { struct signal *sig; for (sig = signals; sig->signo != 0; sig++) { rstatus_t status; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig->handler; sa.sa_flags = sig->flags; sigemptyset(&sa.sa_mask); status = sigaction(sig->signo, &sa, NULL); if (status < 0) { log_error("sigaction(%s) failed: %s", sig->signame, strerror(errno)); return NC_ERROR; } } return NC_OK; } void signal_deinit(void) { } void signal_handler(int signo) { struct signal *sig; void (*action)(void); char *actionstr; bool done; for (sig = signals; sig->signo != 0; sig++) { if (sig->signo == signo) { break; } } ASSERT(sig->signo != 0); actionstr = ""; action = NULL; done = false; switch (signo) { case SIGUSR1: break; case SIGUSR2: break; case SIGTTIN: actionstr = ", up logging level"; action = log_level_up; break; case SIGTTOU: actionstr = ", down logging level"; action = log_level_down; break; case SIGHUP: actionstr = ", reopening log file"; action = log_reopen; break; case SIGINT: done = true; actionstr = ", exiting"; break; case SIGSEGV: log_stacktrace(); actionstr = ", core dumping"; raise(SIGSEGV); break; default: NOT_REACHED(); } log_safe("signal %d (%s) received%s", signo, sig->signame, actionstr); if (action != NULL) { action(); } if (done) { exit(1); } } nutcracker-0.4.1+dfsg/src/nc_rbtree.h0000664000000000000000000000324212542173515016205 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_RBTREE_ #define _NC_RBTREE_ #define rbtree_red(_node) ((_node)->color = 1) #define rbtree_black(_node) ((_node)->color = 0) #define rbtree_is_red(_node) ((_node)->color) #define rbtree_is_black(_node) (!rbtree_is_red(_node)) #define rbtree_copy_color(_n1, _n2) ((_n1)->color = (_n2)->color) struct rbnode { struct rbnode *left; /* left link */ struct rbnode *right; /* right link */ struct rbnode *parent; /* parent link */ int64_t key; /* key for ordering */ void *data; /* opaque data */ uint8_t color; /* red | black */ }; struct rbtree { struct rbnode *root; /* root node */ struct rbnode *sentinel; /* nil node */ }; void rbtree_node_init(struct rbnode *node); void rbtree_init(struct rbtree *tree, struct rbnode *node); struct rbnode *rbtree_min(struct rbtree *tree); void rbtree_insert(struct rbtree *tree, struct rbnode *node); void rbtree_delete(struct rbtree *tree, struct rbnode *node); #endif nutcracker-0.4.1+dfsg/src/nc_rbtree.c0000664000000000000000000002057212542173515016205 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include void rbtree_node_init(struct rbnode *node) { node->left = NULL; node->right = NULL; node->parent = NULL; node->key = 0ULL; node->data = NULL; /* color is left uninitialized */ } void rbtree_init(struct rbtree *tree, struct rbnode *node) { rbtree_node_init(node); rbtree_black(node); tree->root = node; tree->sentinel = node; } static struct rbnode * rbtree_node_min(struct rbnode *node, struct rbnode *sentinel) { /* traverse left links */ while (node->left != sentinel) { node = node->left; } return node; } struct rbnode * rbtree_min(struct rbtree *tree) { struct rbnode *node = tree->root; struct rbnode *sentinel = tree->sentinel; /* empty tree */ if (node == sentinel) { return NULL; } return rbtree_node_min(node, sentinel); } static void rbtree_left_rotate(struct rbnode **root, struct rbnode *sentinel, struct rbnode *node) { struct rbnode *temp; temp = node->right; node->right = temp->left; if (temp->left != sentinel) { temp->left->parent = node; } temp->parent = node->parent; if (node == *root) { *root = temp; } else if (node == node->parent->left) { node->parent->left = temp; } else { node->parent->right = temp; } temp->left = node; node->parent = temp; } static void rbtree_right_rotate(struct rbnode **root, struct rbnode *sentinel, struct rbnode *node) { struct rbnode *temp; temp = node->left; node->left = temp->right; if (temp->right != sentinel) { temp->right->parent = node; } temp->parent = node->parent; if (node == *root) { *root = temp; } else if (node == node->parent->right) { node->parent->right = temp; } else { node->parent->left = temp; } temp->right = node; node->parent = temp; } void rbtree_insert(struct rbtree *tree, struct rbnode *node) { struct rbnode **root = &tree->root; struct rbnode *sentinel = tree->sentinel; struct rbnode *temp, **p; /* empty tree */ if (*root == sentinel) { node->parent = NULL; node->left = sentinel; node->right = sentinel; rbtree_black(node); *root = node; return; } /* a binary tree insert */ temp = *root; for (;;) { p = (node->key < temp->key) ? &temp->left : &temp->right; if (*p == sentinel) { break; } temp = *p; } *p = node; node->parent = temp; node->left = sentinel; node->right = sentinel; rbtree_red(node); /* re-balance tree */ while (node != *root && rbtree_is_red(node->parent)) { if (node->parent == node->parent->parent->left) { temp = node->parent->parent->right; if (rbtree_is_red(temp)) { rbtree_black(node->parent); rbtree_black(temp); rbtree_red(node->parent->parent); node = node->parent->parent; } else { if (node == node->parent->right) { node = node->parent; rbtree_left_rotate(root, sentinel, node); } rbtree_black(node->parent); rbtree_red(node->parent->parent); rbtree_right_rotate(root, sentinel, node->parent->parent); } } else { temp = node->parent->parent->left; if (rbtree_is_red(temp)) { rbtree_black(node->parent); rbtree_black(temp); rbtree_red(node->parent->parent); node = node->parent->parent; } else { if (node == node->parent->left) { node = node->parent; rbtree_right_rotate(root, sentinel, node); } rbtree_black(node->parent); rbtree_red(node->parent->parent); rbtree_left_rotate(root, sentinel, node->parent->parent); } } } rbtree_black(*root); } void rbtree_delete(struct rbtree *tree, struct rbnode *node) { struct rbnode **root = &tree->root; struct rbnode *sentinel = tree->sentinel; struct rbnode *subst, *temp, *w; uint8_t red; /* a binary tree delete */ if (node->left == sentinel) { temp = node->right; subst = node; } else if (node->right == sentinel) { temp = node->left; subst = node; } else { subst = rbtree_node_min(node->right, sentinel); temp = subst->right; } if (subst == *root) { *root = temp; rbtree_black(temp); rbtree_node_init(node); return; } red = rbtree_is_red(subst); if (subst == subst->parent->left) { subst->parent->left = temp; } else { subst->parent->right = temp; } if (subst == node) { temp->parent = subst->parent; } else { if (subst->parent == node) { temp->parent = subst; } else { temp->parent = subst->parent; } subst->left = node->left; subst->right = node->right; subst->parent = node->parent; rbtree_copy_color(subst, node); if (node == *root) { *root = subst; } else { if (node == node->parent->left) { node->parent->left = subst; } else { node->parent->right = subst; } } if (subst->left != sentinel) { subst->left->parent = subst; } if (subst->right != sentinel) { subst->right->parent = subst; } } rbtree_node_init(node); if (red) { return; } /* a delete fixup */ while (temp != *root && rbtree_is_black(temp)) { if (temp == temp->parent->left) { w = temp->parent->right; if (rbtree_is_red(w)) { rbtree_black(w); rbtree_red(temp->parent); rbtree_left_rotate(root, sentinel, temp->parent); w = temp->parent->right; } if (rbtree_is_black(w->left) && rbtree_is_black(w->right)) { rbtree_red(w); temp = temp->parent; } else { if (rbtree_is_black(w->right)) { rbtree_black(w->left); rbtree_red(w); rbtree_right_rotate(root, sentinel, w); w = temp->parent->right; } rbtree_copy_color(w, temp->parent); rbtree_black(temp->parent); rbtree_black(w->right); rbtree_left_rotate(root, sentinel, temp->parent); temp = *root; } } else { w = temp->parent->left; if (rbtree_is_red(w)) { rbtree_black(w); rbtree_red(temp->parent); rbtree_right_rotate(root, sentinel, temp->parent); w = temp->parent->left; } if (rbtree_is_black(w->left) && rbtree_is_black(w->right)) { rbtree_red(w); temp = temp->parent; } else { if (rbtree_is_black(w->left)) { rbtree_black(w->right); rbtree_red(w); rbtree_left_rotate(root, sentinel, w); w = temp->parent->left; } rbtree_copy_color(w, temp->parent); rbtree_black(temp->parent); rbtree_black(w->left); rbtree_right_rotate(root, sentinel, temp->parent); temp = *root; } } } rbtree_black(temp); } nutcracker-0.4.1+dfsg/src/nc_proxy.h0000664000000000000000000000215512542173515016105 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_PROXY_H_ #define _NC_PROXY_H_ #include void proxy_ref(struct conn *conn, void *owner); void proxy_unref(struct conn *conn); void proxy_close(struct context *ctx, struct conn *conn); rstatus_t proxy_each_init(void *elem, void *data); rstatus_t proxy_each_deinit(void *elem, void *data); rstatus_t proxy_init(struct context *ctx); void proxy_deinit(struct context *ctx); rstatus_t proxy_recv(struct context *ctx, struct conn *conn); #endif nutcracker-0.4.1+dfsg/src/nc_request.c0000664000000000000000000004633312542173515016415 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include struct msg * req_get(struct conn *conn) { struct msg *msg; ASSERT(conn->client && !conn->proxy); msg = msg_get(conn, true, conn->redis); if (msg == NULL) { conn->err = errno; } return msg; } static void req_log(struct msg *req) { struct msg *rsp; /* peer message (response) */ int64_t req_time; /* time cost for this request */ char *peer_str; /* peer client ip:port */ uint32_t req_len, rsp_len; /* request and response length */ struct string *req_type; /* request type string */ struct keypos *kpos; if (log_loggable(LOG_NOTICE) == 0) { return; } /* a fragment? */ if (req->frag_id != 0 && req->frag_owner != req) { return; } /* conn close normally? */ if (req->mlen == 0) { return; } /* * there is a race scenario where a requests comes in, the log level is not LOG_NOTICE, * and before the response arrives you modify the log level to LOG_NOTICE * using SIGTTIN OR SIGTTOU, then req_log() wouldn't have msg->start_ts set */ if (req->start_ts == 0) { return; } req_time = nc_usec_now() - req->start_ts; rsp = req->peer; req_len = req->mlen; rsp_len = (rsp != NULL) ? rsp->mlen : 0; if (array_n(req->keys) < 1) { return; } kpos = array_get(req->keys, 0); if (kpos->end != NULL) { *(kpos->end) = '\0'; } /* * FIXME: add backend addr here * Maybe we can store addrstr just like server_pool in conn struct * when connections are resolved */ peer_str = nc_unresolve_peer_desc(req->owner->sd); req_type = msg_type_string(req->type); log_debug(LOG_NOTICE, "req %"PRIu64" done on c %d req_time %"PRIi64".%03"PRIi64 " msec type %.*s narg %"PRIu32" req_len %"PRIu32" rsp_len %"PRIu32 " key0 '%s' peer '%s' done %d error %d", req->id, req->owner->sd, req_time / 1000, req_time % 1000, req_type->len, req_type->data, req->narg, req_len, rsp_len, kpos->start, peer_str, req->done, req->error); } void req_put(struct msg *msg) { struct msg *pmsg; /* peer message (response) */ ASSERT(msg->request); req_log(msg); pmsg = msg->peer; if (pmsg != NULL) { ASSERT(!pmsg->request && pmsg->peer == msg); msg->peer = NULL; pmsg->peer = NULL; rsp_put(pmsg); } msg_tmo_delete(msg); msg_put(msg); } /* * Return true if request is done, false otherwise * * A request is done, if we received response for the given request. * A request vector is done if we received responses for all its * fragments. */ bool req_done(struct conn *conn, struct msg *msg) { struct msg *cmsg, *pmsg; /* current and previous message */ uint64_t id; /* fragment id */ uint32_t nfragment; /* # fragment */ ASSERT(conn->client && !conn->proxy); ASSERT(msg->request); if (!msg->done) { return false; } id = msg->frag_id; if (id == 0) { return true; } if (msg->fdone) { /* request has already been marked as done */ return true; } if (msg->nfrag_done < msg->nfrag) { return false; } /* check all fragments of the given request vector are done */ for (pmsg = msg, cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe); cmsg != NULL && cmsg->frag_id == id; pmsg = cmsg, cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) { if (!cmsg->done) { return false; } } for (pmsg = msg, cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; pmsg = cmsg, cmsg = TAILQ_NEXT(cmsg, c_tqe)) { if (!cmsg->done) { return false; } } /* * At this point, all the fragments including the last fragment have * been received. * * Mark all fragments of the given request vector to be done to speed up * future req_done calls for any of fragments of this request */ msg->fdone = 1; nfragment = 0; for (pmsg = msg, cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe); cmsg != NULL && cmsg->frag_id == id; pmsg = cmsg, cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) { cmsg->fdone = 1; nfragment++; } for (pmsg = msg, cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; pmsg = cmsg, cmsg = TAILQ_NEXT(cmsg, c_tqe)) { cmsg->fdone = 1; nfragment++; } ASSERT(msg->frag_owner->nfrag == nfragment); msg->post_coalesce(msg->frag_owner); log_debug(LOG_DEBUG, "req from c %d with fid %"PRIu64" and %"PRIu32" " "fragments is done", conn->sd, id, nfragment); return true; } /* * Return true if request is in error, false otherwise * * A request is in error, if there was an error in receiving response for the * given request. A multiget request is in error if there was an error in * receiving response for any its fragments. */ bool req_error(struct conn *conn, struct msg *msg) { struct msg *cmsg; /* current message */ uint64_t id; uint32_t nfragment; ASSERT(msg->request && req_done(conn, msg)); if (msg->error) { return true; } id = msg->frag_id; if (id == 0) { return false; } if (msg->ferror) { /* request has already been marked to be in error */ return true; } /* check if any of the fragments of the given request are in error */ for (cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) { if (cmsg->error) { goto ferror; } } for (cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = TAILQ_NEXT(cmsg, c_tqe)) { if (cmsg->error) { goto ferror; } } return false; ferror: /* * Mark all fragments of the given request to be in error to speed up * future req_error calls for any of fragments of this request */ msg->ferror = 1; nfragment = 1; for (cmsg = TAILQ_PREV(msg, msg_tqh, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = TAILQ_PREV(cmsg, msg_tqh, c_tqe)) { cmsg->ferror = 1; nfragment++; } for (cmsg = TAILQ_NEXT(msg, c_tqe); cmsg != NULL && cmsg->frag_id == id; cmsg = TAILQ_NEXT(cmsg, c_tqe)) { cmsg->ferror = 1; nfragment++; } log_debug(LOG_DEBUG, "req from c %d with fid %"PRIu64" and %"PRIu32" " "fragments is in error", conn->sd, id, nfragment); return true; } void req_server_enqueue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); /* * timeout clock starts ticking the instant the message is enqueued into * the server in_q; the clock continues to tick until it either expires * or the message is dequeued from the server out_q * * noreply request are free from timeouts because client is not intrested * in the response anyway! */ if (!msg->noreply) { msg_tmo_insert(msg, conn); } TAILQ_INSERT_TAIL(&conn->imsg_q, msg, s_tqe); stats_server_incr(ctx, conn->owner, in_queue); stats_server_incr_by(ctx, conn->owner, in_queue_bytes, msg->mlen); } void req_server_enqueue_imsgq_head(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); /* * timeout clock starts ticking the instant the message is enqueued into * the server in_q; the clock continues to tick until it either expires * or the message is dequeued from the server out_q * * noreply request are free from timeouts because client is not intrested * in the reponse anyway! */ if (!msg->noreply) { msg_tmo_insert(msg, conn); } TAILQ_INSERT_HEAD(&conn->imsg_q, msg, s_tqe); stats_server_incr(ctx, conn->owner, in_queue); stats_server_incr_by(ctx, conn->owner, in_queue_bytes, msg->mlen); } void req_server_dequeue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); TAILQ_REMOVE(&conn->imsg_q, msg, s_tqe); stats_server_decr(ctx, conn->owner, in_queue); stats_server_decr_by(ctx, conn->owner, in_queue_bytes, msg->mlen); } void req_client_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(conn->client && !conn->proxy); TAILQ_INSERT_TAIL(&conn->omsg_q, msg, c_tqe); } void req_server_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); TAILQ_INSERT_TAIL(&conn->omsg_q, msg, s_tqe); stats_server_incr(ctx, conn->owner, out_queue); stats_server_incr_by(ctx, conn->owner, out_queue_bytes, msg->mlen); } void req_client_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(conn->client && !conn->proxy); TAILQ_REMOVE(&conn->omsg_q, msg, c_tqe); } void req_server_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(msg->request); ASSERT(!conn->client && !conn->proxy); msg_tmo_delete(msg); TAILQ_REMOVE(&conn->omsg_q, msg, s_tqe); stats_server_decr(ctx, conn->owner, out_queue); stats_server_decr_by(ctx, conn->owner, out_queue_bytes, msg->mlen); } struct msg * req_recv_next(struct context *ctx, struct conn *conn, bool alloc) { struct msg *msg; ASSERT(conn->client && !conn->proxy); if (conn->eof) { msg = conn->rmsg; /* client sent eof before sending the entire request */ if (msg != NULL) { conn->rmsg = NULL; ASSERT(msg->peer == NULL); ASSERT(msg->request && !msg->done); log_error("eof c %d discarding incomplete req %"PRIu64" len " "%"PRIu32"", conn->sd, msg->id, msg->mlen); req_put(msg); } /* * TCP half-close enables the client to terminate its half of the * connection (i.e. the client no longer sends data), but it still * is able to receive data from the proxy. The proxy closes its * half (by sending the second FIN) when the client has no * outstanding requests */ if (!conn->active(conn)) { conn->done = 1; log_debug(LOG_INFO, "c %d is done", conn->sd); } return NULL; } msg = conn->rmsg; if (msg != NULL) { ASSERT(msg->request); return msg; } if (!alloc) { return NULL; } msg = req_get(conn); if (msg != NULL) { conn->rmsg = msg; } return msg; } static rstatus_t req_make_reply(struct context *ctx, struct conn *conn, struct msg *req) { struct msg *rsp; rsp = msg_get(conn, false, conn->redis); /* replay */ if (rsp == NULL) { conn->err = errno; return NC_ENOMEM; } req->peer = rsp; rsp->peer = req; rsp->request = 0; req->done = 1; conn->enqueue_outq(ctx, conn, req); return NC_OK; } static bool req_filter(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(conn->client && !conn->proxy); if (msg_empty(msg)) { ASSERT(conn->rmsg == NULL); log_debug(LOG_VERB, "filter empty req %"PRIu64" from c %d", msg->id, conn->sd); req_put(msg); return true; } /* * Handle "quit\r\n" (memcache) or "*1\r\n$4\r\nquit\r\n" (redis), which * is the protocol way of doing a passive close. The connection is closed * as soon as all pending replies have been written to the client. */ if (msg->quit) { log_debug(LOG_INFO, "filter quit req %"PRIu64" from c %d", msg->id, conn->sd); if (conn->rmsg != NULL) { log_debug(LOG_INFO, "discard invalid req %"PRIu64" len %"PRIu32" " "from c %d sent after quit req", conn->rmsg->id, conn->rmsg->mlen, conn->sd); } conn->eof = 1; conn->recv_ready = 0; req_put(msg); return true; } /* * If this conn is not authenticated, we will mark it as noforward, * and handle it in the redis_reply handler. */ if (!conn_authenticated(conn)) { msg->noforward = 1; } return false; } static void req_forward_error(struct context *ctx, struct conn *conn, struct msg *msg) { rstatus_t status; ASSERT(conn->client && !conn->proxy); log_debug(LOG_INFO, "forward req %"PRIu64" len %"PRIu32" type %d from " "c %d failed: %s", msg->id, msg->mlen, msg->type, conn->sd, strerror(errno)); msg->done = 1; msg->error = 1; msg->err = errno; /* noreply request don't expect any response */ if (msg->noreply) { req_put(msg); return; } if (req_done(conn, TAILQ_FIRST(&conn->omsg_q))) { status = event_add_out(ctx->evb, conn); if (status != NC_OK) { conn->err = errno; } } } static void req_forward_stats(struct context *ctx, struct server *server, struct msg *msg) { ASSERT(msg->request); stats_server_incr(ctx, server, requests); stats_server_incr_by(ctx, server, request_bytes, msg->mlen); } static void req_forward(struct context *ctx, struct conn *c_conn, struct msg *msg) { rstatus_t status; struct conn *s_conn; struct server_pool *pool; uint8_t *key; uint32_t keylen; struct keypos *kpos; ASSERT(c_conn->client && !c_conn->proxy); /* enqueue message (request) into client outq, if response is expected */ if (!msg->noreply) { c_conn->enqueue_outq(ctx, c_conn, msg); } pool = c_conn->owner; ASSERT(array_n(msg->keys) > 0); kpos = array_get(msg->keys, 0); key = kpos->start; keylen = (uint32_t)(kpos->end - kpos->start); s_conn = server_pool_conn(ctx, c_conn->owner, key, keylen); if (s_conn == NULL) { req_forward_error(ctx, c_conn, msg); return; } ASSERT(!s_conn->client && !s_conn->proxy); /* enqueue the message (request) into server inq */ if (TAILQ_EMPTY(&s_conn->imsg_q)) { status = event_add_out(ctx->evb, s_conn); if (status != NC_OK) { req_forward_error(ctx, c_conn, msg); s_conn->err = errno; return; } } if (!conn_authenticated(s_conn)) { status = msg->add_auth(ctx, c_conn, s_conn); if (status != NC_OK) { req_forward_error(ctx, c_conn, msg); s_conn->err = errno; return; } } s_conn->enqueue_inq(ctx, s_conn, msg); req_forward_stats(ctx, s_conn->owner, msg); log_debug(LOG_VERB, "forward from c %d to s %d req %"PRIu64" len %"PRIu32 " type %d with key '%.*s'", c_conn->sd, s_conn->sd, msg->id, msg->mlen, msg->type, keylen, key); } void req_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg) { rstatus_t status; struct server_pool *pool; struct msg_tqh frag_msgq; struct msg *sub_msg; struct msg *tmsg; /* tmp next message */ ASSERT(conn->client && !conn->proxy); ASSERT(msg->request); ASSERT(msg->owner == conn); ASSERT(conn->rmsg == msg); ASSERT(nmsg == NULL || nmsg->request); /* enqueue next message (request), if any */ conn->rmsg = nmsg; if (req_filter(ctx, conn, msg)) { return; } if (msg->noforward) { status = req_make_reply(ctx, conn, msg); if (status != NC_OK) { conn->err = errno; return; } status = msg->reply(msg); if (status != NC_OK) { conn->err = errno; return; } status = event_add_out(ctx->evb, conn); if (status != NC_OK) { conn->err = errno; } return; } /* do fragment */ pool = conn->owner; TAILQ_INIT(&frag_msgq); status = msg->fragment(msg, pool->ncontinuum, &frag_msgq); if (status != NC_OK) { if (!msg->noreply) { conn->enqueue_outq(ctx, conn, msg); } req_forward_error(ctx, conn, msg); } /* if no fragment happened */ if (TAILQ_EMPTY(&frag_msgq)) { req_forward(ctx, conn, msg); return; } status = req_make_reply(ctx, conn, msg); if (status != NC_OK) { if (!msg->noreply) { conn->enqueue_outq(ctx, conn, msg); } req_forward_error(ctx, conn, msg); } for (sub_msg = TAILQ_FIRST(&frag_msgq); sub_msg != NULL; sub_msg = tmsg) { tmsg = TAILQ_NEXT(sub_msg, m_tqe); TAILQ_REMOVE(&frag_msgq, sub_msg, m_tqe); req_forward(ctx, conn, sub_msg); } ASSERT(TAILQ_EMPTY(&frag_msgq)); return; } struct msg * req_send_next(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg, *nmsg; /* current and next message */ ASSERT(!conn->client && !conn->proxy); if (conn->connecting) { server_connected(ctx, conn); } nmsg = TAILQ_FIRST(&conn->imsg_q); if (nmsg == NULL) { /* nothing to send as the server inq is empty */ status = event_del_out(ctx->evb, conn); if (status != NC_OK) { conn->err = errno; } return NULL; } msg = conn->smsg; if (msg != NULL) { ASSERT(msg->request && !msg->done); nmsg = TAILQ_NEXT(msg, s_tqe); } conn->smsg = nmsg; if (nmsg == NULL) { return NULL; } ASSERT(nmsg->request && !nmsg->done); log_debug(LOG_VVERB, "send next req %"PRIu64" len %"PRIu32" type %d on " "s %d", nmsg->id, nmsg->mlen, nmsg->type, conn->sd); return nmsg; } void req_send_done(struct context *ctx, struct conn *conn, struct msg *msg) { ASSERT(!conn->client && !conn->proxy); ASSERT(msg != NULL && conn->smsg == NULL); ASSERT(msg->request && !msg->done); ASSERT(msg->owner != conn); log_debug(LOG_VVERB, "send done req %"PRIu64" len %"PRIu32" type %d on " "s %d", msg->id, msg->mlen, msg->type, conn->sd); /* dequeue the message (request) from server inq */ conn->dequeue_inq(ctx, conn, msg); /* * noreply request instructs the server not to send any response. So, * enqueue message (request) in server outq, if response is expected. * Otherwise, free the noreply request */ if (!msg->noreply) { conn->enqueue_outq(ctx, conn, msg); } else { req_put(msg); } } nutcracker-0.4.1+dfsg/src/nc_util.c0000664000000000000000000003122412542173515015673 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef NC_HAVE_BACKTRACE # include #endif int nc_set_blocking(int sd) { int flags; flags = fcntl(sd, F_GETFL, 0); if (flags < 0) { return flags; } return fcntl(sd, F_SETFL, flags & ~O_NONBLOCK); } int nc_set_nonblocking(int sd) { int flags; flags = fcntl(sd, F_GETFL, 0); if (flags < 0) { return flags; } return fcntl(sd, F_SETFL, flags | O_NONBLOCK); } int nc_set_reuseaddr(int sd) { int reuse; socklen_t len; reuse = 1; len = sizeof(reuse); return setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, len); } /* * Disable Nagle algorithm on TCP socket. * * This option helps to minimize transmit latency by disabling coalescing * of data to fill up a TCP segment inside the kernel. Sockets with this * option must use readv() or writev() to do data transfer in bulk and * hence avoid the overhead of small packets. */ int nc_set_tcpnodelay(int sd) { int nodelay; socklen_t len; nodelay = 1; len = sizeof(nodelay); return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); } int nc_set_linger(int sd, int timeout) { struct linger linger; socklen_t len; linger.l_onoff = 1; linger.l_linger = timeout; len = sizeof(linger); return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len); } int nc_set_sndbuf(int sd, int size) { socklen_t len; len = sizeof(size); return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len); } int nc_set_rcvbuf(int sd, int size) { socklen_t len; len = sizeof(size); return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len); } int nc_get_soerror(int sd) { int status, err; socklen_t len; err = 0; len = sizeof(err); status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len); if (status == 0) { errno = err; } return status; } int nc_get_sndbuf(int sd) { int status, size; socklen_t len; size = 0; len = sizeof(size); status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len); if (status < 0) { return status; } return size; } int nc_get_rcvbuf(int sd) { int status, size; socklen_t len; size = 0; len = sizeof(size); status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len); if (status < 0) { return status; } return size; } int _nc_atoi(uint8_t *line, size_t n) { int value; if (n == 0) { return -1; } for (value = 0; n--; line++) { if (*line < '0' || *line > '9') { return -1; } value = value * 10 + (*line - '0'); } if (value < 0) { return -1; } return value; } bool nc_valid_port(int n) { if (n < 1 || n > UINT16_MAX) { return false; } return true; } void * _nc_alloc(size_t size, const char *name, int line) { void *p; ASSERT(size != 0); p = malloc(size); if (p == NULL) { log_error("malloc(%zu) failed @ %s:%d", size, name, line); } else { log_debug(LOG_VVERB, "malloc(%zu) at %p @ %s:%d", size, p, name, line); } return p; } void * _nc_zalloc(size_t size, const char *name, int line) { void *p; p = _nc_alloc(size, name, line); if (p != NULL) { memset(p, 0, size); } return p; } void * _nc_calloc(size_t nmemb, size_t size, const char *name, int line) { return _nc_zalloc(nmemb * size, name, line); } void * _nc_realloc(void *ptr, size_t size, const char *name, int line) { void *p; ASSERT(size != 0); p = realloc(ptr, size); if (p == NULL) { log_error("realloc(%zu) failed @ %s:%d", size, name, line); } else { log_debug(LOG_VVERB, "realloc(%zu) at %p @ %s:%d", size, p, name, line); } return p; } void _nc_free(void *ptr, const char *name, int line) { ASSERT(ptr != NULL); log_debug(LOG_VVERB, "free(%p) @ %s:%d", ptr, name, line); free(ptr); } void nc_stacktrace(int skip_count) { #ifdef NC_HAVE_BACKTRACE void *stack[64]; char **symbols; int size, i, j; size = backtrace(stack, 64); symbols = backtrace_symbols(stack, size); if (symbols == NULL) { return; } skip_count++; /* skip the current frame also */ for (i = skip_count, j = 0; i < size; i++, j++) { loga("[%d] %s", j, symbols[i]); } free(symbols); #endif } void nc_stacktrace_fd(int fd) { #ifdef NC_HAVE_BACKTRACE void *stack[64]; int size; size = backtrace(stack, 64); backtrace_symbols_fd(stack, size, fd); #endif } void nc_assert(const char *cond, const char *file, int line, int panic) { log_error("assert '%s' failed @ (%s, %d)", cond, file, line); if (panic) { nc_stacktrace(1); abort(); } } int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args) { int n; n = vsnprintf(buf, size, fmt, args); /* * The return value is the number of characters which would be written * into buf not including the trailing '\0'. If size is == 0 the * function returns 0. * * On error, the function also returns 0. This is to allow idiom such * as len += _vscnprintf(...) * * See: http://lwn.net/Articles/69419/ */ if (n <= 0) { return 0; } if (n < (int) size) { return n; } return (int)(size - 1); } int _scnprintf(char *buf, size_t size, const char *fmt, ...) { va_list args; int n; va_start(args, fmt); n = _vscnprintf(buf, size, fmt, args); va_end(args); return n; } /* * Send n bytes on a blocking descriptor */ ssize_t _nc_sendn(int sd, const void *vptr, size_t n) { size_t nleft; ssize_t nsend; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { nsend = send(sd, ptr, nleft, 0); if (nsend < 0) { if (errno == EINTR) { continue; } return nsend; } if (nsend == 0) { return -1; } nleft -= (size_t)nsend; ptr += nsend; } return (ssize_t)n; } /* * Recv n bytes from a blocking descriptor */ ssize_t _nc_recvn(int sd, void *vptr, size_t n) { size_t nleft; ssize_t nrecv; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { nrecv = recv(sd, ptr, nleft, 0); if (nrecv < 0) { if (errno == EINTR) { continue; } return nrecv; } if (nrecv == 0) { break; } nleft -= (size_t)nrecv; ptr += nrecv; } return (ssize_t)(n - nleft); } /* * Return the current time in microseconds since Epoch */ int64_t nc_usec_now(void) { struct timeval now; int64_t usec; int status; status = gettimeofday(&now, NULL); if (status < 0) { log_error("gettimeofday failed: %s", strerror(errno)); return -1; } usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec; return usec; } /* * Return the current time in milliseconds since Epoch */ int64_t nc_msec_now(void) { return nc_usec_now() / 1000LL; } static int nc_resolve_inet(struct string *name, int port, struct sockinfo *si) { int status; struct addrinfo *ai, *cai; /* head and current addrinfo */ struct addrinfo hints; char *node, service[NC_UINTMAX_MAXLEN]; bool found; ASSERT(nc_valid_port(port)); memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_NUMERICSERV; hints.ai_family = AF_UNSPEC; /* AF_INET or AF_INET6 */ hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_addrlen = 0; hints.ai_addr = NULL; hints.ai_canonname = NULL; if (name != NULL) { node = (char *)name->data; } else { /* * If AI_PASSIVE flag is specified in hints.ai_flags, and node is * NULL, then the returned socket addresses will be suitable for * bind(2)ing a socket that will accept(2) connections. The returned * socket address will contain the wildcard IP address. */ node = NULL; hints.ai_flags |= AI_PASSIVE; } nc_snprintf(service, NC_UINTMAX_MAXLEN, "%d", port); /* * getaddrinfo() returns zero on success or one of the error codes listed * in gai_strerror(3) if an error occurs */ status = getaddrinfo(node, service, &hints, &ai); if (status != 0) { log_error("address resolution of node '%s' service '%s' failed: %s", node, service, gai_strerror(status)); return -1; } /* * getaddrinfo() can return a linked list of more than one addrinfo, * since we requested for both AF_INET and AF_INET6 addresses and the * host itself can be multi-homed. Since we don't care whether we are * using ipv4 or ipv6, we just use the first address from this collection * in the order in which it was returned. * * The sorting function used within getaddrinfo() is defined in RFC 3484; * the order can be tweaked for a particular system by editing * /etc/gai.conf */ for (cai = ai, found = false; cai != NULL; cai = cai->ai_next) { si->family = cai->ai_family; si->addrlen = cai->ai_addrlen; nc_memcpy(&si->addr, cai->ai_addr, si->addrlen); found = true; break; } freeaddrinfo(ai); return !found ? -1 : 0; } static int nc_resolve_unix(struct string *name, struct sockinfo *si) { struct sockaddr_un *un; if (name->len >= NC_UNIX_ADDRSTRLEN) { return -1; } un = &si->addr.un; un->sun_family = AF_UNIX; nc_memcpy(un->sun_path, name->data, name->len); un->sun_path[name->len] = '\0'; si->family = AF_UNIX; si->addrlen = sizeof(*un); /* si->addr is an alias of un */ return 0; } /* * Resolve a hostname and service by translating it to socket address and * return it in si * * This routine is reentrant */ int nc_resolve(struct string *name, int port, struct sockinfo *si) { if (name != NULL && name->data[0] == '/') { return nc_resolve_unix(name, si); } return nc_resolve_inet(name, port, si); } /* * Unresolve the socket address by translating it to a character string * describing the host and service * * This routine is not reentrant */ char * nc_unresolve_addr(struct sockaddr *addr, socklen_t addrlen) { static char unresolve[NI_MAXHOST + NI_MAXSERV]; static char host[NI_MAXHOST], service[NI_MAXSERV]; int status; status = getnameinfo(addr, addrlen, host, sizeof(host), service, sizeof(service), NI_NUMERICHOST | NI_NUMERICSERV); if (status < 0) { return "unknown"; } nc_snprintf(unresolve, sizeof(unresolve), "%s:%s", host, service); return unresolve; } /* * Unresolve the socket descriptor peer address by translating it to a * character string describing the host and service * * This routine is not reentrant */ char * nc_unresolve_peer_desc(int sd) { static struct sockinfo si; struct sockaddr *addr; socklen_t addrlen; int status; memset(&si, 0, sizeof(si)); addr = (struct sockaddr *)&si.addr; addrlen = sizeof(si.addr); status = getpeername(sd, addr, &addrlen); if (status < 0) { return "unknown"; } return nc_unresolve_addr(addr, addrlen); } /* * Unresolve the socket descriptor address by translating it to a * character string describing the host and service * * This routine is not reentrant */ char * nc_unresolve_desc(int sd) { static struct sockinfo si; struct sockaddr *addr; socklen_t addrlen; int status; memset(&si, 0, sizeof(si)); addr = (struct sockaddr *)&si.addr; addrlen = sizeof(si.addr); status = getsockname(sd, addr, &addrlen); if (status < 0) { return "unknown"; } return nc_unresolve_addr(addr, addrlen); } nutcracker-0.4.1+dfsg/src/nc_client.c0000664000000000000000000001136612542173515016201 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include void client_ref(struct conn *conn, void *owner) { struct server_pool *pool = owner; ASSERT(conn->client && !conn->proxy); ASSERT(conn->owner == NULL); /* * We use null pointer as the sockaddr argument in the accept() call as * we are not interested in the address of the peer for the accepted * connection */ conn->family = 0; conn->addrlen = 0; conn->addr = NULL; pool->nc_conn_q++; TAILQ_INSERT_TAIL(&pool->c_conn_q, conn, conn_tqe); /* owner of the client connection is the server pool */ conn->owner = owner; log_debug(LOG_VVERB, "ref conn %p owner %p into pool '%.*s'", conn, pool, pool->name.len, pool->name.data); } void client_unref(struct conn *conn) { struct server_pool *pool; ASSERT(conn->client && !conn->proxy); ASSERT(conn->owner != NULL); pool = conn->owner; conn->owner = NULL; ASSERT(pool->nc_conn_q != 0); pool->nc_conn_q--; TAILQ_REMOVE(&pool->c_conn_q, conn, conn_tqe); log_debug(LOG_VVERB, "unref conn %p owner %p from pool '%.*s'", conn, pool, pool->name.len, pool->name.data); } bool client_active(struct conn *conn) { ASSERT(conn->client && !conn->proxy); ASSERT(TAILQ_EMPTY(&conn->imsg_q)); if (!TAILQ_EMPTY(&conn->omsg_q)) { log_debug(LOG_VVERB, "c %d is active", conn->sd); return true; } if (conn->rmsg != NULL) { log_debug(LOG_VVERB, "c %d is active", conn->sd); return true; } if (conn->smsg != NULL) { log_debug(LOG_VVERB, "c %d is active", conn->sd); return true; } log_debug(LOG_VVERB, "c %d is inactive", conn->sd); return false; } static void client_close_stats(struct context *ctx, struct server_pool *pool, err_t err, unsigned eof) { stats_pool_decr(ctx, pool, client_connections); if (eof) { stats_pool_incr(ctx, pool, client_eof); return; } switch (err) { case EPIPE: case ETIMEDOUT: case ECONNRESET: case ECONNABORTED: case ENOTCONN: case ENETDOWN: case ENETUNREACH: case EHOSTDOWN: case EHOSTUNREACH: default: stats_pool_incr(ctx, pool, client_err); break; } } void client_close(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg, *nmsg; /* current and next message */ ASSERT(conn->client && !conn->proxy); client_close_stats(ctx, conn->owner, conn->err, conn->eof); if (conn->sd < 0) { conn->unref(conn); conn_put(conn); return; } msg = conn->rmsg; if (msg != NULL) { conn->rmsg = NULL; ASSERT(msg->peer == NULL); ASSERT(msg->request && !msg->done); log_debug(LOG_INFO, "close c %d discarding pending req %"PRIu64" len " "%"PRIu32" type %d", conn->sd, msg->id, msg->mlen, msg->type); req_put(msg); } ASSERT(conn->smsg == NULL); ASSERT(TAILQ_EMPTY(&conn->imsg_q)); for (msg = TAILQ_FIRST(&conn->omsg_q); msg != NULL; msg = nmsg) { nmsg = TAILQ_NEXT(msg, c_tqe); /* dequeue the message (request) from client outq */ conn->dequeue_outq(ctx, conn, msg); if (msg->done) { log_debug(LOG_INFO, "close c %d discarding %s req %"PRIu64" len " "%"PRIu32" type %d", conn->sd, msg->error ? "error": "completed", msg->id, msg->mlen, msg->type); req_put(msg); } else { msg->swallow = 1; ASSERT(msg->request); ASSERT(msg->peer == NULL); log_debug(LOG_INFO, "close c %d schedule swallow of req %"PRIu64" " "len %"PRIu32" type %d", conn->sd, msg->id, msg->mlen, msg->type); } } ASSERT(TAILQ_EMPTY(&conn->omsg_q)); conn->unref(conn); status = close(conn->sd); if (status < 0) { log_error("close c %d failed, ignored: %s", conn->sd, strerror(errno)); } conn->sd = -1; conn_put(conn); } nutcracker-0.4.1+dfsg/src/nc_server.h0000664000000000000000000001441412542173515016233 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_SERVER_H_ #define _NC_SERVER_H_ #include /* * server_pool is a collection of servers and their continuum. Each * server_pool is the owner of a single proxy connection and one or * more client connections. server_pool itself is owned by the current * context. * * Each server is the owner of one or more server connections. server * itself is owned by the server_pool. * * +-------------+ * | |<---------------------+ * | |<------------+ | * | | +-------+--+-----+----+--------------+ * | pool 0 |+--->| | | | * | | | server 0 | server 1 | ... ... | * | | | | | |--+ * | | +----------+----------+--------------+ | * +-------------+ // * | | * | | * | | * | pool 1 | * | | * | | * | | * +-------------+ * | | * | | * . . * . ... . * . . * | | * | | * +-------------+ * | * | * // */ typedef uint32_t (*hash_t)(const char *, size_t); struct continuum { uint32_t index; /* server index */ uint32_t value; /* hash value */ }; struct server { uint32_t idx; /* server index */ struct server_pool *owner; /* owner pool */ struct string pname; /* hostname:port:weight (ref in conf_server) */ struct string name; /* hostname:port or [name] (ref in conf_server) */ struct string addrstr; /* hostname (ref in conf_server) */ uint16_t port; /* port */ uint32_t weight; /* weight */ struct sockinfo info; /* server socket info */ uint32_t ns_conn_q; /* # server connection */ struct conn_tqh s_conn_q; /* server connection q */ int64_t next_retry; /* next retry time in usec */ uint32_t failure_count; /* # consecutive failures */ }; struct server_pool { uint32_t idx; /* pool index */ struct context *ctx; /* owner context */ struct conn *p_conn; /* proxy connection (listener) */ uint32_t nc_conn_q; /* # client connection */ struct conn_tqh c_conn_q; /* client connection q */ struct array server; /* server[] */ uint32_t ncontinuum; /* # continuum points */ uint32_t nserver_continuum; /* # servers - live and dead on continuum (const) */ struct continuum *continuum; /* continuum */ uint32_t nlive_server; /* # live server */ int64_t next_rebuild; /* next distribution rebuild time in usec */ struct string name; /* pool name (ref in conf_pool) */ struct string addrstr; /* pool address - hostname:port (ref in conf_pool) */ uint16_t port; /* port */ struct sockinfo info; /* listen socket info */ mode_t perm; /* socket permission */ int dist_type; /* distribution type (dist_type_t) */ int key_hash_type; /* key hash type (hash_type_t) */ hash_t key_hash; /* key hasher */ struct string hash_tag; /* key hash tag (ref in conf_pool) */ int timeout; /* timeout in msec */ int backlog; /* listen backlog */ int redis_db; /* redis database to connect to */ uint32_t client_connections; /* maximum # client connection */ uint32_t server_connections; /* maximum # server connection */ int64_t server_retry_timeout; /* server retry timeout in usec */ uint32_t server_failure_limit; /* server failure limit */ struct string redis_auth; /* redis_auth password (matches requirepass on redis) */ unsigned require_auth; /* require_auth? */ unsigned auto_eject_hosts:1; /* auto_eject_hosts? */ unsigned preconnect:1; /* preconnect? */ unsigned redis:1; /* redis? */ }; void server_ref(struct conn *conn, void *owner); void server_unref(struct conn *conn); int server_timeout(struct conn *conn); bool server_active(struct conn *conn); rstatus_t server_init(struct array *server, struct array *conf_server, struct server_pool *sp); void server_deinit(struct array *server); struct conn *server_conn(struct server *server); rstatus_t server_connect(struct context *ctx, struct server *server, struct conn *conn); void server_close(struct context *ctx, struct conn *conn); void server_connected(struct context *ctx, struct conn *conn); void server_ok(struct context *ctx, struct conn *conn); uint32_t server_pool_idx(struct server_pool *pool, uint8_t *key, uint32_t keylen); struct conn *server_pool_conn(struct context *ctx, struct server_pool *pool, uint8_t *key, uint32_t keylen); rstatus_t server_pool_run(struct server_pool *pool); rstatus_t server_pool_preconnect(struct context *ctx); void server_pool_disconnect(struct context *ctx); rstatus_t server_pool_init(struct array *server_pool, struct array *conf_pool, struct context *ctx); void server_pool_deinit(struct array *server_pool); #endif nutcracker-0.4.1+dfsg/src/proto/0000775000000000000000000000000012542173515015233 5ustar rootrootnutcracker-0.4.1+dfsg/src/proto/nc_proto.h0000664000000000000000000002023412542173515017230 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_PROTO_H_ #define _NC_PROTO_H_ #include #ifdef NC_LITTLE_ENDIAN #define str4cmp(m, c0, c1, c2, c3) \ (*(uint32_t *) m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)) #define str5cmp(m, c0, c1, c2, c3, c4) \ (str4cmp(m, c0, c1, c2, c3) && (m[4] == c4)) #define str6cmp(m, c0, c1, c2, c3, c4, c5) \ (str4cmp(m, c0, c1, c2, c3) && \ (((uint32_t *) m)[1] & 0xffff) == ((c5 << 8) | c4)) #define str7cmp(m, c0, c1, c2, c3, c4, c5, c6) \ (str6cmp(m, c0, c1, c2, c3, c4, c5) && (m[6] == c6)) #define str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ (str4cmp(m, c0, c1, c2, c3) && \ (((uint32_t *) m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4))) #define str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && m[8] == c8) #define str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ (((uint32_t *) m)[2] & 0xffff) == ((c9 << 8) | c8)) #define str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ (str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && (m[10] == c10)) #define str12cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ (((uint32_t *) m)[2] == ((c11 << 24) | (c10 << 16) | (c9 << 8) | c8))) #else #define str4cmp(m, c0, c1, c2, c3) \ (m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3) #define str5cmp(m, c0, c1, c2, c3, c4) \ (str4cmp(m, c0, c1, c2, c3) && (m[4] == c4)) #define str6cmp(m, c0, c1, c2, c3, c4, c5) \ (str5cmp(m, c0, c1, c2, c3, c4) && m[5] == c5) #define str7cmp(m, c0, c1, c2, c3, c4, c5, c6) \ (str6cmp(m, c0, c1, c2, c3, c4, c5) && m[6] == c6) #define str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ (str7cmp(m, c0, c1, c2, c3, c4, c5, c6) && m[7] == c7) #define str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ (str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && m[8] == c8) #define str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ (str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && m[9] == c9) #define str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ (str10cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && m[10] == c10) #define str12cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ (str11cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && m[11] == c11) #endif #define str3icmp(m, c0, c1, c2) \ ((m[0] == c0 || m[0] == (c0 ^ 0x20)) && \ (m[1] == c1 || m[1] == (c1 ^ 0x20)) && \ (m[2] == c2 || m[2] == (c2 ^ 0x20))) #define str4icmp(m, c0, c1, c2, c3) \ (str3icmp(m, c0, c1, c2) && (m[3] == c3 || m[3] == (c3 ^ 0x20))) #define str5icmp(m, c0, c1, c2, c3, c4) \ (str4icmp(m, c0, c1, c2, c3) && (m[4] == c4 || m[4] == (c4 ^ 0x20))) #define str6icmp(m, c0, c1, c2, c3, c4, c5) \ (str5icmp(m, c0, c1, c2, c3, c4) && (m[5] == c5 || m[5] == (c5 ^ 0x20))) #define str7icmp(m, c0, c1, c2, c3, c4, c5, c6) \ (str6icmp(m, c0, c1, c2, c3, c4, c5) && \ (m[6] == c6 || m[6] == (c6 ^ 0x20))) #define str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \ (str7icmp(m, c0, c1, c2, c3, c4, c5, c6) && \ (m[7] == c7 || m[7] == (c7 ^ 0x20))) #define str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \ (str8icmp(m, c0, c1, c2, c3, c4, c5, c6, c7) && \ (m[8] == c8 || m[8] == (c8 ^ 0x20))) #define str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) \ (str9icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) && \ (m[9] == c9 || m[9] == (c9 ^ 0x20))) #define str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) \ (str10icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9) && \ (m[10] == c10 || m[10] == (c10 ^ 0x20))) #define str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) \ (str11icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10) && \ (m[11] == c11 || m[11] == (c11 ^ 0x20))) #define str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) \ (str12icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) && \ (m[12] == c12 || m[12] == (c12 ^ 0x20))) #define str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) \ (str13icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12) && \ (m[13] == c13 || m[13] == (c13 ^ 0x20))) #define str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) \ (str14icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13) && \ (m[14] == c14 || m[14] == (c14 ^ 0x20))) #define str16icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15) \ (str15icmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14) && \ (m[15] == c15 || m[15] == (c15 ^ 0x20))) void memcache_parse_req(struct msg *r); void memcache_parse_rsp(struct msg *r); bool memcache_failure(struct msg *r); void memcache_pre_coalesce(struct msg *r); void memcache_post_coalesce(struct msg *r); rstatus_t memcache_add_auth(struct context *ctx, struct conn *c_conn, struct conn *s_conn); rstatus_t memcache_fragment(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq); rstatus_t memcache_reply(struct msg *r); void memcache_post_connect(struct context *ctx, struct conn *conn, struct server *server); void memcache_swallow_msg(struct conn *conn, struct msg *pmsg, struct msg *msg); void redis_parse_req(struct msg *r); void redis_parse_rsp(struct msg *r); bool redis_failure(struct msg *r); void redis_pre_coalesce(struct msg *r); void redis_post_coalesce(struct msg *r); rstatus_t redis_add_auth(struct context *ctx, struct conn *c_conn, struct conn *s_conn); rstatus_t redis_fragment(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq); rstatus_t redis_reply(struct msg *r); void redis_post_connect(struct context *ctx, struct conn *conn, struct server *server); void redis_swallow_msg(struct conn *conn, struct msg *pmsg, struct msg *msg); #endif nutcracker-0.4.1+dfsg/src/proto/nc_redis.c0000664000000000000000000024257512542173515017204 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #define RSP_STRING(ACTION) \ ACTION( ok, "+OK\r\n" ) \ ACTION( pong, "+PONG\r\n" ) \ ACTION( invalid_password, "-ERR invalid password\r\n" ) \ ACTION( auth_required, "-NOAUTH Authentication required\r\n" ) \ ACTION( no_password, "-ERR Client sent AUTH, but no password is set\r\n" ) \ #define DEFINE_ACTION(_var, _str) static struct string rsp_##_var = string(_str); RSP_STRING( DEFINE_ACTION ) #undef DEFINE_ACTION static rstatus_t redis_handle_auth_req(struct msg *request, struct msg *response); /* * Return true, if the redis command take no key, otherwise * return false */ static bool redis_argz(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_PING: case MSG_REQ_REDIS_QUIT: return true; default: break; } return false; } /* * Return true, if the redis command accepts no arguments, otherwise * return false */ static bool redis_arg0(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_EXISTS: case MSG_REQ_REDIS_PERSIST: case MSG_REQ_REDIS_PTTL: case MSG_REQ_REDIS_TTL: case MSG_REQ_REDIS_TYPE: case MSG_REQ_REDIS_DUMP: case MSG_REQ_REDIS_DECR: case MSG_REQ_REDIS_GET: case MSG_REQ_REDIS_INCR: case MSG_REQ_REDIS_STRLEN: case MSG_REQ_REDIS_HGETALL: case MSG_REQ_REDIS_HKEYS: case MSG_REQ_REDIS_HLEN: case MSG_REQ_REDIS_HVALS: case MSG_REQ_REDIS_LLEN: case MSG_REQ_REDIS_LPOP: case MSG_REQ_REDIS_RPOP: case MSG_REQ_REDIS_SCARD: case MSG_REQ_REDIS_SMEMBERS: case MSG_REQ_REDIS_SPOP: case MSG_REQ_REDIS_ZCARD: case MSG_REQ_REDIS_PFCOUNT: case MSG_REQ_REDIS_AUTH: return true; default: break; } return false; } /* * Return true, if the redis command accepts exactly 1 argument, otherwise * return false */ static bool redis_arg1(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_EXPIRE: case MSG_REQ_REDIS_EXPIREAT: case MSG_REQ_REDIS_PEXPIRE: case MSG_REQ_REDIS_PEXPIREAT: case MSG_REQ_REDIS_APPEND: case MSG_REQ_REDIS_DECRBY: case MSG_REQ_REDIS_GETBIT: case MSG_REQ_REDIS_GETSET: case MSG_REQ_REDIS_INCRBY: case MSG_REQ_REDIS_INCRBYFLOAT: case MSG_REQ_REDIS_SETNX: case MSG_REQ_REDIS_HEXISTS: case MSG_REQ_REDIS_HGET: case MSG_REQ_REDIS_LINDEX: case MSG_REQ_REDIS_LPUSHX: case MSG_REQ_REDIS_RPOPLPUSH: case MSG_REQ_REDIS_RPUSHX: case MSG_REQ_REDIS_SISMEMBER: case MSG_REQ_REDIS_ZRANK: case MSG_REQ_REDIS_ZREVRANK: case MSG_REQ_REDIS_ZSCORE: return true; default: break; } return false; } /* * Return true, if the redis command accepts exactly 2 arguments, otherwise * return false */ static bool redis_arg2(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_GETRANGE: case MSG_REQ_REDIS_PSETEX: case MSG_REQ_REDIS_SETBIT: case MSG_REQ_REDIS_SETEX: case MSG_REQ_REDIS_SETRANGE: case MSG_REQ_REDIS_HINCRBY: case MSG_REQ_REDIS_HINCRBYFLOAT: case MSG_REQ_REDIS_HSET: case MSG_REQ_REDIS_HSETNX: case MSG_REQ_REDIS_LRANGE: case MSG_REQ_REDIS_LREM: case MSG_REQ_REDIS_LSET: case MSG_REQ_REDIS_LTRIM: case MSG_REQ_REDIS_SMOVE: case MSG_REQ_REDIS_ZCOUNT: case MSG_REQ_REDIS_ZLEXCOUNT: case MSG_REQ_REDIS_ZINCRBY: case MSG_REQ_REDIS_ZREMRANGEBYLEX: case MSG_REQ_REDIS_ZREMRANGEBYRANK: case MSG_REQ_REDIS_ZREMRANGEBYSCORE: case MSG_REQ_REDIS_RESTORE: return true; default: break; } return false; } /* * Return true, if the redis command accepts exactly 3 arguments, otherwise * return false */ static bool redis_arg3(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_LINSERT: return true; default: break; } return false; } /* * Return true, if the redis command accepts 0 or more arguments, otherwise * return false */ static bool redis_argn(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_SORT: case MSG_REQ_REDIS_BITCOUNT: case MSG_REQ_REDIS_SET: case MSG_REQ_REDIS_HDEL: case MSG_REQ_REDIS_HMGET: case MSG_REQ_REDIS_HMSET: case MSG_REQ_REDIS_HSCAN: case MSG_REQ_REDIS_LPUSH: case MSG_REQ_REDIS_RPUSH: case MSG_REQ_REDIS_SADD: case MSG_REQ_REDIS_SDIFF: case MSG_REQ_REDIS_SDIFFSTORE: case MSG_REQ_REDIS_SINTER: case MSG_REQ_REDIS_SINTERSTORE: case MSG_REQ_REDIS_SREM: case MSG_REQ_REDIS_SUNION: case MSG_REQ_REDIS_SUNIONSTORE: case MSG_REQ_REDIS_SRANDMEMBER: case MSG_REQ_REDIS_SSCAN: case MSG_REQ_REDIS_PFADD: case MSG_REQ_REDIS_PFMERGE: case MSG_REQ_REDIS_ZADD: case MSG_REQ_REDIS_ZINTERSTORE: case MSG_REQ_REDIS_ZRANGE: case MSG_REQ_REDIS_ZRANGEBYSCORE: case MSG_REQ_REDIS_ZREM: case MSG_REQ_REDIS_ZREVRANGE: case MSG_REQ_REDIS_ZRANGEBYLEX: case MSG_REQ_REDIS_ZREVRANGEBYSCORE: case MSG_REQ_REDIS_ZUNIONSTORE: case MSG_REQ_REDIS_ZSCAN: return true; default: break; } return false; } /* * Return true, if the redis command is a vector command accepting one or * more keys, otherwise return false */ static bool redis_argx(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_MGET: case MSG_REQ_REDIS_DEL: return true; default: break; } return false; } /* * Return true, if the redis command is a vector command accepting one or * more key-value pairs, otherwise return false */ static bool redis_argkvx(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_MSET: return true; default: break; } return false; } /* * Return true, if the redis command is either EVAL or EVALSHA. These commands * have a special format with exactly 2 arguments, followed by one or more keys, * followed by zero or more arguments (the documentation online seems to suggest * that at least one argument is required, but that shouldn't be the case). */ static bool redis_argeval(struct msg *r) { switch (r->type) { case MSG_REQ_REDIS_EVAL: case MSG_REQ_REDIS_EVALSHA: return true; default: break; } return false; } /* * Return true, if the redis response is an error response i.e. a simple * string whose first character is '-', otherwise return false. */ static bool redis_error(struct msg *r) { switch (r->type) { case MSG_RSP_REDIS_ERROR: case MSG_RSP_REDIS_ERROR_ERR: case MSG_RSP_REDIS_ERROR_OOM: case MSG_RSP_REDIS_ERROR_BUSY: case MSG_RSP_REDIS_ERROR_NOAUTH: case MSG_RSP_REDIS_ERROR_LOADING: case MSG_RSP_REDIS_ERROR_BUSYKEY: case MSG_RSP_REDIS_ERROR_MISCONF: case MSG_RSP_REDIS_ERROR_NOSCRIPT: case MSG_RSP_REDIS_ERROR_READONLY: case MSG_RSP_REDIS_ERROR_WRONGTYPE: case MSG_RSP_REDIS_ERROR_EXECABORT: case MSG_RSP_REDIS_ERROR_MASTERDOWN: case MSG_RSP_REDIS_ERROR_NOREPLICAS: return true; default: break; } return false; } /* * Reference: http://redis.io/topics/protocol * * Redis >= 1.2 uses the unified protocol to send requests to the Redis * server. In the unified protocol all the arguments sent to the server * are binary safe and every request has the following general form: * * * CR LF * $ CR LF * CR LF * ... * $ CR LF * CR LF * * Before the unified request protocol, redis protocol for requests supported * the following commands * 1). Inline commands: simple commands where arguments are just space * separated strings. No binary safeness is possible. * 2). Bulk commands: bulk commands are exactly like inline commands, but * the last argument is handled in a special way in order to allow for * a binary-safe last argument. * * Nutcracker only supports the Redis unified protocol for requests. */ void redis_parse_req(struct msg *r) { struct mbuf *b; uint8_t *p, *m; uint8_t ch; enum { SW_START, SW_NARG, SW_NARG_LF, SW_REQ_TYPE_LEN, SW_REQ_TYPE_LEN_LF, SW_REQ_TYPE, SW_REQ_TYPE_LF, SW_KEY_LEN, SW_KEY_LEN_LF, SW_KEY, SW_KEY_LF, SW_ARG1_LEN, SW_ARG1_LEN_LF, SW_ARG1, SW_ARG1_LF, SW_ARG2_LEN, SW_ARG2_LEN_LF, SW_ARG2, SW_ARG2_LF, SW_ARG3_LEN, SW_ARG3_LEN_LF, SW_ARG3, SW_ARG3_LF, SW_ARGN_LEN, SW_ARGN_LEN_LF, SW_ARGN, SW_ARGN_LF, SW_SENTINEL } state; state = r->state; b = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(r->request); ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(b != NULL); ASSERT(b->pos <= b->last); /* validate the parsing maker */ ASSERT(r->pos != NULL); ASSERT(r->pos >= b->pos && r->pos <= b->last); for (p = r->pos; p < b->last; p++) { ch = *p; switch (state) { case SW_START: case SW_NARG: if (r->token == NULL) { if (ch != '*') { goto error; } r->token = p; /* req_start <- p */ r->narg_start = p; r->rnarg = 0; state = SW_NARG; } else if (isdigit(ch)) { r->rnarg = r->rnarg * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if (r->rnarg == 0) { goto error; } r->narg = r->rnarg; r->narg_end = p; r->token = NULL; state = SW_NARG_LF; } else { goto error; } break; case SW_NARG_LF: switch (ch) { case LF: state = SW_REQ_TYPE_LEN; break; default: goto error; } break; case SW_REQ_TYPE_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->token = p; r->rlen = 0; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if (r->rlen == 0 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_REQ_TYPE_LEN_LF; } else { goto error; } break; case SW_REQ_TYPE_LEN_LF: switch (ch) { case LF: state = SW_REQ_TYPE; break; default: goto error; } break; case SW_REQ_TYPE: if (r->token == NULL) { r->token = p; } m = r->token + r->rlen; if (m >= b->last) { m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; m = r->token; r->token = NULL; r->type = MSG_UNKNOWN; switch (p - m) { case 3: if (str3icmp(m, 'g', 'e', 't')) { r->type = MSG_REQ_REDIS_GET; break; } if (str3icmp(m, 's', 'e', 't')) { r->type = MSG_REQ_REDIS_SET; break; } if (str3icmp(m, 't', 't', 'l')) { r->type = MSG_REQ_REDIS_TTL; break; } if (str3icmp(m, 'd', 'e', 'l')) { r->type = MSG_REQ_REDIS_DEL; break; } break; case 4: if (str4icmp(m, 'p', 't', 't', 'l')) { r->type = MSG_REQ_REDIS_PTTL; break; } if (str4icmp(m, 'd', 'e', 'c', 'r')) { r->type = MSG_REQ_REDIS_DECR; break; } if (str4icmp(m, 'd', 'u', 'm', 'p')) { r->type = MSG_REQ_REDIS_DUMP; break; } if (str4icmp(m, 'h', 'd', 'e', 'l')) { r->type = MSG_REQ_REDIS_HDEL; break; } if (str4icmp(m, 'h', 'g', 'e', 't')) { r->type = MSG_REQ_REDIS_HGET; break; } if (str4icmp(m, 'h', 'l', 'e', 'n')) { r->type = MSG_REQ_REDIS_HLEN; break; } if (str4icmp(m, 'h', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_HSET; break; } if (str4icmp(m, 'i', 'n', 'c', 'r')) { r->type = MSG_REQ_REDIS_INCR; break; } if (str4icmp(m, 'l', 'l', 'e', 'n')) { r->type = MSG_REQ_REDIS_LLEN; break; } if (str4icmp(m, 'l', 'p', 'o', 'p')) { r->type = MSG_REQ_REDIS_LPOP; break; } if (str4icmp(m, 'l', 'r', 'e', 'm')) { r->type = MSG_REQ_REDIS_LREM; break; } if (str4icmp(m, 'l', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_LSET; break; } if (str4icmp(m, 'r', 'p', 'o', 'p')) { r->type = MSG_REQ_REDIS_RPOP; break; } if (str4icmp(m, 's', 'a', 'd', 'd')) { r->type = MSG_REQ_REDIS_SADD; break; } if (str4icmp(m, 's', 'p', 'o', 'p')) { r->type = MSG_REQ_REDIS_SPOP; break; } if (str4icmp(m, 's', 'r', 'e', 'm')) { r->type = MSG_REQ_REDIS_SREM; break; } if (str4icmp(m, 't', 'y', 'p', 'e')) { r->type = MSG_REQ_REDIS_TYPE; break; } if (str4icmp(m, 'm', 'g', 'e', 't')) { r->type = MSG_REQ_REDIS_MGET; break; } if (str4icmp(m, 'm', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_MSET; break; } if (str4icmp(m, 'z', 'a', 'd', 'd')) { r->type = MSG_REQ_REDIS_ZADD; break; } if (str4icmp(m, 'z', 'r', 'e', 'm')) { r->type = MSG_REQ_REDIS_ZREM; break; } if (str4icmp(m, 'e', 'v', 'a', 'l')) { r->type = MSG_REQ_REDIS_EVAL; break; } if (str4icmp(m, 's', 'o', 'r', 't')) { r->type = MSG_REQ_REDIS_SORT; break; } if (str4icmp(m, 'p', 'i', 'n', 'g')) { r->type = MSG_REQ_REDIS_PING; r->noforward = 1; break; } if (str4icmp(m, 'q', 'u', 'i', 't')) { r->type = MSG_REQ_REDIS_QUIT; r->quit = 1; break; } if (str4icmp(m, 'a', 'u', 't', 'h')) { r->type = MSG_REQ_REDIS_AUTH; r->noforward = 1; break; } break; case 5: if (str5icmp(m, 'h', 'k', 'e', 'y', 's')) { r->type = MSG_REQ_REDIS_HKEYS; break; } if (str5icmp(m, 'h', 'm', 'g', 'e', 't')) { r->type = MSG_REQ_REDIS_HMGET; break; } if (str5icmp(m, 'h', 'm', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_HMSET; break; } if (str5icmp(m, 'h', 'v', 'a', 'l', 's')) { r->type = MSG_REQ_REDIS_HVALS; break; } if (str5icmp(m, 'h', 's', 'c', 'a', 'n')) { r->type = MSG_REQ_REDIS_HSCAN; break; } if (str5icmp(m, 'l', 'p', 'u', 's', 'h')) { r->type = MSG_REQ_REDIS_LPUSH; break; } if (str5icmp(m, 'l', 't', 'r', 'i', 'm')) { r->type = MSG_REQ_REDIS_LTRIM; break; } if (str5icmp(m, 'r', 'p', 'u', 's', 'h')) { r->type = MSG_REQ_REDIS_RPUSH; break; } if (str5icmp(m, 's', 'c', 'a', 'r', 'd')) { r->type = MSG_REQ_REDIS_SCARD; break; } if (str5icmp(m, 's', 'd', 'i', 'f', 'f')) { r->type = MSG_REQ_REDIS_SDIFF; break; } if (str5icmp(m, 's', 'e', 't', 'e', 'x')) { r->type = MSG_REQ_REDIS_SETEX; break; } if (str5icmp(m, 's', 'e', 't', 'n', 'x')) { r->type = MSG_REQ_REDIS_SETNX; break; } if (str5icmp(m, 's', 'm', 'o', 'v', 'e')) { r->type = MSG_REQ_REDIS_SMOVE; break; } if (str5icmp(m, 's', 's', 'c', 'a', 'n')) { r->type = MSG_REQ_REDIS_SSCAN; break; } if (str5icmp(m, 'z', 'c', 'a', 'r', 'd')) { r->type = MSG_REQ_REDIS_ZCARD; break; } if (str5icmp(m, 'z', 'r', 'a', 'n', 'k')) { r->type = MSG_REQ_REDIS_ZRANK; break; } if (str5icmp(m, 'z', 's', 'c', 'a', 'n')) { r->type = MSG_REQ_REDIS_ZSCAN; break; } if (str5icmp(m, 'p', 'f', 'a', 'd', 'd')) { r->type = MSG_REQ_REDIS_PFADD; break; } break; case 6: if (str6icmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) { r->type = MSG_REQ_REDIS_APPEND; break; } if (str6icmp(m, 'd', 'e', 'c', 'r', 'b', 'y')) { r->type = MSG_REQ_REDIS_DECRBY; break; } if (str6icmp(m, 'e', 'x', 'i', 's', 't', 's')) { r->type = MSG_REQ_REDIS_EXISTS; break; } if (str6icmp(m, 'e', 'x', 'p', 'i', 'r', 'e')) { r->type = MSG_REQ_REDIS_EXPIRE; break; } if (str6icmp(m, 'g', 'e', 't', 'b', 'i', 't')) { r->type = MSG_REQ_REDIS_GETBIT; break; } if (str6icmp(m, 'g', 'e', 't', 's', 'e', 't')) { r->type = MSG_REQ_REDIS_GETSET; break; } if (str6icmp(m, 'p', 's', 'e', 't', 'e', 'x')) { r->type = MSG_REQ_REDIS_PSETEX; break; } if (str6icmp(m, 'h', 's', 'e', 't', 'n', 'x')) { r->type = MSG_REQ_REDIS_HSETNX; break; } if (str6icmp(m, 'i', 'n', 'c', 'r', 'b', 'y')) { r->type = MSG_REQ_REDIS_INCRBY; break; } if (str6icmp(m, 'l', 'i', 'n', 'd', 'e', 'x')) { r->type = MSG_REQ_REDIS_LINDEX; break; } if (str6icmp(m, 'l', 'p', 'u', 's', 'h', 'x')) { r->type = MSG_REQ_REDIS_LPUSHX; break; } if (str6icmp(m, 'l', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_LRANGE; break; } if (str6icmp(m, 'r', 'p', 'u', 's', 'h', 'x')) { r->type = MSG_REQ_REDIS_RPUSHX; break; } if (str6icmp(m, 's', 'e', 't', 'b', 'i', 't')) { r->type = MSG_REQ_REDIS_SETBIT; break; } if (str6icmp(m, 's', 'i', 'n', 't', 'e', 'r')) { r->type = MSG_REQ_REDIS_SINTER; break; } if (str6icmp(m, 's', 't', 'r', 'l', 'e', 'n')) { r->type = MSG_REQ_REDIS_STRLEN; break; } if (str6icmp(m, 's', 'u', 'n', 'i', 'o', 'n')) { r->type = MSG_REQ_REDIS_SUNION; break; } if (str6icmp(m, 'z', 'c', 'o', 'u', 'n', 't')) { r->type = MSG_REQ_REDIS_ZCOUNT; break; } if (str6icmp(m, 'z', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_ZRANGE; break; } if (str6icmp(m, 'z', 's', 'c', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZSCORE; break; } break; case 7: if (str7icmp(m, 'p', 'e', 'r', 's', 'i', 's', 't')) { r->type = MSG_REQ_REDIS_PERSIST; break; } if (str7icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e')) { r->type = MSG_REQ_REDIS_PEXPIRE; break; } if (str7icmp(m, 'h', 'e', 'x', 'i', 's', 't', 's')) { r->type = MSG_REQ_REDIS_HEXISTS; break; } if (str7icmp(m, 'h', 'g', 'e', 't', 'a', 'l', 'l')) { r->type = MSG_REQ_REDIS_HGETALL; break; } if (str7icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y')) { r->type = MSG_REQ_REDIS_HINCRBY; break; } if (str7icmp(m, 'l', 'i', 'n', 's', 'e', 'r', 't')) { r->type = MSG_REQ_REDIS_LINSERT; break; } if (str7icmp(m, 'z', 'i', 'n', 'c', 'r', 'b', 'y')) { r->type = MSG_REQ_REDIS_ZINCRBY; break; } if (str7icmp(m, 'e', 'v', 'a', 'l', 's', 'h', 'a')) { r->type = MSG_REQ_REDIS_EVALSHA; break; } if (str7icmp(m, 'r', 'e', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_RESTORE; break; } if (str7icmp(m, 'p', 'f', 'c', 'o', 'u', 'n', 't')) { r->type = MSG_REQ_REDIS_PFCOUNT; break; } if (str7icmp(m, 'p', 'f', 'm', 'e', 'r', 'g', 'e')) { r->type = MSG_REQ_REDIS_PFMERGE; break; } break; case 8: if (str8icmp(m, 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { r->type = MSG_REQ_REDIS_EXPIREAT; break; } if (str8icmp(m, 'b', 'i', 't', 'c', 'o', 'u', 'n', 't')) { r->type = MSG_REQ_REDIS_BITCOUNT; break; } if (str8icmp(m, 'g', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_GETRANGE; break; } if (str8icmp(m, 's', 'e', 't', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_SETRANGE; break; } if (str8icmp(m, 's', 'm', 'e', 'm', 'b', 'e', 'r', 's')) { r->type = MSG_REQ_REDIS_SMEMBERS; break; } if (str8icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'k')) { r->type = MSG_REQ_REDIS_ZREVRANK; break; } break; case 9: if (str9icmp(m, 'p', 'e', 'x', 'p', 'i', 'r', 'e', 'a', 't')) { r->type = MSG_REQ_REDIS_PEXPIREAT; break; } if (str9icmp(m, 'r', 'p', 'o', 'p', 'l', 'p', 'u', 's', 'h')) { r->type = MSG_REQ_REDIS_RPOPLPUSH; break; } if (str9icmp(m, 's', 'i', 's', 'm', 'e', 'm', 'b', 'e', 'r')) { r->type = MSG_REQ_REDIS_SISMEMBER; break; } if (str9icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e')) { r->type = MSG_REQ_REDIS_ZREVRANGE; break; } if (str9icmp(m, 'z', 'l', 'e', 'x', 'c', 'o', 'u', 'n', 't')) { r->type = MSG_REQ_REDIS_ZLEXCOUNT; break; } break; case 10: if (str10icmp(m, 's', 'd', 'i', 'f', 'f', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_SDIFFSTORE; break; } case 11: if (str11icmp(m, 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { r->type = MSG_REQ_REDIS_INCRBYFLOAT; break; } if (str11icmp(m, 's', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_SINTERSTORE; break; } if (str11icmp(m, 's', 'r', 'a', 'n', 'd', 'm', 'e', 'm', 'b', 'e', 'r')) { r->type = MSG_REQ_REDIS_SRANDMEMBER; break; } if (str11icmp(m, 's', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_SUNIONSTORE; break; } if (str11icmp(m, 'z', 'i', 'n', 't', 'e', 'r', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZINTERSTORE; break; } if (str11icmp(m, 'z', 'u', 'n', 'i', 'o', 'n', 's', 't', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZUNIONSTORE; break; } if (str11icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { r->type = MSG_REQ_REDIS_ZRANGEBYLEX; break; } break; case 12: if (str12icmp(m, 'h', 'i', 'n', 'c', 'r', 'b', 'y', 'f', 'l', 'o', 'a', 't')) { r->type = MSG_REQ_REDIS_HINCRBYFLOAT; break; } break; case 13: if (str13icmp(m, 'z', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZRANGEBYSCORE; break; } break; case 14: if (str14icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'l', 'e', 'x')) { r->type = MSG_REQ_REDIS_ZREMRANGEBYLEX; break; } break; case 15: if (str15icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 'r', 'a', 'n', 'k')) { r->type = MSG_REQ_REDIS_ZREMRANGEBYRANK; break; } break; case 16: if (str16icmp(m, 'z', 'r', 'e', 'm', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZREMRANGEBYSCORE; break; } if (str16icmp(m, 'z', 'r', 'e', 'v', 'r', 'a', 'n', 'g', 'e', 'b', 'y', 's', 'c', 'o', 'r', 'e')) { r->type = MSG_REQ_REDIS_ZREVRANGEBYSCORE; break; } break; default: break; } if (r->type == MSG_UNKNOWN) { log_error("parsed unsupported command '%.*s'", p - m, m); goto error; } log_debug(LOG_VERB, "parsed command '%.*s'", p - m, m); state = SW_REQ_TYPE_LF; break; case SW_REQ_TYPE_LF: switch (ch) { case LF: if (redis_argz(r)) { goto done; } else if (r->narg == 1) { goto error; } else if (redis_argeval(r)) { state = SW_ARG1_LEN; } else { state = SW_KEY_LEN; } break; default: goto error; } break; case SW_KEY_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->token = p; r->rlen = 0; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if (r->rlen >= mbuf_data_size()) { log_error("parsed bad req %"PRIu64" of type %d with key " "length %d that greater than or equal to maximum" " redis key length of %d", r->id, r->type, r->rlen, mbuf_data_size()); goto error; } if (r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_KEY_LEN_LF; } else { goto error; } break; case SW_KEY_LEN_LF: switch (ch) { case LF: state = SW_KEY; break; default: goto error; } break; case SW_KEY: if (r->token == NULL) { r->token = p; } m = r->token + r->rlen; if (m >= b->last) { m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } else { /* got a key */ struct keypos *kpos; p = m; /* move forward by rlen bytes */ r->rlen = 0; m = r->token; r->token = NULL; kpos = array_push(r->keys); if (kpos == NULL) { goto enomem; } kpos->start = m; kpos->end = p; state = SW_KEY_LF; } break; case SW_KEY_LF: switch (ch) { case LF: if (redis_arg0(r)) { if (r->rnarg != 0) { goto error; } goto done; } else if (redis_arg1(r)) { if (r->rnarg != 1) { goto error; } state = SW_ARG1_LEN; } else if (redis_arg2(r)) { if (r->rnarg != 2) { goto error; } state = SW_ARG1_LEN; } else if (redis_arg3(r)) { if (r->rnarg != 3) { goto error; } state = SW_ARG1_LEN; } else if (redis_argn(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARG1_LEN; } else if (redis_argx(r)) { if (r->rnarg == 0) { goto done; } state = SW_KEY_LEN; } else if (redis_argkvx(r)) { if (r->narg % 2 == 0) { goto error; } state = SW_ARG1_LEN; } else if (redis_argeval(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else { goto error; } break; default: goto error; } break; case SW_ARG1_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->rlen = 0; r->token = p; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_ARG1_LEN_LF; } else { goto error; } break; case SW_ARG1_LEN_LF: switch (ch) { case LF: state = SW_ARG1; break; default: goto error; } break; case SW_ARG1: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; state = SW_ARG1_LF; break; case SW_ARG1_LF: switch (ch) { case LF: if (redis_arg1(r)) { if (r->rnarg != 0) { goto error; } goto done; } else if (redis_arg2(r)) { if (r->rnarg != 1) { goto error; } state = SW_ARG2_LEN; } else if (redis_arg3(r)) { if (r->rnarg != 2) { goto error; } state = SW_ARG2_LEN; } else if (redis_argn(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else if (redis_argeval(r)) { if (r->rnarg < 2) { goto error; } state = SW_ARG2_LEN; } else if (redis_argkvx(r)) { if (r->rnarg == 0) { goto done; } state = SW_KEY_LEN; } else { goto error; } break; default: goto error; } break; case SW_ARG2_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->rlen = 0; r->token = p; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_ARG2_LEN_LF; } else { goto error; } break; case SW_ARG2_LEN_LF: switch (ch) { case LF: state = SW_ARG2; break; default: goto error; } break; case SW_ARG2: if (r->token == NULL && redis_argeval(r)) { /* * For EVAL/EVALSHA, ARG2 represents the # key/arg pairs which must * be tokenized and stored in contiguous memory. */ r->token = p; } m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; if (redis_argeval(r)) { uint32_t nkey; uint8_t *chp; /* * For EVAL/EVALSHA, we need to find the integer value of this * argument. It tells us the number of keys in the script, and * we need to error out if number of keys is 0. At this point, * both p and m point to the end of the argument and r->token * points to the start. */ if (p - r->token < 1) { goto error; } for (nkey = 0, chp = r->token; chp < p; chp++) { if (isdigit(*chp)) { nkey = nkey * 10 + (uint32_t)(*chp - '0'); } else { goto error; } } if (nkey == 0) { goto error; } r->token = NULL; } state = SW_ARG2_LF; break; case SW_ARG2_LF: switch (ch) { case LF: if (redis_arg2(r)) { if (r->rnarg != 0) { goto error; } goto done; } else if (redis_arg3(r)) { if (r->rnarg != 1) { goto error; } state = SW_ARG3_LEN; } else if (redis_argn(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else if (redis_argeval(r)) { if (r->rnarg < 1) { goto error; } state = SW_KEY_LEN; } else { goto error; } break; default: goto error; } break; case SW_ARG3_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->rlen = 0; r->token = p; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_ARG3_LEN_LF; } else { goto error; } break; case SW_ARG3_LEN_LF: switch (ch) { case LF: state = SW_ARG3; break; default: goto error; } break; case SW_ARG3: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; state = SW_ARG3_LF; break; case SW_ARG3_LF: switch (ch) { case LF: if (redis_arg3(r)) { if (r->rnarg != 0) { goto error; } goto done; } else if (redis_argn(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else { goto error; } break; default: goto error; } break; case SW_ARGN_LEN: if (r->token == NULL) { if (ch != '$') { goto error; } r->rlen = 0; r->token = p; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } r->rnarg--; r->token = NULL; state = SW_ARGN_LEN_LF; } else { goto error; } break; case SW_ARGN_LEN_LF: switch (ch) { case LF: state = SW_ARGN; break; default: goto error; } break; case SW_ARGN: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; state = SW_ARGN_LF; break; case SW_ARGN_LF: switch (ch) { case LF: if (redis_argn(r) || redis_argeval(r)) { if (r->rnarg == 0) { goto done; } state = SW_ARGN_LEN; } else { goto error; } break; default: goto error; } break; case SW_SENTINEL: default: NOT_REACHED(); break; } } ASSERT(p == b->last); r->pos = p; r->state = state; if (b->last == b->end && r->token != NULL) { r->pos = r->token; r->token = NULL; r->result = MSG_PARSE_REPAIR; } else { r->result = MSG_PARSE_AGAIN; } log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed req %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; done: ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL); r->pos = p + 1; ASSERT(r->pos <= b->last); r->state = SW_START; r->token = NULL; r->result = MSG_PARSE_OK; log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed req %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; enomem: r->result = MSG_PARSE_ERROR; r->state = state; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "out of memory on parse req %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); return; error: r->result = MSG_PARSE_ERROR; r->state = state; errno = EINVAL; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "parsed bad req %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); } /* * Reference: http://redis.io/topics/protocol * * Redis will reply to commands with different kinds of replies. It is * possible to check the kind of reply from the first byte sent by the * server: * - with a single line reply the first byte of the reply will be "+" * - with an error message the first byte of the reply will be "-" * - with an integer number the first byte of the reply will be ":" * - with bulk reply the first byte of the reply will be "$" * - with multi-bulk reply the first byte of the reply will be "*" * * 1). Status reply (or single line reply) is in the form of a single line * string starting with "+" terminated by "\r\n". * 2). Error reply are similar to status replies. The only difference is * that the first byte is "-" instead of "+". * 3). Integer reply is just a CRLF terminated string representing an * integer, and prefixed by a ":" byte. * 4). Bulk reply is used by server to return a single binary safe string. * The first reply line is a "$" byte followed by the number of bytes * of the actual reply, followed by CRLF, then the actual data bytes, * followed by additional two bytes for the final CRLF. If the requested * value does not exist the bulk reply will use the special value '-1' * as the data length. * 5). Multi-bulk reply is used by the server to return many binary safe * strings (bulks) with the initial line indicating how many bulks that * will follow. The first byte of a multi bulk reply is always *. */ void redis_parse_rsp(struct msg *r) { struct mbuf *b; uint8_t *p, *m; uint8_t ch; enum { SW_START, SW_STATUS, SW_ERROR, SW_INTEGER, SW_INTEGER_START, SW_SIMPLE, SW_BULK, SW_BULK_LF, SW_BULK_ARG, SW_BULK_ARG_LF, SW_MULTIBULK, SW_MULTIBULK_NARG_LF, SW_MULTIBULK_ARGN_LEN, SW_MULTIBULK_ARGN_LEN_LF, SW_MULTIBULK_ARGN, SW_MULTIBULK_ARGN_LF, SW_RUNTO_CRLF, SW_ALMOST_DONE, SW_SENTINEL } state; state = r->state; b = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(!r->request); ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(b != NULL); ASSERT(b->pos <= b->last); /* validate the parsing marker */ ASSERT(r->pos != NULL); ASSERT(r->pos >= b->pos && r->pos <= b->last); for (p = r->pos; p < b->last; p++) { ch = *p; switch (state) { case SW_START: r->type = MSG_UNKNOWN; switch (ch) { case '+': p = p - 1; /* go back by 1 byte */ r->type = MSG_RSP_REDIS_STATUS; state = SW_STATUS; break; case '-': r->type = MSG_RSP_REDIS_ERROR; p = p - 1; /* go back by 1 byte */ state = SW_ERROR; break; case ':': r->type = MSG_RSP_REDIS_INTEGER; p = p - 1; /* go back by 1 byte */ state = SW_INTEGER; break; case '$': r->type = MSG_RSP_REDIS_BULK; p = p - 1; /* go back by 1 byte */ state = SW_BULK; break; case '*': r->type = MSG_RSP_REDIS_MULTIBULK; p = p - 1; /* go back by 1 byte */ state = SW_MULTIBULK; break; default: goto error; } break; case SW_STATUS: /* rsp_start <- p */ state = SW_RUNTO_CRLF; break; case SW_ERROR: if (r->token == NULL) { if (ch != '-') { goto error; } /* rsp_start <- p */ r->token = p; } if (ch == ' ' || ch == CR) { m = r->token; r->token = NULL; switch (p - m) { case 4: /* * -ERR no such key\r\n * -ERR syntax error\r\n * -ERR source and destination objects are the same\r\n * -ERR index out of range\r\n */ if (str4cmp(m, '-', 'E', 'R', 'R')) { r->type = MSG_RSP_REDIS_ERROR_ERR; break; } /* -OOM command not allowed when used memory > 'maxmemory'.\r\n */ if (str4cmp(m, '-', 'O', 'O', 'M')) { r->type = MSG_RSP_REDIS_ERROR_OOM; break; } break; case 5: /* -BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n" */ if (str5cmp(m, '-', 'B', 'U', 'S', 'Y')) { r->type = MSG_RSP_REDIS_ERROR_BUSY; break; } break; case 7: /* -NOAUTH Authentication required.\r\n */ if (str7cmp(m, '-', 'N', 'O', 'A', 'U', 'T', 'H')) { r->type = MSG_RSP_REDIS_ERROR_NOAUTH; break; } break; case 8: /* rsp: "-LOADING Redis is loading the dataset in memory\r\n" */ if (str8cmp(m, '-', 'L', 'O', 'A', 'D', 'I', 'N', 'G')) { r->type = MSG_RSP_REDIS_ERROR_LOADING; break; } /* -BUSYKEY Target key name already exists.\r\n */ if (str8cmp(m, '-', 'B', 'U', 'S', 'Y', 'K', 'E', 'Y')) { r->type = MSG_RSP_REDIS_ERROR_BUSYKEY; break; } /* "-MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.\r\n" */ if (str8cmp(m, '-', 'M', 'I', 'S', 'C', 'O', 'N', 'F')) { r->type = MSG_RSP_REDIS_ERROR_MISCONF; break; } break; case 9: /* -NOSCRIPT No matching script. Please use EVAL.\r\n */ if (str9cmp(m, '-', 'N', 'O', 'S', 'C', 'R', 'I', 'P', 'T')) { r->type = MSG_RSP_REDIS_ERROR_NOSCRIPT; break; } /* -READONLY You can't write against a read only slave.\r\n */ if (str9cmp(m, '-', 'R', 'E', 'A', 'D', 'O', 'N', 'L', 'Y')) { r->type = MSG_RSP_REDIS_ERROR_READONLY; break; } break; case 10: /* -WRONGTYPE Operation against a key holding the wrong kind of value\r\n */ if (str10cmp(m, '-', 'W', 'R', 'O', 'N', 'G', 'T', 'Y', 'P', 'E')) { r->type = MSG_RSP_REDIS_ERROR_WRONGTYPE; break; } /* -EXECABORT Transaction discarded because of previous errors.\r\n" */ if (str10cmp(m, '-', 'E', 'X', 'E', 'C', 'A', 'B', 'O', 'R', 'T')) { r->type = MSG_RSP_REDIS_ERROR_EXECABORT; break; } break; case 11: /* -MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'.\r\n */ if (str11cmp(m, '-', 'M', 'A', 'S', 'T', 'E', 'R', 'D', 'O', 'W', 'N')) { r->type = MSG_RSP_REDIS_ERROR_MASTERDOWN; break; } /* -NOREPLICAS Not enough good slaves to write.\r\n */ if (str11cmp(m, '-', 'N', 'O', 'R', 'E', 'P', 'L', 'I', 'C', 'A', 'S')) { r->type = MSG_RSP_REDIS_ERROR_NOREPLICAS; break; } break; } state = SW_RUNTO_CRLF; } break; case SW_INTEGER: /* rsp_start <- p */ state = SW_INTEGER_START; r->integer = 0; break; case SW_SIMPLE: if (ch == CR) { state = SW_MULTIBULK_ARGN_LF; r->rnarg--; } break; case SW_INTEGER_START: if (ch == CR) { state = SW_ALMOST_DONE; } else if (ch == '-') { ; } else if (isdigit(ch)) { r->integer = r->integer * 10 + (uint32_t)(ch - '0'); } else { goto error; } break; case SW_RUNTO_CRLF: switch (ch) { case CR: state = SW_ALMOST_DONE; break; default: break; } break; case SW_ALMOST_DONE: switch (ch) { case LF: /* rsp_end <- p */ goto done; default: goto error; } break; case SW_BULK: if (r->token == NULL) { if (ch != '$') { goto error; } /* rsp_start <- p */ r->token = p; r->rlen = 0; } else if (ch == '-') { /* handles null bulk reply = '$-1' */ state = SW_RUNTO_CRLF; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1) { goto error; } r->token = NULL; state = SW_BULK_LF; } else { goto error; } break; case SW_BULK_LF: switch (ch) { case LF: state = SW_BULK_ARG; break; default: goto error; } break; case SW_BULK_ARG: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p = m; /* move forward by rlen bytes */ r->rlen = 0; state = SW_BULK_ARG_LF; break; case SW_BULK_ARG_LF: switch (ch) { case LF: goto done; default: goto error; } break; case SW_MULTIBULK: if (r->token == NULL) { if (ch != '*') { goto error; } r->token = p; /* rsp_start <- p */ r->narg_start = p; r->rnarg = 0; } else if (ch == '-') { state = SW_RUNTO_CRLF; } else if (isdigit(ch)) { r->rnarg = r->rnarg * 10 + (uint32_t)(ch - '0'); } else if (ch == CR) { if ((p - r->token) <= 1) { goto error; } r->narg = r->rnarg; r->narg_end = p; r->token = NULL; state = SW_MULTIBULK_NARG_LF; } else { goto error; } break; case SW_MULTIBULK_NARG_LF: switch (ch) { case LF: if (r->rnarg == 0) { /* response is '*0\r\n' */ goto done; } state = SW_MULTIBULK_ARGN_LEN; break; default: goto error; } break; case SW_MULTIBULK_ARGN_LEN: if (r->token == NULL) { /* * From: http://redis.io/topics/protocol, a multi bulk reply * is used to return an array of other replies. Every element * of a multi bulk reply can be of any kind, including a * nested multi bulk reply. * * Here, we only handle a multi bulk reply element that * are either integer reply or bulk reply. * * there is a special case for sscan/hscan/zscan, these command * replay a nested multi-bulk with a number and a multi bulk like this: * * - mulit-bulk * - cursor * - mulit-bulk * - val1 * - val2 * - val3 * * in this case, there is only one sub-multi-bulk, * and it's the last element of parent, * we can handle it like tail-recursive. * */ if (ch == '*') { /* for sscan/hscan/zscan only */ p = p - 1; /* go back by 1 byte */ state = SW_MULTIBULK; break; } if (ch == ':' || ch == '+' || ch == '-') { /* handles not-found reply = '$-1' or integer reply = ':' */ /* and *2\r\n$2\r\nr0\r\n+OK\r\n or *1\r\n+OK\r\n */ state = SW_SIMPLE; break; } if (ch != '$') { goto error; } r->token = p; r->rlen = 0; } else if (isdigit(ch)) { r->rlen = r->rlen * 10 + (uint32_t)(ch - '0'); } else if (ch == '-') { ; } else if (ch == CR) { if ((p - r->token) <= 1 || r->rnarg == 0) { goto error; } if ((r->rlen == 1 && (p - r->token) == 3)) { r->rlen = 0; state = SW_MULTIBULK_ARGN_LF; } else { state = SW_MULTIBULK_ARGN_LEN_LF; } r->rnarg--; r->token = NULL; } else { goto error; } break; case SW_MULTIBULK_ARGN_LEN_LF: switch (ch) { case LF: state = SW_MULTIBULK_ARGN; break; default: goto error; } break; case SW_MULTIBULK_ARGN: m = p + r->rlen; if (m >= b->last) { r->rlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; break; } if (*m != CR) { goto error; } p += r->rlen; /* move forward by rlen bytes */ r->rlen = 0; state = SW_MULTIBULK_ARGN_LF; break; case SW_MULTIBULK_ARGN_LF: switch (ch) { case LF: if (r->rnarg == 0) { goto done; } state = SW_MULTIBULK_ARGN_LEN; break; default: goto error; } break; case SW_SENTINEL: default: NOT_REACHED(); break; } } ASSERT(p == b->last); r->pos = p; r->state = state; if (b->last == b->end && r->token != NULL) { r->pos = r->token; r->token = NULL; r->result = MSG_PARSE_REPAIR; } else { r->result = MSG_PARSE_AGAIN; } log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed rsp %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; done: ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL); r->pos = p + 1; ASSERT(r->pos <= b->last); r->state = SW_START; r->token = NULL; r->result = MSG_PARSE_OK; log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed rsp %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; error: r->result = MSG_PARSE_ERROR; r->state = state; errno = EINVAL; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "parsed bad rsp %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); } /* * Return true, if redis replies with a transient server failure response, * otherwise return false * * Transient failures on redis are scenarios when it is temporarily * unresponsive and responds with the following protocol specific error * reply: * -OOM, when redis is out-of-memory * -BUSY, when redis is busy * -LOADING when redis is loading dataset into memory * * See issue: https://github.com/twitter/twemproxy/issues/369 */ bool redis_failure(struct msg *r) { ASSERT(!r->request); switch (r->type) { case MSG_RSP_REDIS_ERROR_OOM: case MSG_RSP_REDIS_ERROR_BUSY: case MSG_RSP_REDIS_ERROR_LOADING: return true; default: break; } return false; } /* * copy one bulk from src to dst * * if dst == NULL, we just eat the bulk * * */ static rstatus_t redis_copy_bulk(struct msg *dst, struct msg *src) { struct mbuf *mbuf, *nbuf; uint8_t *p; uint32_t len = 0; uint32_t bytes = 0; rstatus_t status; for (mbuf = STAILQ_FIRST(&src->mhdr); mbuf && mbuf_empty(mbuf); mbuf = STAILQ_FIRST(&src->mhdr)) { mbuf_remove(&src->mhdr, mbuf); mbuf_put(mbuf); } mbuf = STAILQ_FIRST(&src->mhdr); if (mbuf == NULL) { return NC_ERROR; } p = mbuf->pos; ASSERT(*p == '$'); p++; if (p[0] == '-' && p[1] == '1') { len = 1 + 2 + CRLF_LEN; /* $-1\r\n */ p = mbuf->pos + len; } else { len = 0; for (; p < mbuf->last && isdigit(*p); p++) { len = len * 10 + (uint32_t)(*p - '0'); } len += CRLF_LEN * 2; len += (p - mbuf->pos); } bytes = len; /* copy len bytes to dst */ for (; mbuf;) { if (mbuf_length(mbuf) <= len) { /* steal this buf from src to dst */ nbuf = STAILQ_NEXT(mbuf, next); mbuf_remove(&src->mhdr, mbuf); if (dst != NULL) { mbuf_insert(&dst->mhdr, mbuf); } len -= mbuf_length(mbuf); mbuf = nbuf; } else { /* split it */ if (dst != NULL) { status = msg_append(dst, mbuf->pos, len); if (status != NC_OK) { return status; } } mbuf->pos += len; break; } } if (dst != NULL) { dst->mlen += bytes; } src->mlen -= bytes; log_debug(LOG_VVERB, "redis_copy_bulk copy bytes: %d", bytes); return NC_OK; } /* * Pre-coalesce handler is invoked when the message is a response to * the fragmented multi vector request - 'mget' or 'del' and all the * responses to the fragmented request vector hasn't been received */ void redis_pre_coalesce(struct msg *r) { struct msg *pr = r->peer; /* peer request */ struct mbuf *mbuf; ASSERT(!r->request); ASSERT(pr->request); if (pr->frag_id == 0) { /* do nothing, if not a response to a fragmented request */ return; } pr->frag_owner->nfrag_done++; switch (r->type) { case MSG_RSP_REDIS_INTEGER: /* only redis 'del' fragmented request sends back integer reply */ ASSERT(pr->type == MSG_REQ_REDIS_DEL); mbuf = STAILQ_FIRST(&r->mhdr); /* * Our response parser guarantees that the integer reply will be * completely encapsulated in a single mbuf and we should skip over * all the mbuf contents and discard it as the parser has already * parsed the integer reply and stored it in msg->integer */ ASSERT(mbuf == STAILQ_LAST(&r->mhdr, mbuf, next)); ASSERT(r->mlen == mbuf_length(mbuf)); r->mlen -= mbuf_length(mbuf); mbuf_rewind(mbuf); /* accumulate the integer value in frag_owner of peer request */ pr->frag_owner->integer += r->integer; break; case MSG_RSP_REDIS_MULTIBULK: /* only redis 'mget' fragmented request sends back multi-bulk reply */ ASSERT(pr->type == MSG_REQ_REDIS_MGET); mbuf = STAILQ_FIRST(&r->mhdr); /* * Muti-bulk reply can span over multiple mbufs and in each reply * we should skip over the narg token. Our response parser * guarantees thaat the narg token and the immediately following * '\r\n' will exist in a contiguous region in the first mbuf */ ASSERT(r->narg_start == mbuf->pos); ASSERT(r->narg_start < r->narg_end); r->narg_end += CRLF_LEN; r->mlen -= (uint32_t)(r->narg_end - r->narg_start); mbuf->pos = r->narg_end; break; case MSG_RSP_REDIS_STATUS: if (pr->type == MSG_REQ_REDIS_MSET) { /* MSET segments */ mbuf = STAILQ_FIRST(&r->mhdr); r->mlen -= mbuf_length(mbuf); mbuf_rewind(mbuf); } break; default: /* * Valid responses for a fragmented request are MSG_RSP_REDIS_INTEGER or, * MSG_RSP_REDIS_MULTIBULK. For an invalid response, we send out -ERR * with EINVAL errno */ mbuf = STAILQ_FIRST(&r->mhdr); log_hexdump(LOG_ERR, mbuf->pos, mbuf_length(mbuf), "rsp fragment " "with unknown type %d", r->type); pr->error = 1; pr->err = EINVAL; break; } } static rstatus_t redis_append_key(struct msg *r, uint8_t *key, uint32_t keylen) { uint32_t len; struct mbuf *mbuf; uint8_t printbuf[32]; struct keypos *kpos; /* 1. keylen */ len = (uint32_t)nc_snprintf(printbuf, sizeof(printbuf), "$%d\r\n", keylen); mbuf = msg_ensure_mbuf(r, len); if (mbuf == NULL) { return NC_ENOMEM; } mbuf_copy(mbuf, printbuf, len); r->mlen += len; /* 2. key */ mbuf = msg_ensure_mbuf(r, keylen); if (mbuf == NULL) { return NC_ENOMEM; } kpos = array_push(r->keys); if (kpos == NULL) { return NC_ENOMEM; } kpos->start = mbuf->last; kpos->end = mbuf->last + keylen; mbuf_copy(mbuf, key, keylen); r->mlen += keylen; /* 3. CRLF */ mbuf = msg_ensure_mbuf(r, CRLF_LEN); if (mbuf == NULL) { return NC_ENOMEM; } mbuf_copy(mbuf, (uint8_t *)CRLF, CRLF_LEN); r->mlen += (uint32_t)CRLF_LEN; return NC_OK; } /* * input a msg, return a msg chain. * ncontinuum is the number of backend redis/memcache server * * the original msg will be fragment into at most ncontinuum fragments. * all the keys map to the same backend will group into one fragment. * * frag_id: * a unique fragment id for all fragments of the message vector. including the orig msg. * * frag_owner: * All fragments of the message use frag_owner point to the orig msg * * frag_seq: * the map from each key to it's fragment, (only in the orig msg) * * For example, a message vector with 3 keys: * * get key1 key2 key3 * * suppose we have 2 backend server, and the map is: * * key1 => backend 0 * key2 => backend 1 * key3 => backend 0 * * it will fragment like this: * * +-----------------+ * | msg vector | * |(original msg) | * |key1, key2, key3 | * +-----------------+ * * frag_owner * /--------------------------------------+ * frag_owner / | * /-----------+ | /------------+ frag_owner | * | | | | | | * | v v v | | * +--------------------+ +---------------------+ +----+----------------+ * | frag_id = 10 | | frag_id = 10 | | frag_id = 10 | * | nfrag = 3 | | nfrag = 0 | | nfrag = 0 | * | frag_seq = x x x | | key1, key3 | | key2 | * +------------|-|-|---+ +---------------------+ +---------------------+ * | | | ^ ^ ^ * | \ \ | | | * | \ ----------+ | | * +---\---------------+ | * ------------------------------------------+ * */ static rstatus_t redis_fragment_argx(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq, uint32_t key_step) { struct mbuf *mbuf; struct msg **sub_msgs; uint32_t i; rstatus_t status; ASSERT(array_n(r->keys) == (r->narg - 1) / key_step); sub_msgs = nc_zalloc(ncontinuum * sizeof(*sub_msgs)); if (sub_msgs == NULL) { return NC_ENOMEM; } ASSERT(r->frag_seq == NULL); r->frag_seq = nc_alloc(array_n(r->keys) * sizeof(*r->frag_seq)); if (r->frag_seq == NULL) { nc_free(sub_msgs); return NC_ENOMEM; } mbuf = STAILQ_FIRST(&r->mhdr); mbuf->pos = mbuf->start; /* * This code is based on the assumption that '*narg\r\n$4\r\nMGET\r\n' is located * in a contiguous location. * This is always true because we have capped our MBUF_MIN_SIZE at 512 and * whenever we have multiple messages, we copy the tail message into a new mbuf */ for (i = 0; i < 3; i++) { /* eat *narg\r\n$4\r\nMGET\r\n */ for (; *(mbuf->pos) != '\n';) { mbuf->pos++; } mbuf->pos++; } r->frag_id = msg_gen_frag_id(); r->nfrag = 0; r->frag_owner = r; for (i = 0; i < array_n(r->keys); i++) { /* for each key */ struct msg *sub_msg; struct keypos *kpos = array_get(r->keys, i); uint32_t idx = msg_backend_idx(r, kpos->start, kpos->end - kpos->start); if (sub_msgs[idx] == NULL) { sub_msgs[idx] = msg_get(r->owner, r->request, r->redis); if (sub_msgs[idx] == NULL) { nc_free(sub_msgs); return NC_ENOMEM; } } r->frag_seq[i] = sub_msg = sub_msgs[idx]; sub_msg->narg++; status = redis_append_key(sub_msg, kpos->start, kpos->end - kpos->start); if (status != NC_OK) { nc_free(sub_msgs); return status; } if (key_step == 1) { /* mget,del */ continue; } else { /* mset */ status = redis_copy_bulk(NULL, r); /* eat key */ if (status != NC_OK) { nc_free(sub_msgs); return status; } status = redis_copy_bulk(sub_msg, r); if (status != NC_OK) { nc_free(sub_msgs); return status; } sub_msg->narg++; } } for (i = 0; i < ncontinuum; i++) { /* prepend mget header, and forward it */ struct msg *sub_msg = sub_msgs[i]; if (sub_msg == NULL) { continue; } if (r->type == MSG_REQ_REDIS_MGET) { status = msg_prepend_format(sub_msg, "*%d\r\n$4\r\nmget\r\n", sub_msg->narg + 1); } else if (r->type == MSG_REQ_REDIS_DEL) { status = msg_prepend_format(sub_msg, "*%d\r\n$3\r\ndel\r\n", sub_msg->narg + 1); } else if (r->type == MSG_REQ_REDIS_MSET) { status = msg_prepend_format(sub_msg, "*%d\r\n$4\r\nmset\r\n", sub_msg->narg + 1); } else { NOT_REACHED(); } if (status != NC_OK) { nc_free(sub_msgs); return status; } sub_msg->type = r->type; sub_msg->frag_id = r->frag_id; sub_msg->frag_owner = r->frag_owner; TAILQ_INSERT_TAIL(frag_msgq, sub_msg, m_tqe); r->nfrag++; } nc_free(sub_msgs); return NC_OK; } rstatus_t redis_fragment(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq) { switch (r->type) { case MSG_REQ_REDIS_MGET: case MSG_REQ_REDIS_DEL: return redis_fragment_argx(r, ncontinuum, frag_msgq, 1); case MSG_REQ_REDIS_MSET: return redis_fragment_argx(r, ncontinuum, frag_msgq, 2); default: return NC_OK; } } rstatus_t redis_reply(struct msg *r) { struct conn *c_conn; struct msg *response = r->peer; ASSERT(response != NULL && response->owner != NULL); c_conn = response->owner; if (r->type == MSG_REQ_REDIS_AUTH) { return redis_handle_auth_req(r, response); } if (!conn_authenticated(c_conn)) { return msg_append(response, rsp_auth_required.data, rsp_auth_required.len); } switch (r->type) { case MSG_REQ_REDIS_PING: return msg_append(response, rsp_pong.data, rsp_pong.len); default: NOT_REACHED(); return NC_ERROR; } } void redis_post_coalesce_mset(struct msg *request) { rstatus_t status; struct msg *response = request->peer; status = msg_append(response, rsp_ok.data, rsp_ok.len); if (status != NC_OK) { response->error = 1; /* mark this msg as err */ response->err = errno; } } void redis_post_coalesce_del(struct msg *request) { struct msg *response = request->peer; rstatus_t status; status = msg_prepend_format(response, ":%d\r\n", request->integer); if (status != NC_OK) { response->error = 1; response->err = errno; } } static void redis_post_coalesce_mget(struct msg *request) { struct msg *response = request->peer; struct msg *sub_msg; rstatus_t status; uint32_t i; status = msg_prepend_format(response, "*%d\r\n", request->narg - 1); if (status != NC_OK) { /* * the fragments is still in c_conn->omsg_q, we have to discard all of them, * we just close the conn here */ response->owner->err = 1; return; } for (i = 0; i < array_n(request->keys); i++) { /* for each key */ sub_msg = request->frag_seq[i]->peer; /* get it's peer response */ if (sub_msg == NULL) { response->owner->err = 1; return; } status = redis_copy_bulk(response, sub_msg); if (status != NC_OK) { response->owner->err = 1; return; } } } /* * Post-coalesce handler is invoked when the message is a response to * the fragmented multi vector request - 'mget' or 'del' and all the * responses to the fragmented request vector has been received and * the fragmented request is consider to be done */ void redis_post_coalesce(struct msg *r) { struct msg *pr = r->peer; /* peer response */ ASSERT(!pr->request); ASSERT(r->request && (r->frag_owner == r)); if (r->error || r->ferror) { /* do nothing, if msg is in error */ return; } switch (r->type) { case MSG_REQ_REDIS_MGET: return redis_post_coalesce_mget(r); case MSG_REQ_REDIS_DEL: return redis_post_coalesce_del(r); case MSG_REQ_REDIS_MSET: return redis_post_coalesce_mset(r); default: NOT_REACHED(); } } static rstatus_t redis_handle_auth_req(struct msg *req, struct msg *rsp) { struct conn *conn = (struct conn *)rsp->owner; struct server_pool *pool; struct keypos *kpos; uint8_t *key; uint32_t keylen; bool valid; ASSERT(conn->client && !conn->proxy); pool = (struct server_pool *)conn->owner; if (!pool->require_auth) { /* * AUTH command from the client in absence of a redis_auth: * directive should be treated as an error */ return msg_append(rsp, rsp_no_password.data, rsp_no_password.len); } kpos = array_get(req->keys, 0); key = kpos->start; keylen = (uint32_t)(kpos->end - kpos->start); valid = (keylen == pool->redis_auth.len) && (memcmp(pool->redis_auth.data, key, keylen) == 0) ? true : false; if (valid) { conn->authenticated = 1; return msg_append(rsp, rsp_ok.data, rsp_ok.len); } /* * Password in the AUTH command doesn't match the one configured in * redis_auth: directive * * We mark the connection has unauthenticated until the client * reauthenticates with the correct password */ conn->authenticated = 0; return msg_append(rsp, rsp_invalid_password.data, rsp_invalid_password.len); } rstatus_t redis_add_auth(struct context *ctx, struct conn *c_conn, struct conn *s_conn) { rstatus_t status; struct msg *msg; struct server_pool *pool; ASSERT(!s_conn->client && !s_conn->proxy); ASSERT(!conn_authenticated(s_conn)); pool = c_conn->owner; msg = msg_get(c_conn, true, c_conn->redis); if (msg == NULL) { c_conn->err = errno; return NC_ENOMEM; } status = msg_prepend_format(msg, "*2\r\n$4\r\nAUTH\r\n$%d\r\n%s\r\n", pool->redis_auth.len, pool->redis_auth.data); if (status != NC_OK) { msg_put(msg); return status; } msg->swallow = 1; s_conn->enqueue_inq(ctx, s_conn, msg); s_conn->authenticated = 1; return NC_OK; } void redis_post_connect(struct context *ctx, struct conn *conn, struct server *server) { rstatus_t status; struct server_pool *pool = server->owner; struct msg *msg; int digits; ASSERT(!conn->client && conn->connected); ASSERT(conn->redis); /* * By default, every connection to redis uses the database DB 0. You * can select a different one on a per-connection basis by sending * a request 'SELECT ', where is the configured * on a per pool basis in the configuration */ if (pool->redis_db <= 0) { return; } /* * Create a fake client message and add it to the pipeline. We force this * message to be head of queue as it might already contain a command * that triggered the connect. */ msg = msg_get(conn, true, conn->redis); if (msg == NULL) { return; } digits = (pool->redis_db >= 10) ? (int)log10(pool->redis_db) + 1 : 1; status = msg_prepend_format(msg, "*2\r\n$6\r\nSELECT\r\n$%d\r\n%d\r\n", digits, pool->redis_db); if (status != NC_OK) { msg_put(msg); return; } msg->type = MSG_REQ_REDIS_SELECT; msg->result = MSG_PARSE_OK; msg->swallow = 1; msg->owner = NULL; /* enqueue as head and send */ req_server_enqueue_imsgq_head(ctx, conn, msg); msg_send(ctx, conn); log_debug(LOG_NOTICE, "sent 'SELECT %d' to %s | %s", pool->redis_db, pool->name.data, server->name.data); } void redis_swallow_msg(struct conn *conn, struct msg *pmsg, struct msg *msg) { if (pmsg != NULL && pmsg->type == MSG_REQ_REDIS_SELECT && msg != NULL && redis_error(msg)) { struct server* conn_server; struct server_pool* conn_pool; struct mbuf* rsp_buffer; uint8_t message[128]; size_t copy_len; /* * Get a substring from the message so that the inital - and the trailing * \r\n is removed. */ conn_server = (struct server*)conn->owner; conn_pool = conn_server->owner; rsp_buffer = STAILQ_LAST(&msg->mhdr, mbuf, next); copy_len = MIN(mbuf_length(rsp_buffer) - 3, sizeof(message) - 1); nc_memcpy(message, &rsp_buffer->start[1], copy_len); message[copy_len] = 0; log_warn("SELECT %d failed on %s | %s: %s", conn_pool->redis_db, conn_pool->name.data, conn_server->name.data, message); } } nutcracker-0.4.1+dfsg/src/proto/nc_memcache.c0000664000000000000000000012003212542173515017617 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include /* * From memcache protocol specification: * * Data stored by memcached is identified with the help of a key. A key * is a text string which should uniquely identify the data for clients * that are interested in storing and retrieving it. Currently the * length limit of a key is set at 250 characters (of course, normally * clients wouldn't need to use such long keys); the key must not include * control characters or whitespace. */ #define MEMCACHE_MAX_KEY_LENGTH 250 /* * Return true, if the memcache command is a storage command, otherwise * return false */ static bool memcache_storage(struct msg *r) { switch (r->type) { case MSG_REQ_MC_SET: case MSG_REQ_MC_CAS: case MSG_REQ_MC_ADD: case MSG_REQ_MC_REPLACE: case MSG_REQ_MC_APPEND: case MSG_REQ_MC_PREPEND: return true; default: break; } return false; } /* * Return true, if the memcache command is a cas command, otherwise * return false */ static bool memcache_cas(struct msg *r) { if (r->type == MSG_REQ_MC_CAS) { return true; } return false; } /* * Return true, if the memcache command is a retrieval command, otherwise * return false */ static bool memcache_retrieval(struct msg *r) { switch (r->type) { case MSG_REQ_MC_GET: case MSG_REQ_MC_GETS: return true; default: break; } return false; } /* * Return true, if the memcache command is a arithmetic command, otherwise * return false */ static bool memcache_arithmetic(struct msg *r) { switch (r->type) { case MSG_REQ_MC_INCR: case MSG_REQ_MC_DECR: return true; default: break; } return false; } /* * Return true, if the memcache command is a delete command, otherwise * return false */ static bool memcache_delete(struct msg *r) { if (r->type == MSG_REQ_MC_DELETE) { return true; } return false; } /* * Return true, if the memcache command is a touch command, otherwise * return false */ static bool memcache_touch(struct msg *r) { if (r->type == MSG_REQ_MC_TOUCH) { return true; } return false; } void memcache_parse_req(struct msg *r) { struct mbuf *b; uint8_t *p, *m; uint8_t ch; enum { SW_START, SW_REQ_TYPE, SW_SPACES_BEFORE_KEY, SW_KEY, SW_SPACES_BEFORE_KEYS, SW_SPACES_BEFORE_FLAGS, SW_FLAGS, SW_SPACES_BEFORE_EXPIRY, SW_EXPIRY, SW_SPACES_BEFORE_VLEN, SW_VLEN, SW_SPACES_BEFORE_CAS, SW_CAS, SW_RUNTO_VAL, SW_VAL, SW_SPACES_BEFORE_NUM, SW_NUM, SW_RUNTO_CRLF, SW_CRLF, SW_NOREPLY, SW_AFTER_NOREPLY, SW_ALMOST_DONE, SW_SENTINEL } state; state = r->state; b = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(r->request); ASSERT(!r->redis); ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(b != NULL); ASSERT(b->pos <= b->last); /* validate the parsing maker */ ASSERT(r->pos != NULL); ASSERT(r->pos >= b->pos && r->pos <= b->last); for (p = r->pos; p < b->last; p++) { ch = *p; switch (state) { case SW_START: if (ch == ' ') { break; } if (!islower(ch)) { goto error; } /* req_start <- p; type_start <- p */ r->token = p; state = SW_REQ_TYPE; break; case SW_REQ_TYPE: if (ch == ' ' || ch == CR) { /* type_end = p - 1 */ m = r->token; r->token = NULL; r->type = MSG_UNKNOWN; r->narg++; switch (p - m) { case 3: if (str4cmp(m, 'g', 'e', 't', ' ')) { r->type = MSG_REQ_MC_GET; break; } if (str4cmp(m, 's', 'e', 't', ' ')) { r->type = MSG_REQ_MC_SET; break; } if (str4cmp(m, 'a', 'd', 'd', ' ')) { r->type = MSG_REQ_MC_ADD; break; } if (str4cmp(m, 'c', 'a', 's', ' ')) { r->type = MSG_REQ_MC_CAS; break; } break; case 4: if (str4cmp(m, 'g', 'e', 't', 's')) { r->type = MSG_REQ_MC_GETS; break; } if (str4cmp(m, 'i', 'n', 'c', 'r')) { r->type = MSG_REQ_MC_INCR; break; } if (str4cmp(m, 'd', 'e', 'c', 'r')) { r->type = MSG_REQ_MC_DECR; break; } if (str4cmp(m, 'q', 'u', 'i', 't')) { r->type = MSG_REQ_MC_QUIT; r->quit = 1; break; } break; case 5: if (str5cmp(m, 't', 'o', 'u', 'c', 'h')) { r->type = MSG_REQ_MC_TOUCH; break; } break; case 6: if (str6cmp(m, 'a', 'p', 'p', 'e', 'n', 'd')) { r->type = MSG_REQ_MC_APPEND; break; } if (str6cmp(m, 'd', 'e', 'l', 'e', 't', 'e')) { r->type = MSG_REQ_MC_DELETE; break; } break; case 7: if (str7cmp(m, 'p', 'r', 'e', 'p', 'e', 'n', 'd')) { r->type = MSG_REQ_MC_PREPEND; break; } if (str7cmp(m, 'r', 'e', 'p', 'l', 'a', 'c', 'e')) { r->type = MSG_REQ_MC_REPLACE; break; } break; } switch (r->type) { case MSG_REQ_MC_GET: case MSG_REQ_MC_GETS: case MSG_REQ_MC_DELETE: case MSG_REQ_MC_CAS: case MSG_REQ_MC_SET: case MSG_REQ_MC_ADD: case MSG_REQ_MC_REPLACE: case MSG_REQ_MC_APPEND: case MSG_REQ_MC_PREPEND: case MSG_REQ_MC_INCR: case MSG_REQ_MC_DECR: case MSG_REQ_MC_TOUCH: if (ch == CR) { goto error; } state = SW_SPACES_BEFORE_KEY; break; case MSG_REQ_MC_QUIT: p = p - 1; /* go back by 1 byte */ state = SW_CRLF; break; case MSG_UNKNOWN: goto error; default: NOT_REACHED(); } } else if (!islower(ch)) { goto error; } break; case SW_SPACES_BEFORE_KEY: if (ch != ' ') { p = p - 1; /* go back by 1 byte */ r->token = NULL; state = SW_KEY; } break; case SW_KEY: if (r->token == NULL) { r->token = p; } if (ch == ' ' || ch == CR) { struct keypos *kpos; int keylen = p - r->token; if (keylen > MEMCACHE_MAX_KEY_LENGTH) { log_error("parsed bad req %"PRIu64" of type %d with key " "prefix '%.*s...' and length %d that exceeds " "maximum key length", r->id, r->type, 16, r->token, p - r->token); goto error; } else if (keylen == 0) { log_error("parsed bad req %"PRIu64" of type %d with an " "empty key", r->id, r->type); goto error; } kpos = array_push(r->keys); if (kpos == NULL) { goto enomem; } kpos->start = r->token; kpos->end = p; r->narg++; r->token = NULL; /* get next state */ if (memcache_storage(r)) { state = SW_SPACES_BEFORE_FLAGS; } else if (memcache_arithmetic(r) || memcache_touch(r) ) { state = SW_SPACES_BEFORE_NUM; } else if (memcache_delete(r)) { state = SW_RUNTO_CRLF; } else if (memcache_retrieval(r)) { state = SW_SPACES_BEFORE_KEYS; } else { state = SW_RUNTO_CRLF; } if (ch == CR) { if (memcache_storage(r) || memcache_arithmetic(r)) { goto error; } p = p - 1; /* go back by 1 byte */ } } break; case SW_SPACES_BEFORE_KEYS: ASSERT(memcache_retrieval(r)); switch (ch) { case ' ': break; case CR: state = SW_ALMOST_DONE; break; default: r->token = NULL; p = p - 1; /* go back by 1 byte */ state = SW_KEY; } break; case SW_SPACES_BEFORE_FLAGS: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* flags_start <- p; flags <- ch - '0' */ r->token = p; state = SW_FLAGS; } break; case SW_FLAGS: if (isdigit(ch)) { /* flags <- flags * 10 + (ch - '0') */ ; } else if (ch == ' ') { /* flags_end <- p - 1 */ r->token = NULL; state = SW_SPACES_BEFORE_EXPIRY; } else { goto error; } break; case SW_SPACES_BEFORE_EXPIRY: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* expiry_start <- p; expiry <- ch - '0' */ r->token = p; state = SW_EXPIRY; } break; case SW_EXPIRY: if (isdigit(ch)) { /* expiry <- expiry * 10 + (ch - '0') */ ; } else if (ch == ' ') { /* expiry_end <- p - 1 */ r->token = NULL; state = SW_SPACES_BEFORE_VLEN; } else { goto error; } break; case SW_SPACES_BEFORE_VLEN: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* vlen_start <- p */ r->vlen = (uint32_t)(ch - '0'); state = SW_VLEN; } break; case SW_VLEN: if (isdigit(ch)) { r->vlen = r->vlen * 10 + (uint32_t)(ch - '0'); } else if (memcache_cas(r)) { if (ch != ' ') { goto error; } /* vlen_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ r->token = NULL; state = SW_SPACES_BEFORE_CAS; } else if (ch == ' ' || ch == CR) { /* vlen_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ r->token = NULL; state = SW_RUNTO_CRLF; } else { goto error; } break; case SW_SPACES_BEFORE_CAS: if (ch != ' ') { if (!isdigit(ch)) { goto error; } /* cas_start <- p; cas <- ch - '0' */ r->token = p; state = SW_CAS; } break; case SW_CAS: if (isdigit(ch)) { /* cas <- cas * 10 + (ch - '0') */ ; } else if (ch == ' ' || ch == CR) { /* cas_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ r->token = NULL; state = SW_RUNTO_CRLF; } else { goto error; } break; case SW_RUNTO_VAL: switch (ch) { case LF: /* val_start <- p + 1 */ state = SW_VAL; break; default: goto error; } break; case SW_VAL: m = p + r->vlen; if (m >= b->last) { ASSERT(r->vlen >= (uint32_t)(b->last - p)); r->vlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; /* move forward by vlen bytes */ break; } switch (*m) { case CR: /* val_end <- p - 1 */ p = m; /* move forward by vlen bytes */ state = SW_ALMOST_DONE; break; default: goto error; } break; case SW_SPACES_BEFORE_NUM: if (ch != ' ') { if (!(isdigit(ch) || ch == '-')) { goto error; } /* num_start <- p; num <- ch - '0' */ r->token = p; state = SW_NUM; } break; case SW_NUM: if (isdigit(ch)) { /* num <- num * 10 + (ch - '0') */ ; } else if (ch == ' ' || ch == CR) { r->token = NULL; /* num_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ state = SW_RUNTO_CRLF; } else { goto error; } break; case SW_RUNTO_CRLF: switch (ch) { case ' ': break; case 'n': if (memcache_storage(r) || memcache_arithmetic(r) || memcache_delete(r) || memcache_touch(r)) { /* noreply_start <- p */ r->token = p; state = SW_NOREPLY; } else { goto error; } break; case CR: if (memcache_storage(r)) { state = SW_RUNTO_VAL; } else { state = SW_ALMOST_DONE; } break; default: goto error; } break; case SW_NOREPLY: switch (ch) { case ' ': case CR: m = r->token; if (((p - m) == 7) && str7cmp(m, 'n', 'o', 'r', 'e', 'p', 'l', 'y')) { ASSERT(memcache_storage(r) || memcache_arithmetic(r) || memcache_delete(r) || memcache_touch(r)); r->token = NULL; /* noreply_end <- p - 1 */ r->noreply = 1; state = SW_AFTER_NOREPLY; p = p - 1; /* go back by 1 byte */ } else { goto error; } } break; case SW_AFTER_NOREPLY: switch (ch) { case ' ': break; case CR: if (memcache_storage(r)) { state = SW_RUNTO_VAL; } else { state = SW_ALMOST_DONE; } break; default: goto error; } break; case SW_CRLF: switch (ch) { case ' ': break; case CR: state = SW_ALMOST_DONE; break; default: goto error; } break; case SW_ALMOST_DONE: switch (ch) { case LF: /* req_end <- p */ goto done; default: goto error; } break; case SW_SENTINEL: default: NOT_REACHED(); break; } } /* * At this point, buffer from b->pos to b->last has been parsed completely * but we haven't been able to reach to any conclusion. Normally, this * means that we have to parse again starting from the state we are in * after more data has been read. The newly read data is either read into * a new mbuf, if existing mbuf is full (b->last == b->end) or into the * existing mbuf. * * The only exception to this is when the existing mbuf is full (b->last * is at b->end) and token marker is set, which means that we have to * copy the partial token into a new mbuf and parse again with more data * read into new mbuf. */ ASSERT(p == b->last); r->pos = p; r->state = state; if (b->last == b->end && r->token != NULL) { r->pos = r->token; r->token = NULL; r->result = MSG_PARSE_REPAIR; } else { r->result = MSG_PARSE_AGAIN; } log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed req %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; done: ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL); r->pos = p + 1; ASSERT(r->pos <= b->last); r->state = SW_START; r->result = MSG_PARSE_OK; log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed req %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; enomem: r->result = MSG_PARSE_ERROR; r->state = state; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "out of memory on parse req %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); return; error: r->result = MSG_PARSE_ERROR; r->state = state; errno = EINVAL; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "parsed bad req %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); } void memcache_parse_rsp(struct msg *r) { struct mbuf *b; uint8_t *p, *m; uint8_t ch; enum { SW_START, SW_RSP_NUM, SW_RSP_STR, SW_SPACES_BEFORE_KEY, SW_KEY, SW_SPACES_BEFORE_FLAGS, /* 5 */ SW_FLAGS, SW_SPACES_BEFORE_VLEN, SW_VLEN, SW_RUNTO_VAL, SW_VAL, /* 10 */ SW_VAL_LF, SW_END, SW_RUNTO_CRLF, SW_CRLF, SW_ALMOST_DONE, /* 15 */ SW_SENTINEL } state; state = r->state; b = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(!r->request); ASSERT(!r->redis); ASSERT(state >= SW_START && state < SW_SENTINEL); ASSERT(b != NULL); ASSERT(b->pos <= b->last); /* validate the parsing marker */ ASSERT(r->pos != NULL); ASSERT(r->pos >= b->pos && r->pos <= b->last); for (p = r->pos; p < b->last; p++) { ch = *p; switch (state) { case SW_START: if (isdigit(ch)) { state = SW_RSP_NUM; } else { state = SW_RSP_STR; } p = p - 1; /* go back by 1 byte */ break; case SW_RSP_NUM: if (r->token == NULL) { /* rsp_start <- p; type_start <- p */ r->token = p; } if (isdigit(ch)) { /* num <- num * 10 + (ch - '0') */ ; } else if (ch == ' ' || ch == CR) { /* type_end <- p - 1 */ r->token = NULL; r->type = MSG_RSP_MC_NUM; p = p - 1; /* go back by 1 byte */ state = SW_CRLF; } else { goto error; } break; case SW_RSP_STR: if (r->token == NULL) { /* rsp_start <- p; type_start <- p */ r->token = p; } if (ch == ' ' || ch == CR) { /* type_end <- p - 1 */ m = r->token; /* r->token = NULL; */ r->type = MSG_UNKNOWN; switch (p - m) { case 3: if (str4cmp(m, 'E', 'N', 'D', '\r')) { r->type = MSG_RSP_MC_END; /* end_start <- m; end_end <- p - 1 */ r->end = m; break; } break; case 5: if (str5cmp(m, 'V', 'A', 'L', 'U', 'E')) { /* * Encompasses responses for 'get', 'gets' and * 'cas' command. */ r->type = MSG_RSP_MC_VALUE; break; } if (str5cmp(m, 'E', 'R', 'R', 'O', 'R')) { r->type = MSG_RSP_MC_ERROR; break; } break; case 6: if (str6cmp(m, 'S', 'T', 'O', 'R', 'E', 'D')) { r->type = MSG_RSP_MC_STORED; break; } if (str6cmp(m, 'E', 'X', 'I', 'S', 'T', 'S')) { r->type = MSG_RSP_MC_EXISTS; break; } break; case 7: if (str7cmp(m, 'D', 'E', 'L', 'E', 'T', 'E', 'D')) { r->type = MSG_RSP_MC_DELETED; break; } if (str7cmp(m, 'T', 'O', 'U', 'C', 'H', 'E', 'D')) { r->type = MSG_RSP_MC_TOUCHED; break; } break; case 9: if (str9cmp(m, 'N', 'O', 'T', '_', 'F', 'O', 'U', 'N', 'D')) { r->type = MSG_RSP_MC_NOT_FOUND; break; } break; case 10: if (str10cmp(m, 'N', 'O', 'T', '_', 'S', 'T', 'O', 'R', 'E', 'D')) { r->type = MSG_RSP_MC_NOT_STORED; break; } break; case 12: if (str12cmp(m, 'C', 'L', 'I', 'E', 'N', 'T', '_', 'E', 'R', 'R', 'O', 'R')) { r->type = MSG_RSP_MC_CLIENT_ERROR; break; } if (str12cmp(m, 'S', 'E', 'R', 'V', 'E', 'R', '_', 'E', 'R', 'R', 'O', 'R')) { r->type = MSG_RSP_MC_SERVER_ERROR; break; } break; } switch (r->type) { case MSG_UNKNOWN: goto error; case MSG_RSP_MC_STORED: case MSG_RSP_MC_NOT_STORED: case MSG_RSP_MC_EXISTS: case MSG_RSP_MC_NOT_FOUND: case MSG_RSP_MC_DELETED: case MSG_RSP_MC_TOUCHED: state = SW_CRLF; break; case MSG_RSP_MC_END: state = SW_CRLF; break; case MSG_RSP_MC_VALUE: state = SW_SPACES_BEFORE_KEY; break; case MSG_RSP_MC_ERROR: state = SW_CRLF; break; case MSG_RSP_MC_CLIENT_ERROR: case MSG_RSP_MC_SERVER_ERROR: state = SW_RUNTO_CRLF; break; default: NOT_REACHED(); } p = p - 1; /* go back by 1 byte */ } break; case SW_SPACES_BEFORE_KEY: if (ch != ' ') { state = SW_KEY; p = p - 1; /* go back by 1 byte */ } break; case SW_KEY: if (ch == ' ') { /* r->token = NULL; */ state = SW_SPACES_BEFORE_FLAGS; } break; case SW_SPACES_BEFORE_FLAGS: if (ch != ' ') { if (!isdigit(ch)) { goto error; } state = SW_FLAGS; p = p - 1; /* go back by 1 byte */ } break; case SW_FLAGS: if (r->token == NULL) { /* flags_start <- p */ /* r->token = p; */ } if (isdigit(ch)) { /* flags <- flags * 10 + (ch - '0') */ ; } else if (ch == ' ') { /* flags_end <- p - 1 */ /* r->token = NULL; */ state = SW_SPACES_BEFORE_VLEN; } else { goto error; } break; case SW_SPACES_BEFORE_VLEN: if (ch != ' ') { if (!isdigit(ch)) { goto error; } p = p - 1; /* go back by 1 byte */ state = SW_VLEN; r->vlen = 0; } break; case SW_VLEN: if (isdigit(ch)) { r->vlen = r->vlen * 10 + (uint32_t)(ch - '0'); } else if (ch == ' ' || ch == CR) { /* vlen_end <- p - 1 */ p = p - 1; /* go back by 1 byte */ /* r->token = NULL; */ state = SW_RUNTO_CRLF; } else { goto error; } break; case SW_RUNTO_VAL: switch (ch) { case LF: /* val_start <- p + 1 */ state = SW_VAL; r->token = NULL; break; default: goto error; } break; case SW_VAL: m = p + r->vlen; if (m >= b->last) { ASSERT(r->vlen >= (uint32_t)(b->last - p)); r->vlen -= (uint32_t)(b->last - p); m = b->last - 1; p = m; /* move forward by vlen bytes */ break; } switch (*m) { case CR: /* val_end <- p - 1 */ p = m; /* move forward by vlen bytes */ state = SW_VAL_LF; break; default: goto error; } break; case SW_VAL_LF: switch (ch) { case LF: /* state = SW_END; */ state = SW_RSP_STR; break; default: goto error; } break; case SW_END: if (r->token == NULL) { if (ch != 'E') { goto error; } /* end_start <- p */ r->token = p; } else if (ch == CR) { /* end_end <- p */ m = r->token; r->token = NULL; switch (p - m) { case 3: if (str4cmp(m, 'E', 'N', 'D', '\r')) { r->end = m; state = SW_ALMOST_DONE; } break; default: goto error; } } break; case SW_RUNTO_CRLF: switch (ch) { case CR: if (r->type == MSG_RSP_MC_VALUE) { state = SW_RUNTO_VAL; } else { state = SW_ALMOST_DONE; } break; default: break; } break; case SW_CRLF: switch (ch) { case ' ': break; case CR: state = SW_ALMOST_DONE; break; default: goto error; } break; case SW_ALMOST_DONE: switch (ch) { case LF: /* rsp_end <- p */ goto done; default: goto error; } break; case SW_SENTINEL: default: NOT_REACHED(); break; } } ASSERT(p == b->last); r->pos = p; r->state = state; if (b->last == b->end && r->token != NULL) { if (state <= SW_RUNTO_VAL || state == SW_CRLF || state == SW_ALMOST_DONE) { r->state = SW_START; } r->pos = r->token; r->token = NULL; r->result = MSG_PARSE_REPAIR; } else { r->result = MSG_PARSE_AGAIN; } log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed rsp %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; done: ASSERT(r->type > MSG_UNKNOWN && r->type < MSG_SENTINEL); r->pos = p + 1; ASSERT(r->pos <= b->last); r->state = SW_START; r->token = NULL; r->result = MSG_PARSE_OK; log_hexdump(LOG_VERB, b->pos, mbuf_length(b), "parsed rsp %"PRIu64" res %d " "type %d state %d rpos %d of %d", r->id, r->result, r->type, r->state, r->pos - b->pos, b->last - b->pos); return; error: r->result = MSG_PARSE_ERROR; r->state = state; errno = EINVAL; log_hexdump(LOG_INFO, b->pos, mbuf_length(b), "parsed bad rsp %"PRIu64" " "res %d type %d state %d", r->id, r->result, r->type, r->state); } bool memcache_failure(struct msg *r) { return false; } static rstatus_t memcache_append_key(struct msg *r, uint8_t *key, uint32_t keylen) { struct mbuf *mbuf; struct keypos *kpos; mbuf = msg_ensure_mbuf(r, keylen + 2); if (mbuf == NULL) { return NC_ENOMEM; } kpos = array_push(r->keys); if (kpos == NULL) { return NC_ENOMEM; } kpos->start = mbuf->last; kpos->end = mbuf->last + keylen; mbuf_copy(mbuf, key, keylen); r->mlen += keylen; mbuf_copy(mbuf, (uint8_t *)" ", 1); r->mlen += 1; return NC_OK; } /* * read the comment in proto/nc_redis.c */ static rstatus_t memcache_fragment_retrieval(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq, uint32_t key_step) { struct mbuf *mbuf; struct msg **sub_msgs; uint32_t i; rstatus_t status; sub_msgs = nc_zalloc(ncontinuum * sizeof(*sub_msgs)); if (sub_msgs == NULL) { return NC_ENOMEM; } ASSERT(r->frag_seq == NULL); r->frag_seq = nc_alloc(array_n(r->keys) * sizeof(*r->frag_seq)); if (r->frag_seq == NULL) { nc_free(sub_msgs); return NC_ENOMEM; } mbuf = STAILQ_FIRST(&r->mhdr); mbuf->pos = mbuf->start; /* * This code is based on the assumption that 'gets ' is located * in a contiguous location. * This is always true because we have capped our MBUF_MIN_SIZE at 512 and * whenever we have multiple messages, we copy the tail message into a new mbuf */ for (; *(mbuf->pos) != ' ';) { /* eat get/gets */ mbuf->pos++; } mbuf->pos++; r->frag_id = msg_gen_frag_id(); r->nfrag = 0; r->frag_owner = r; for (i = 0; i < array_n(r->keys); i++) { /* for each key */ struct msg *sub_msg; struct keypos *kpos = array_get(r->keys, i); uint32_t idx = msg_backend_idx(r, kpos->start, kpos->end - kpos->start); if (sub_msgs[idx] == NULL) { sub_msgs[idx] = msg_get(r->owner, r->request, r->redis); if (sub_msgs[idx] == NULL) { nc_free(sub_msgs); return NC_ENOMEM; } } r->frag_seq[i] = sub_msg = sub_msgs[idx]; sub_msg->narg++; status = memcache_append_key(sub_msg, kpos->start, kpos->end - kpos->start); if (status != NC_OK) { nc_free(sub_msgs); return status; } } for (i = 0; i < ncontinuum; i++) { /* prepend mget header, and forward it */ struct msg *sub_msg = sub_msgs[i]; if (sub_msg == NULL) { continue; } /* prepend get/gets */ if (r->type == MSG_REQ_MC_GET) { status = msg_prepend(sub_msg, (uint8_t *)"get ", 4); } else if (r->type == MSG_REQ_MC_GETS) { status = msg_prepend(sub_msg, (uint8_t *)"gets ", 5); } if (status != NC_OK) { nc_free(sub_msgs); return status; } /* append \r\n */ status = msg_append(sub_msg, (uint8_t *)CRLF, CRLF_LEN); if (status != NC_OK) { nc_free(sub_msgs); return status; } sub_msg->type = r->type; sub_msg->frag_id = r->frag_id; sub_msg->frag_owner = r->frag_owner; TAILQ_INSERT_TAIL(frag_msgq, sub_msg, m_tqe); r->nfrag++; } nc_free(sub_msgs); return NC_OK; } rstatus_t memcache_fragment(struct msg *r, uint32_t ncontinuum, struct msg_tqh *frag_msgq) { if (memcache_retrieval(r)) { return memcache_fragment_retrieval(r, ncontinuum, frag_msgq, 1); } return NC_OK; } /* * Pre-coalesce handler is invoked when the message is a response to * the fragmented multi vector request - 'get' or 'gets' and all the * responses to the fragmented request vector hasn't been received */ void memcache_pre_coalesce(struct msg *r) { struct msg *pr = r->peer; /* peer request */ struct mbuf *mbuf; ASSERT(!r->request); ASSERT(pr->request); if (pr->frag_id == 0) { /* do nothing, if not a response to a fragmented request */ return; } pr->frag_owner->nfrag_done++; switch (r->type) { case MSG_RSP_MC_VALUE: case MSG_RSP_MC_END: /* * Readjust responses of the fragmented message vector by not * including the end marker for all */ ASSERT(r->end != NULL); for (;;) { mbuf = STAILQ_LAST(&r->mhdr, mbuf, next); ASSERT(mbuf != NULL); /* * We cannot assert that end marker points to the last mbuf * Consider a scenario where end marker points to the * penultimate mbuf and the last mbuf only contains spaces * and CRLF: mhdr -> [...END] -> [\r\n] */ if (r->end >= mbuf->pos && r->end < mbuf->last) { /* end marker is within this mbuf */ r->mlen -= (uint32_t)(mbuf->last - r->end); mbuf->last = r->end; break; } /* end marker is not in this mbuf */ r->mlen -= mbuf_length(mbuf); mbuf_remove(&r->mhdr, mbuf); mbuf_put(mbuf); } break; default: /* * Valid responses for a fragmented requests are MSG_RSP_MC_VALUE or, * MSG_RSP_MC_END. For an invalid response, we send out SERVER_ERRROR * with EINVAL errno */ mbuf = STAILQ_FIRST(&r->mhdr); log_hexdump(LOG_ERR, mbuf->pos, mbuf_length(mbuf), "rsp fragment " "with unknown type %d", r->type); pr->error = 1; pr->err = EINVAL; break; } } /* * Copy one response from src to dst and return bytes copied */ static rstatus_t memcache_copy_bulk(struct msg *dst, struct msg *src) { struct mbuf *mbuf, *nbuf; uint8_t *p; uint32_t len = 0; uint32_t bytes = 0; uint32_t i = 0; for (mbuf = STAILQ_FIRST(&src->mhdr); mbuf && mbuf_empty(mbuf); mbuf = STAILQ_FIRST(&src->mhdr)) { mbuf_remove(&src->mhdr, mbuf); mbuf_put(mbuf); } mbuf = STAILQ_FIRST(&src->mhdr); if (mbuf == NULL) { return NC_OK; /* key not exists */ } p = mbuf->pos; /* * get : VALUE key 0 len\r\nval\r\n * gets: VALUE key 0 len cas\r\nval\r\n */ ASSERT(*p == 'V'); for (i = 0; i < 3; i++) { /* eat 'VALUE key 0 ' */ for (; *p != ' ';) { p++; } p++; } len = 0; for (; p < mbuf->last && isdigit(*p); p++) { len = len * 10 + (uint32_t)(*p - '0'); } for (; p < mbuf->last && ('\r' != *p); p++) { /* eat cas for gets */ ; } len += CRLF_LEN * 2; len += (p - mbuf->pos); bytes = len; /* copy len bytes to dst */ for (; mbuf;) { if (mbuf_length(mbuf) <= len) { /* steal this mbuf from src to dst */ nbuf = STAILQ_NEXT(mbuf, next); mbuf_remove(&src->mhdr, mbuf); mbuf_insert(&dst->mhdr, mbuf); len -= mbuf_length(mbuf); mbuf = nbuf; } else { /* split it */ nbuf = mbuf_get(); if (nbuf == NULL) { return NC_ENOMEM; } mbuf_copy(nbuf, mbuf->pos, len); mbuf_insert(&dst->mhdr, nbuf); mbuf->pos += len; break; } } dst->mlen += bytes; src->mlen -= bytes; log_debug(LOG_VVERB, "memcache_copy_bulk copy bytes: %d", bytes); return NC_OK; } /* * Post-coalesce handler is invoked when the message is a response to * the fragmented multi vector request - 'get' or 'gets' and all the * responses to the fragmented request vector has been received and * the fragmented request is consider to be done */ void memcache_post_coalesce(struct msg *request) { struct msg *response = request->peer; struct msg *sub_msg; uint32_t i; rstatus_t status; ASSERT(!response->request); ASSERT(request->request && (request->frag_owner == request)); if (request->error || request->ferror) { response->owner->err = 1; return; } for (i = 0; i < array_n(request->keys); i++) { /* for each key */ sub_msg = request->frag_seq[i]->peer; /* get it's peer response */ if (sub_msg == NULL) { response->owner->err = 1; return; } status = memcache_copy_bulk(response, sub_msg); if (status != NC_OK) { response->owner->err = 1; return; } } /* append END\r\n */ status = msg_append(response, (uint8_t *)"END\r\n", 5); if (status != NC_OK) { response->owner->err = 1; return; } } void memcache_post_connect(struct context *ctx, struct conn *conn, struct server *server) { } void memcache_swallow_msg(struct conn *conn, struct msg *pmsg, struct msg *msg) { } rstatus_t memcache_add_auth(struct context *ctx, struct conn *c_conn, struct conn *s_conn) { NOT_REACHED(); return NC_OK; } rstatus_t memcache_reply(struct msg *r) { NOT_REACHED(); return NC_OK; } nutcracker-0.4.1+dfsg/src/proto/Makefile.am0000664000000000000000000000041512542173515017267 0ustar rootrootMAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I $(top_srcdir)/src AM_CFLAGS = -Wall -Wshadow AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value noinst_LIBRARIES = libproto.a noinst_HEADERS = nc_proto.h libproto_a_SOURCES = \ nc_memcache.c \ nc_redis.c nutcracker-0.4.1+dfsg/src/nc_array.h0000664000000000000000000000374312542173515016046 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_ARRAY_H_ #define _NC_ARRAY_H_ #include typedef int (*array_compare_t)(const void *, const void *); typedef rstatus_t (*array_each_t)(void *, void *); struct array { uint32_t nelem; /* # element */ void *elem; /* element */ size_t size; /* element size */ uint32_t nalloc; /* # allocated element */ }; #define null_array { 0, NULL, 0, 0 } static inline void array_null(struct array *a) { a->nelem = 0; a->elem = NULL; a->size = 0; a->nalloc = 0; } static inline void array_set(struct array *a, void *elem, size_t size, uint32_t nalloc) { a->nelem = 0; a->elem = elem; a->size = size; a->nalloc = nalloc; } static inline uint32_t array_n(const struct array *a) { return a->nelem; } struct array *array_create(uint32_t n, size_t size); void array_destroy(struct array *a); rstatus_t array_init(struct array *a, uint32_t n, size_t size); void array_deinit(struct array *a); uint32_t array_idx(struct array *a, void *elem); void *array_push(struct array *a); void *array_pop(struct array *a); void *array_get(struct array *a, uint32_t idx); void *array_top(struct array *a); void array_swap(struct array *a, struct array *b); void array_sort(struct array *a, array_compare_t compare); rstatus_t array_each(struct array *a, array_each_t func, void *data); #endif nutcracker-0.4.1+dfsg/src/nc_util.h0000664000000000000000000001465612542173515015712 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_UTIL_H_ #define _NC_UTIL_H_ #include #define LF (uint8_t) 10 #define CR (uint8_t) 13 #define CRLF "\x0d\x0a" #define CRLF_LEN (sizeof("\x0d\x0a") - 1) #define NELEMS(a) ((sizeof(a)) / sizeof((a)[0])) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define SQUARE(d) ((d) * (d)) #define VAR(s, s2, n) (((n) < 2) ? 0.0 : ((s2) - SQUARE(s)/(n)) / ((n) - 1)) #define STDDEV(s, s2, n) (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n)))) #define NC_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1) #define NC_INET6_ADDRSTRLEN \ (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1) #define NC_INET_ADDRSTRLEN MAX(NC_INET4_ADDRSTRLEN, NC_INET6_ADDRSTRLEN) #define NC_UNIX_ADDRSTRLEN \ (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) #define NC_MAXHOSTNAMELEN 256 /* * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral * type (uintmax_t) in ascii, including the null terminator '\0' * * From stdint.h, we have: * # define UINT8_MAX (255) * # define UINT16_MAX (65535) * # define UINT32_MAX (4294967295U) * # define UINT64_MAX (__UINT64_C(18446744073709551615)) */ #define NC_UINT8_MAXLEN (3 + 1) #define NC_UINT16_MAXLEN (5 + 1) #define NC_UINT32_MAXLEN (10 + 1) #define NC_UINT64_MAXLEN (20 + 1) #define NC_UINTMAX_MAXLEN NC_UINT64_MAXLEN /* * Make data 'd' or pointer 'p', n-byte aligned, where n is a power of 2 * of 2. */ #define NC_ALIGNMENT sizeof(unsigned long) /* platform word */ #define NC_ALIGN(d, n) (((d) + (n - 1)) & ~(n - 1)) #define NC_ALIGN_PTR(p, n) \ (void *) (((uintptr_t) (p) + ((uintptr_t) n - 1)) & ~((uintptr_t) n - 1)) /* * Wrapper to workaround well known, safe, implicit type conversion when * invoking system calls. */ #define nc_gethostname(_name, _len) \ gethostname((char *)_name, (size_t)_len) #define nc_atoi(_line, _n) \ _nc_atoi((uint8_t *)_line, (size_t)_n) int nc_set_blocking(int sd); int nc_set_nonblocking(int sd); int nc_set_reuseaddr(int sd); int nc_set_tcpnodelay(int sd); int nc_set_linger(int sd, int timeout); int nc_set_sndbuf(int sd, int size); int nc_set_rcvbuf(int sd, int size); int nc_get_soerror(int sd); int nc_get_sndbuf(int sd); int nc_get_rcvbuf(int sd); int _nc_atoi(uint8_t *line, size_t n); bool nc_valid_port(int n); /* * Memory allocation and free wrappers. * * These wrappers enables us to loosely detect double free, dangling * pointer access and zero-byte alloc. */ #define nc_alloc(_s) \ _nc_alloc((size_t)(_s), __FILE__, __LINE__) #define nc_zalloc(_s) \ _nc_zalloc((size_t)(_s), __FILE__, __LINE__) #define nc_calloc(_n, _s) \ _nc_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__) #define nc_realloc(_p, _s) \ _nc_realloc(_p, (size_t)(_s), __FILE__, __LINE__) #define nc_free(_p) do { \ _nc_free(_p, __FILE__, __LINE__); \ (_p) = NULL; \ } while (0) void *_nc_alloc(size_t size, const char *name, int line); void *_nc_zalloc(size_t size, const char *name, int line); void *_nc_calloc(size_t nmemb, size_t size, const char *name, int line); void *_nc_realloc(void *ptr, size_t size, const char *name, int line); void _nc_free(void *ptr, const char *name, int line); /* * Wrappers to send or receive n byte message on a blocking * socket descriptor. */ #define nc_sendn(_s, _b, _n) \ _nc_sendn(_s, _b, (size_t)(_n)) #define nc_recvn(_s, _b, _n) \ _nc_recvn(_s, _b, (size_t)(_n)) /* * Wrappers to read or write data to/from (multiple) buffers * to a file or socket descriptor. */ #define nc_read(_d, _b, _n) \ read(_d, _b, (size_t)(_n)) #define nc_readv(_d, _b, _n) \ readv(_d, _b, (int)(_n)) #define nc_write(_d, _b, _n) \ write(_d, _b, (size_t)(_n)) #define nc_writev(_d, _b, _n) \ writev(_d, _b, (int)(_n)) ssize_t _nc_sendn(int sd, const void *vptr, size_t n); ssize_t _nc_recvn(int sd, void *vptr, size_t n); /* * Wrappers for defining custom assert based on whether macro * NC_ASSERT_PANIC or NC_ASSERT_LOG was defined at the moment * ASSERT was called. */ #ifdef NC_ASSERT_PANIC #define ASSERT(_x) do { \ if (!(_x)) { \ nc_assert(#_x, __FILE__, __LINE__, 1); \ } \ } while (0) #define NOT_REACHED() ASSERT(0) #elif NC_ASSERT_LOG #define ASSERT(_x) do { \ if (!(_x)) { \ nc_assert(#_x, __FILE__, __LINE__, 0); \ } \ } while (0) #define NOT_REACHED() ASSERT(0) #else #define ASSERT(_x) #define NOT_REACHED() #endif void nc_assert(const char *cond, const char *file, int line, int panic); void nc_stacktrace(int skip_count); void nc_stacktrace_fd(int fd); int _scnprintf(char *buf, size_t size, const char *fmt, ...); int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args); int64_t nc_usec_now(void); int64_t nc_msec_now(void); /* * Address resolution for internet (ipv4 and ipv6) and unix domain * socket address. */ struct sockinfo { int family; /* socket address family */ socklen_t addrlen; /* socket address length */ union { struct sockaddr_in in; /* ipv4 socket address */ struct sockaddr_in6 in6; /* ipv6 socket address */ struct sockaddr_un un; /* unix domain address */ } addr; }; int nc_resolve(struct string *name, int port, struct sockinfo *si); char *nc_unresolve_addr(struct sockaddr *addr, socklen_t addrlen); char *nc_unresolve_peer_desc(int sd); char *nc_unresolve_desc(int sd); #endif nutcracker-0.4.1+dfsg/src/nc_mbuf.c0000664000000000000000000001520412542173515015647 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include static uint32_t nfree_mbufq; /* # free mbuf */ static struct mhdr free_mbufq; /* free mbuf q */ static size_t mbuf_chunk_size; /* mbuf chunk size - header + data (const) */ static size_t mbuf_offset; /* mbuf offset in chunk (const) */ static struct mbuf * _mbuf_get(void) { struct mbuf *mbuf; uint8_t *buf; if (!STAILQ_EMPTY(&free_mbufq)) { ASSERT(nfree_mbufq > 0); mbuf = STAILQ_FIRST(&free_mbufq); nfree_mbufq--; STAILQ_REMOVE_HEAD(&free_mbufq, next); ASSERT(mbuf->magic == MBUF_MAGIC); goto done; } buf = nc_alloc(mbuf_chunk_size); if (buf == NULL) { return NULL; } /* * mbuf header is at the tail end of the mbuf. This enables us to catch * buffer overrun early by asserting on the magic value during get or * put operations * * <------------- mbuf_chunk_size -------------> * +-------------------------------------------+ * | mbuf data | mbuf header | * | (mbuf_offset) | (struct mbuf) | * +-------------------------------------------+ * ^ ^ ^ ^^ * | | | || * \ | | |\ * mbuf->start \ | | mbuf->end (one byte past valid bound) * mbuf->pos \ * \ mbuf * mbuf->last (one byte past valid byte) * */ mbuf = (struct mbuf *)(buf + mbuf_offset); mbuf->magic = MBUF_MAGIC; done: STAILQ_NEXT(mbuf, next) = NULL; return mbuf; } struct mbuf * mbuf_get(void) { struct mbuf *mbuf; uint8_t *buf; mbuf = _mbuf_get(); if (mbuf == NULL) { return NULL; } buf = (uint8_t *)mbuf - mbuf_offset; mbuf->start = buf; mbuf->end = buf + mbuf_offset; ASSERT(mbuf->end - mbuf->start == (int)mbuf_offset); ASSERT(mbuf->start < mbuf->end); mbuf->pos = mbuf->start; mbuf->last = mbuf->start; log_debug(LOG_VVERB, "get mbuf %p", mbuf); return mbuf; } static void mbuf_free(struct mbuf *mbuf) { uint8_t *buf; log_debug(LOG_VVERB, "put mbuf %p len %d", mbuf, mbuf->last - mbuf->pos); ASSERT(STAILQ_NEXT(mbuf, next) == NULL); ASSERT(mbuf->magic == MBUF_MAGIC); buf = (uint8_t *)mbuf - mbuf_offset; nc_free(buf); } void mbuf_put(struct mbuf *mbuf) { log_debug(LOG_VVERB, "put mbuf %p len %d", mbuf, mbuf->last - mbuf->pos); ASSERT(STAILQ_NEXT(mbuf, next) == NULL); ASSERT(mbuf->magic == MBUF_MAGIC); nfree_mbufq++; STAILQ_INSERT_HEAD(&free_mbufq, mbuf, next); } /* * Rewind the mbuf by discarding any of the read or unread data that it * might hold. */ void mbuf_rewind(struct mbuf *mbuf) { mbuf->pos = mbuf->start; mbuf->last = mbuf->start; } /* * Return the length of data in mbuf. Mbuf cannot contain more than * 2^32 bytes (4G). */ uint32_t mbuf_length(struct mbuf *mbuf) { ASSERT(mbuf->last >= mbuf->pos); return (uint32_t)(mbuf->last - mbuf->pos); } /* * Return the remaining space size for any new data in mbuf. Mbuf cannot * contain more than 2^32 bytes (4G). */ uint32_t mbuf_size(struct mbuf *mbuf) { ASSERT(mbuf->end >= mbuf->last); return (uint32_t)(mbuf->end - mbuf->last); } /* * Return the maximum available space size for data in any mbuf. Mbuf cannot * contain more than 2^32 bytes (4G). */ size_t mbuf_data_size(void) { return mbuf_offset; } /* * Insert mbuf at the tail of the mhdr Q */ void mbuf_insert(struct mhdr *mhdr, struct mbuf *mbuf) { STAILQ_INSERT_TAIL(mhdr, mbuf, next); log_debug(LOG_VVERB, "insert mbuf %p len %d", mbuf, mbuf->last - mbuf->pos); } /* * Remove mbuf from the mhdr Q */ void mbuf_remove(struct mhdr *mhdr, struct mbuf *mbuf) { log_debug(LOG_VVERB, "remove mbuf %p len %d", mbuf, mbuf->last - mbuf->pos); STAILQ_REMOVE(mhdr, mbuf, mbuf, next); STAILQ_NEXT(mbuf, next) = NULL; } /* * Copy n bytes from memory area pos to mbuf. * * The memory areas should not overlap and the mbuf should have * enough space for n bytes. */ void mbuf_copy(struct mbuf *mbuf, uint8_t *pos, size_t n) { if (n == 0) { return; } /* mbuf has space for n bytes */ ASSERT(!mbuf_full(mbuf) && n <= mbuf_size(mbuf)); /* no overlapping copy */ ASSERT(pos < mbuf->start || pos >= mbuf->end); nc_memcpy(mbuf->last, pos, n); mbuf->last += n; } /* * Split mbuf h into h and t by copying data from h to t. Before * the copy, we invoke a precopy handler cb that will copy a predefined * string to the head of t. * * Return new mbuf t, if the split was successful. */ struct mbuf * mbuf_split(struct mhdr *h, uint8_t *pos, mbuf_copy_t cb, void *cbarg) { struct mbuf *mbuf, *nbuf; size_t size; ASSERT(!STAILQ_EMPTY(h)); mbuf = STAILQ_LAST(h, mbuf, next); ASSERT(pos >= mbuf->pos && pos <= mbuf->last); nbuf = mbuf_get(); if (nbuf == NULL) { return NULL; } if (cb != NULL) { /* precopy nbuf */ cb(nbuf, cbarg); } /* copy data from mbuf to nbuf */ size = (size_t)(mbuf->last - pos); mbuf_copy(nbuf, pos, size); /* adjust mbuf */ mbuf->last = pos; log_debug(LOG_VVERB, "split into mbuf %p len %"PRIu32" and nbuf %p len " "%"PRIu32" copied %zu bytes", mbuf, mbuf_length(mbuf), nbuf, mbuf_length(nbuf), size); return nbuf; } void mbuf_init(struct instance *nci) { nfree_mbufq = 0; STAILQ_INIT(&free_mbufq); mbuf_chunk_size = nci->mbuf_chunk_size; mbuf_offset = mbuf_chunk_size - MBUF_HSIZE; log_debug(LOG_DEBUG, "mbuf hsize %d chunk size %zu offset %zu length %zu", MBUF_HSIZE, mbuf_chunk_size, mbuf_offset, mbuf_offset); } void mbuf_deinit(void) { while (!STAILQ_EMPTY(&free_mbufq)) { struct mbuf *mbuf = STAILQ_FIRST(&free_mbufq); mbuf_remove(&free_mbufq, mbuf); mbuf_free(mbuf); nfree_mbufq--; } ASSERT(nfree_mbufq == 0); } nutcracker-0.4.1+dfsg/src/nc_message.c0000664000000000000000000005315312542173515016347 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #if (IOV_MAX > 128) #define NC_IOV_MAX 128 #else #define NC_IOV_MAX IOV_MAX #endif /* * nc_message.[ch] * message (struct msg) * + + . * | | . * / \ . * Request Response .../ nc_mbuf.[ch] (mesage buffers) * nc_request.c nc_response.c .../ nc_memcache.c; nc_redis.c (message parser) * * Messages in nutcracker are manipulated by a chain of processing handlers, * where each handler is responsible for taking the input and producing an * output for the next handler in the chain. This mechanism of processing * loosely conforms to the standard chain-of-responsibility design pattern * * At the high level, each handler takes in a message: request or response * and produces the message for the next handler in the chain. The input * for a handler is either a request or response, but never both and * similarly the output of an handler is either a request or response or * nothing. * * Each handler itself is composed of two processing units: * * 1). filter: manipulates output produced by the handler, usually based * on a policy. If needed, multiple filters can be hooked into each * location. * 2). forwarder: chooses one of the backend servers to send the request * to, usually based on the configured distribution and key hasher. * * Handlers are registered either with Client or Server or Proxy * connections. A Proxy connection only has a read handler as it is only * responsible for accepting new connections from client. Read handler * (conn_recv_t) registered with client is responsible for reading requests, * while that registered with server is responsible for reading responses. * Write handler (conn_send_t) registered with client is responsible for * writing response, while that registered with server is responsible for * writing requests. * * Note that in the above discussion, the terminology send is used * synonymously with write or OUT event. Similarly recv is used synonymously * with read or IN event * * Client+ Proxy Server+ * (nutcracker) * . * msg_recv {read event} . msg_recv {read event} * + . + * | . | * \ . / * req_recv_next . rsp_recv_next * + . + * | . | Rsp * req_recv_done . rsp_recv_done <=== * + . + * | . | * Req \ . / * ===> req_filter* . *rsp_filter * + . + * | . | * \ . / * req_forward-// (a) . (c) \\-rsp_forward * . * . * msg_send {write event} . msg_send {write event} * + . + * | . | * Rsp' \ . / Req' * <=== rsp_send_next . req_send_next ===> * + . + * | . | * \ . / * rsp_send_done-// (d) . (b) //-req_send_done * * * (a) -> (b) -> (c) -> (d) is the normal flow of transaction consisting * of a single request response, where (a) and (b) handle request from * client, while (c) and (d) handle the corresponding response from the * server. */ static uint64_t msg_id; /* message id counter */ static uint64_t frag_id; /* fragment id counter */ static uint32_t nfree_msgq; /* # free msg q */ static struct msg_tqh free_msgq; /* free msg q */ static struct rbtree tmo_rbt; /* timeout rbtree */ static struct rbnode tmo_rbs; /* timeout rbtree sentinel */ #define DEFINE_ACTION(_name) string(#_name), static struct string msg_type_strings[] = { MSG_TYPE_CODEC( DEFINE_ACTION ) null_string }; #undef DEFINE_ACTION static struct msg * msg_from_rbe(struct rbnode *node) { struct msg *msg; int offset; offset = offsetof(struct msg, tmo_rbe); msg = (struct msg *)((char *)node - offset); return msg; } struct msg * msg_tmo_min(void) { struct rbnode *node; node = rbtree_min(&tmo_rbt); if (node == NULL) { return NULL; } return msg_from_rbe(node); } void msg_tmo_insert(struct msg *msg, struct conn *conn) { struct rbnode *node; int timeout; ASSERT(msg->request); ASSERT(!msg->quit && !msg->noreply); timeout = server_timeout(conn); if (timeout <= 0) { return; } node = &msg->tmo_rbe; node->key = nc_msec_now() + timeout; node->data = conn; rbtree_insert(&tmo_rbt, node); log_debug(LOG_VERB, "insert msg %"PRIu64" into tmo rbt with expiry of " "%d msec", msg->id, timeout); } void msg_tmo_delete(struct msg *msg) { struct rbnode *node; node = &msg->tmo_rbe; /* already deleted */ if (node->data == NULL) { return; } rbtree_delete(&tmo_rbt, node); log_debug(LOG_VERB, "delete msg %"PRIu64" from tmo rbt", msg->id); } static struct msg * _msg_get(void) { struct msg *msg; if (!TAILQ_EMPTY(&free_msgq)) { ASSERT(nfree_msgq > 0); msg = TAILQ_FIRST(&free_msgq); nfree_msgq--; TAILQ_REMOVE(&free_msgq, msg, m_tqe); goto done; } msg = nc_alloc(sizeof(*msg)); if (msg == NULL) { return NULL; } done: /* c_tqe, s_tqe, and m_tqe are left uninitialized */ msg->id = ++msg_id; msg->peer = NULL; msg->owner = NULL; rbtree_node_init(&msg->tmo_rbe); STAILQ_INIT(&msg->mhdr); msg->mlen = 0; msg->start_ts = 0; msg->state = 0; msg->pos = NULL; msg->token = NULL; msg->parser = NULL; msg->add_auth = NULL; msg->result = MSG_PARSE_OK; msg->fragment = NULL; msg->reply = NULL; msg->pre_coalesce = NULL; msg->post_coalesce = NULL; msg->type = MSG_UNKNOWN; msg->keys = array_create(1, sizeof(struct keypos)); if (msg->keys == NULL) { nc_free(msg); return NULL; } msg->vlen = 0; msg->end = NULL; msg->frag_owner = NULL; msg->frag_seq = NULL; msg->nfrag = 0; msg->nfrag_done = 0; msg->frag_id = 0; msg->narg_start = NULL; msg->narg_end = NULL; msg->narg = 0; msg->rnarg = 0; msg->rlen = 0; msg->integer = 0; msg->err = 0; msg->error = 0; msg->ferror = 0; msg->request = 0; msg->quit = 0; msg->noreply = 0; msg->noforward = 0; msg->done = 0; msg->fdone = 0; msg->swallow = 0; msg->redis = 0; return msg; } struct msg * msg_get(struct conn *conn, bool request, bool redis) { struct msg *msg; msg = _msg_get(); if (msg == NULL) { return NULL; } msg->owner = conn; msg->request = request ? 1 : 0; msg->redis = redis ? 1 : 0; if (redis) { if (request) { msg->parser = redis_parse_req; } else { msg->parser = redis_parse_rsp; } msg->add_auth = redis_add_auth; msg->fragment = redis_fragment; msg->reply = redis_reply; msg->failure = redis_failure; msg->pre_coalesce = redis_pre_coalesce; msg->post_coalesce = redis_post_coalesce; } else { if (request) { msg->parser = memcache_parse_req; } else { msg->parser = memcache_parse_rsp; } msg->add_auth = memcache_add_auth; msg->fragment = memcache_fragment; msg->failure = memcache_failure; msg->pre_coalesce = memcache_pre_coalesce; msg->post_coalesce = memcache_post_coalesce; } if (log_loggable(LOG_NOTICE) != 0) { msg->start_ts = nc_usec_now(); } log_debug(LOG_VVERB, "get msg %p id %"PRIu64" request %d owner sd %d", msg, msg->id, msg->request, conn->sd); return msg; } struct msg * msg_get_error(bool redis, err_t err) { struct msg *msg; struct mbuf *mbuf; int n; char *errstr = err ? strerror(err) : "unknown"; char *protstr = redis ? "-ERR" : "SERVER_ERROR"; msg = _msg_get(); if (msg == NULL) { return NULL; } msg->state = 0; msg->type = MSG_RSP_MC_SERVER_ERROR; mbuf = mbuf_get(); if (mbuf == NULL) { msg_put(msg); return NULL; } mbuf_insert(&msg->mhdr, mbuf); n = nc_scnprintf(mbuf->last, mbuf_size(mbuf), "%s %s"CRLF, protstr, errstr); mbuf->last += n; msg->mlen = (uint32_t)n; log_debug(LOG_VVERB, "get msg %p id %"PRIu64" len %"PRIu32" error '%s'", msg, msg->id, msg->mlen, errstr); return msg; } static void msg_free(struct msg *msg) { ASSERT(STAILQ_EMPTY(&msg->mhdr)); log_debug(LOG_VVERB, "free msg %p id %"PRIu64"", msg, msg->id); nc_free(msg); } void msg_put(struct msg *msg) { log_debug(LOG_VVERB, "put msg %p id %"PRIu64"", msg, msg->id); while (!STAILQ_EMPTY(&msg->mhdr)) { struct mbuf *mbuf = STAILQ_FIRST(&msg->mhdr); mbuf_remove(&msg->mhdr, mbuf); mbuf_put(mbuf); } if (msg->frag_seq) { nc_free(msg->frag_seq); msg->frag_seq = NULL; } if (msg->keys) { msg->keys->nelem = 0; /* a hack here */ array_destroy(msg->keys); msg->keys = NULL; } nfree_msgq++; TAILQ_INSERT_HEAD(&free_msgq, msg, m_tqe); } void msg_dump(struct msg *msg, int level) { struct mbuf *mbuf; if (log_loggable(level) == 0) { return; } loga("msg dump id %"PRIu64" request %d len %"PRIu32" type %d done %d " "error %d (err %d)", msg->id, msg->request, msg->mlen, msg->type, msg->done, msg->error, msg->err); STAILQ_FOREACH(mbuf, &msg->mhdr, next) { uint8_t *p, *q; long int len; p = mbuf->start; q = mbuf->last; len = q - p; loga_hexdump(p, len, "mbuf [%p] with %ld bytes of data", p, len); } } void msg_init(void) { log_debug(LOG_DEBUG, "msg size %d", sizeof(struct msg)); msg_id = 0; frag_id = 0; nfree_msgq = 0; TAILQ_INIT(&free_msgq); rbtree_init(&tmo_rbt, &tmo_rbs); } void msg_deinit(void) { struct msg *msg, *nmsg; for (msg = TAILQ_FIRST(&free_msgq); msg != NULL; msg = nmsg, nfree_msgq--) { ASSERT(nfree_msgq > 0); nmsg = TAILQ_NEXT(msg, m_tqe); msg_free(msg); } ASSERT(nfree_msgq == 0); } struct string * msg_type_string(msg_type_t type) { return &msg_type_strings[type]; } bool msg_empty(struct msg *msg) { return msg->mlen == 0 ? true : false; } uint32_t msg_backend_idx(struct msg *msg, uint8_t *key, uint32_t keylen) { struct conn *conn = msg->owner; struct server_pool *pool = conn->owner; return server_pool_idx(pool, key, keylen); } struct mbuf * msg_ensure_mbuf(struct msg *msg, size_t len) { struct mbuf *mbuf; if (STAILQ_EMPTY(&msg->mhdr) || mbuf_size(STAILQ_LAST(&msg->mhdr, mbuf, next)) < len) { mbuf = mbuf_get(); if (mbuf == NULL) { return NULL; } mbuf_insert(&msg->mhdr, mbuf); } else { mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next); } return mbuf; } /* * Append n bytes of data, with n <= mbuf_size(mbuf) * into mbuf */ rstatus_t msg_append(struct msg *msg, uint8_t *pos, size_t n) { struct mbuf *mbuf; ASSERT(n <= mbuf_data_size()); mbuf = msg_ensure_mbuf(msg, n); if (mbuf == NULL) { return NC_ENOMEM; } ASSERT(n <= mbuf_size(mbuf)); mbuf_copy(mbuf, pos, n); msg->mlen += (uint32_t)n; return NC_OK; } /* * Prepend n bytes of data, with n <= mbuf_size(mbuf) * into mbuf */ rstatus_t msg_prepend(struct msg *msg, uint8_t *pos, size_t n) { struct mbuf *mbuf; mbuf = mbuf_get(); if (mbuf == NULL) { return NC_ENOMEM; } ASSERT(n <= mbuf_size(mbuf)); mbuf_copy(mbuf, pos, n); msg->mlen += (uint32_t)n; STAILQ_INSERT_HEAD(&msg->mhdr, mbuf, next); return NC_OK; } /* * Prepend a formatted string into msg. Returns an error if the formatted * string does not fit in a single mbuf. */ rstatus_t msg_prepend_format(struct msg *msg, const char *fmt, ...) { struct mbuf *mbuf; int n; uint32_t size; va_list args; mbuf = mbuf_get(); if (mbuf == NULL) { return NC_ENOMEM; } size = mbuf_size(mbuf); va_start(args, fmt); n = nc_vsnprintf(mbuf->last, size, fmt, args); va_end(args); if (n <= 0 || n >= (int)size) { return NC_ERROR; } mbuf->last += n; msg->mlen += (uint32_t)n; STAILQ_INSERT_HEAD(&msg->mhdr, mbuf, next); return NC_OK; } inline uint64_t msg_gen_frag_id(void) { return ++frag_id; } static rstatus_t msg_parsed(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg *nmsg; struct mbuf *mbuf, *nbuf; mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next); if (msg->pos == mbuf->last) { /* no more data to parse */ conn->recv_done(ctx, conn, msg, NULL); return NC_OK; } /* * Input mbuf has un-parsed data. Split mbuf of the current message msg * into (mbuf, nbuf), where mbuf is the portion of the message that has * been parsed and nbuf is the portion of the message that is un-parsed. * Parse nbuf as a new message nmsg in the next iteration. */ nbuf = mbuf_split(&msg->mhdr, msg->pos, NULL, NULL); if (nbuf == NULL) { return NC_ENOMEM; } nmsg = msg_get(msg->owner, msg->request, conn->redis); if (nmsg == NULL) { mbuf_put(nbuf); return NC_ENOMEM; } mbuf_insert(&nmsg->mhdr, nbuf); nmsg->pos = nbuf->pos; /* update length of current (msg) and new message (nmsg) */ nmsg->mlen = mbuf_length(nbuf); msg->mlen -= nmsg->mlen; conn->recv_done(ctx, conn, msg, nmsg); return NC_OK; } static rstatus_t msg_repair(struct context *ctx, struct conn *conn, struct msg *msg) { struct mbuf *nbuf; nbuf = mbuf_split(&msg->mhdr, msg->pos, NULL, NULL); if (nbuf == NULL) { return NC_ENOMEM; } mbuf_insert(&msg->mhdr, nbuf); msg->pos = nbuf->pos; return NC_OK; } static rstatus_t msg_parse(struct context *ctx, struct conn *conn, struct msg *msg) { rstatus_t status; if (msg_empty(msg)) { /* no data to parse */ conn->recv_done(ctx, conn, msg, NULL); return NC_OK; } msg->parser(msg); switch (msg->result) { case MSG_PARSE_OK: status = msg_parsed(ctx, conn, msg); break; case MSG_PARSE_REPAIR: status = msg_repair(ctx, conn, msg); break; case MSG_PARSE_AGAIN: status = NC_OK; break; default: status = NC_ERROR; conn->err = errno; break; } return conn->err != 0 ? NC_ERROR : status; } static rstatus_t msg_recv_chain(struct context *ctx, struct conn *conn, struct msg *msg) { rstatus_t status; struct msg *nmsg; struct mbuf *mbuf; size_t msize; ssize_t n; mbuf = STAILQ_LAST(&msg->mhdr, mbuf, next); if (mbuf == NULL || mbuf_full(mbuf)) { mbuf = mbuf_get(); if (mbuf == NULL) { return NC_ENOMEM; } mbuf_insert(&msg->mhdr, mbuf); msg->pos = mbuf->pos; } ASSERT(mbuf->end - mbuf->last > 0); msize = mbuf_size(mbuf); n = conn_recv(conn, mbuf->last, msize); if (n < 0) { if (n == NC_EAGAIN) { return NC_OK; } return NC_ERROR; } ASSERT((mbuf->last + n) <= mbuf->end); mbuf->last += n; msg->mlen += (uint32_t)n; for (;;) { status = msg_parse(ctx, conn, msg); if (status != NC_OK) { return status; } /* get next message to parse */ nmsg = conn->recv_next(ctx, conn, false); if (nmsg == NULL || nmsg == msg) { /* no more data to parse */ break; } msg = nmsg; } return NC_OK; } rstatus_t msg_recv(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg; ASSERT(conn->recv_active); conn->recv_ready = 1; do { msg = conn->recv_next(ctx, conn, true); if (msg == NULL) { return NC_OK; } status = msg_recv_chain(ctx, conn, msg); if (status != NC_OK) { return status; } } while (conn->recv_ready); return NC_OK; } static rstatus_t msg_send_chain(struct context *ctx, struct conn *conn, struct msg *msg) { struct msg_tqh send_msgq; /* send msg q */ struct msg *nmsg; /* next msg */ struct mbuf *mbuf, *nbuf; /* current and next mbuf */ size_t mlen; /* current mbuf data length */ struct iovec *ciov, iov[NC_IOV_MAX]; /* current iovec */ struct array sendv; /* send iovec */ size_t nsend, nsent; /* bytes to send; bytes sent */ size_t limit; /* bytes to send limit */ ssize_t n; /* bytes sent by sendv */ TAILQ_INIT(&send_msgq); array_set(&sendv, iov, sizeof(iov[0]), NC_IOV_MAX); /* preprocess - build iovec */ nsend = 0; /* * readv() and writev() returns EINVAL if the sum of the iov_len values * overflows an ssize_t value Or, the vector count iovcnt is less than * zero or greater than the permitted maximum. */ limit = SSIZE_MAX; for (;;) { ASSERT(conn->smsg == msg); TAILQ_INSERT_TAIL(&send_msgq, msg, m_tqe); for (mbuf = STAILQ_FIRST(&msg->mhdr); mbuf != NULL && array_n(&sendv) < NC_IOV_MAX && nsend < limit; mbuf = nbuf) { nbuf = STAILQ_NEXT(mbuf, next); if (mbuf_empty(mbuf)) { continue; } mlen = mbuf_length(mbuf); if ((nsend + mlen) > limit) { mlen = limit - nsend; } ciov = array_push(&sendv); ciov->iov_base = mbuf->pos; ciov->iov_len = mlen; nsend += mlen; } if (array_n(&sendv) >= NC_IOV_MAX || nsend >= limit) { break; } msg = conn->send_next(ctx, conn); if (msg == NULL) { break; } } /* * (nsend == 0) is possible in redis multi-del * see PR: https://github.com/twitter/twemproxy/pull/225 */ conn->smsg = NULL; if (!TAILQ_EMPTY(&send_msgq) && nsend != 0) { n = conn_sendv(conn, &sendv, nsend); } else { n = 0; } nsent = n > 0 ? (size_t)n : 0; /* postprocess - process sent messages in send_msgq */ for (msg = TAILQ_FIRST(&send_msgq); msg != NULL; msg = nmsg) { nmsg = TAILQ_NEXT(msg, m_tqe); TAILQ_REMOVE(&send_msgq, msg, m_tqe); if (nsent == 0) { if (msg->mlen == 0) { conn->send_done(ctx, conn, msg); } continue; } /* adjust mbufs of the sent message */ for (mbuf = STAILQ_FIRST(&msg->mhdr); mbuf != NULL; mbuf = nbuf) { nbuf = STAILQ_NEXT(mbuf, next); if (mbuf_empty(mbuf)) { continue; } mlen = mbuf_length(mbuf); if (nsent < mlen) { /* mbuf was sent partially; process remaining bytes later */ mbuf->pos += nsent; ASSERT(mbuf->pos < mbuf->last); nsent = 0; break; } /* mbuf was sent completely; mark it empty */ mbuf->pos = mbuf->last; nsent -= mlen; } /* message has been sent completely, finalize it */ if (mbuf == NULL) { conn->send_done(ctx, conn, msg); } } ASSERT(TAILQ_EMPTY(&send_msgq)); if (n >= 0) { return NC_OK; } return (n == NC_EAGAIN) ? NC_OK : NC_ERROR; } rstatus_t msg_send(struct context *ctx, struct conn *conn) { rstatus_t status; struct msg *msg; ASSERT(conn->send_active); conn->send_ready = 1; do { msg = conn->send_next(ctx, conn); if (msg == NULL) { /* nothing to send */ return NC_OK; } status = msg_send_chain(ctx, conn, msg); if (status != NC_OK) { return status; } } while (conn->send_ready); return NC_OK; } nutcracker-0.4.1+dfsg/src/nc_signal.h0000664000000000000000000000164312542173515016202 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_SIGNAL_H_ #define _NC_SIGNAL_H_ #include struct signal { int signo; char *signame; int flags; void (*handler)(int signo); }; rstatus_t signal_init(void); void signal_deinit(void); void signal_handler(int signo); #endif nutcracker-0.4.1+dfsg/src/nc_string.c0000664000000000000000000001701212542173515016223 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include /* * String (struct string) is a sequence of unsigned char objects terminated * by the null character '\0'. The length of the string is pre-computed and * made available explicitly as an additional field. This means that we don't * have to walk the entire character sequence until the null terminating * character everytime that the length of the String is requested * * The only way to create a String is to initialize it using, string_init() * and duplicate an existing String - string_duplicate() or copy an existing * raw sequence of character bytes - string_copy(). Such String's must be * freed using string_deinit() * * We can also create String as reference to raw string - string_set_raw() * or to text string - string_set_text() or string(). Such String don't have * to be freed. */ void string_init(struct string *str) { str->len = 0; str->data = NULL; } void string_deinit(struct string *str) { ASSERT((str->len == 0 && str->data == NULL) || (str->len != 0 && str->data != NULL)); if (str->data != NULL) { nc_free(str->data); string_init(str); } } bool string_empty(const struct string *str) { ASSERT((str->len == 0 && str->data == NULL) || (str->len != 0 && str->data != NULL)); return str->len == 0 ? true : false; } rstatus_t string_duplicate(struct string *dst, const struct string *src) { ASSERT(dst->len == 0 && dst->data == NULL); ASSERT(src->len != 0 && src->data != NULL); dst->data = nc_strndup(src->data, src->len + 1); if (dst->data == NULL) { return NC_ENOMEM; } dst->len = src->len; dst->data[dst->len] = '\0'; return NC_OK; } rstatus_t string_copy(struct string *dst, const uint8_t *src, uint32_t srclen) { ASSERT(dst->len == 0 && dst->data == NULL); ASSERT(src != NULL && srclen != 0); dst->data = nc_strndup(src, srclen + 1); if (dst->data == NULL) { return NC_ENOMEM; } dst->len = srclen; dst->data[dst->len] = '\0'; return NC_OK; } int string_compare(const struct string *s1, const struct string *s2) { if (s1->len != s2->len) { return s1->len > s2->len ? 1 : -1; } return nc_strncmp(s1->data, s2->data, s1->len); } static char * _safe_utoa(int _base, uint64_t val, char *buf) { char hex[] = "0123456789abcdef"; uint32_t base = (uint32_t) _base; *buf-- = 0; do { *buf-- = hex[val % base]; } while ((val /= base) != 0); return buf + 1; } static char * _safe_itoa(int base, int64_t val, char *buf) { char hex[] = "0123456789abcdef"; char *orig_buf = buf; const int32_t is_neg = (val < 0); *buf-- = 0; if (is_neg) { val = -val; } if (is_neg && base == 16) { int ix; val -= 1; for (ix = 0; ix < 16; ++ix) buf[-ix] = '0'; } do { *buf-- = hex[val % base]; } while ((val /= base) != 0); if (is_neg && base == 10) { *buf-- = '-'; } if (is_neg && base == 16) { int ix; buf = orig_buf - 1; for (ix = 0; ix < 16; ++ix, --buf) { /* *INDENT-OFF* */ switch (*buf) { case '0': *buf = 'f'; break; case '1': *buf = 'e'; break; case '2': *buf = 'd'; break; case '3': *buf = 'c'; break; case '4': *buf = 'b'; break; case '5': *buf = 'a'; break; case '6': *buf = '9'; break; case '7': *buf = '8'; break; case '8': *buf = '7'; break; case '9': *buf = '6'; break; case 'a': *buf = '5'; break; case 'b': *buf = '4'; break; case 'c': *buf = '3'; break; case 'd': *buf = '2'; break; case 'e': *buf = '1'; break; case 'f': *buf = '0'; break; } /* *INDENT-ON* */ } } return buf + 1; } static const char * _safe_check_longlong(const char *fmt, int32_t * have_longlong) { *have_longlong = false; if (*fmt == 'l') { fmt++; if (*fmt != 'l') { *have_longlong = (sizeof(long) == sizeof(int64_t)); } else { fmt++; *have_longlong = true; } } return fmt; } int _safe_vsnprintf(char *to, size_t size, const char *format, va_list ap) { char *start = to; char *end = start + size - 1; for (; *format; ++format) { int32_t have_longlong = false; if (*format != '%') { if (to == end) { /* end of buffer */ break; } *to++ = *format; /* copy ordinary char */ continue; } ++format; /* skip '%' */ format = _safe_check_longlong(format, &have_longlong); switch (*format) { case 'd': case 'i': case 'u': case 'x': case 'p': { int64_t ival = 0; uint64_t uval = 0; if (*format == 'p') have_longlong = (sizeof(void *) == sizeof(uint64_t)); if (have_longlong) { if (*format == 'u') { uval = va_arg(ap, uint64_t); } else { ival = va_arg(ap, int64_t); } } else { if (*format == 'u') { uval = va_arg(ap, uint32_t); } else { ival = va_arg(ap, int32_t); } } { char buff[22]; const int base = (*format == 'x' || *format == 'p') ? 16 : 10; /* *INDENT-OFF* */ char *val_as_str = (*format == 'u') ? _safe_utoa(base, uval, &buff[sizeof(buff) - 1]) : _safe_itoa(base, ival, &buff[sizeof(buff) - 1]); /* *INDENT-ON* */ /* Strip off "ffffffff" if we have 'x' format without 'll' */ if (*format == 'x' && !have_longlong && ival < 0) { val_as_str += 8; } while (*val_as_str && to < end) { *to++ = *val_as_str++; } continue; } } case 's': { const char *val = va_arg(ap, char *); if (!val) { val = "(null)"; } while (*val && to < end) { *to++ = *val++; } continue; } } } *to = 0; return (int)(to - start); } int _safe_snprintf(char *to, size_t n, const char *fmt, ...) { int result; va_list args; va_start(args, fmt); result = _safe_vsnprintf(to, n, fmt, args); va_end(args); return result; } nutcracker-0.4.1+dfsg/src/nc_connection.h0000664000000000000000000001203012542173515017054 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_CONNECTION_H_ #define _NC_CONNECTION_H_ #include typedef rstatus_t (*conn_recv_t)(struct context *, struct conn*); typedef struct msg* (*conn_recv_next_t)(struct context *, struct conn *, bool); typedef void (*conn_recv_done_t)(struct context *, struct conn *, struct msg *, struct msg *); typedef rstatus_t (*conn_send_t)(struct context *, struct conn*); typedef struct msg* (*conn_send_next_t)(struct context *, struct conn *); typedef void (*conn_send_done_t)(struct context *, struct conn *, struct msg *); typedef void (*conn_close_t)(struct context *, struct conn *); typedef bool (*conn_active_t)(struct conn *); typedef void (*conn_ref_t)(struct conn *, void *); typedef void (*conn_unref_t)(struct conn *); typedef void (*conn_msgq_t)(struct context *, struct conn *, struct msg *); typedef void (*conn_post_connect_t)(struct context *ctx, struct conn *, struct server *server); typedef void (*conn_swallow_msg_t)(struct conn *, struct msg *, struct msg *); struct conn { TAILQ_ENTRY(conn) conn_tqe; /* link in server_pool / server / free q */ void *owner; /* connection owner - server_pool / server */ int sd; /* socket descriptor */ int family; /* socket address family */ socklen_t addrlen; /* socket length */ struct sockaddr *addr; /* socket address (ref in server or server_pool) */ struct msg_tqh imsg_q; /* incoming request Q */ struct msg_tqh omsg_q; /* outstanding request Q */ struct msg *rmsg; /* current message being rcvd */ struct msg *smsg; /* current message being sent */ conn_recv_t recv; /* recv (read) handler */ conn_recv_next_t recv_next; /* recv next message handler */ conn_recv_done_t recv_done; /* read done handler */ conn_send_t send; /* send (write) handler */ conn_send_next_t send_next; /* write next message handler */ conn_send_done_t send_done; /* write done handler */ conn_close_t close; /* close handler */ conn_active_t active; /* active? handler */ conn_post_connect_t post_connect; /* post connect handler */ conn_swallow_msg_t swallow_msg; /* react on messages to be swallowed */ conn_ref_t ref; /* connection reference handler */ conn_unref_t unref; /* connection unreference handler */ conn_msgq_t enqueue_inq; /* connection inq msg enqueue handler */ conn_msgq_t dequeue_inq; /* connection inq msg dequeue handler */ conn_msgq_t enqueue_outq; /* connection outq msg enqueue handler */ conn_msgq_t dequeue_outq; /* connection outq msg dequeue handler */ size_t recv_bytes; /* received (read) bytes */ size_t send_bytes; /* sent (written) bytes */ uint32_t events; /* connection io events */ err_t err; /* connection errno */ unsigned recv_active:1; /* recv active? */ unsigned recv_ready:1; /* recv ready? */ unsigned send_active:1; /* send active? */ unsigned send_ready:1; /* send ready? */ unsigned client:1; /* client? or server? */ unsigned proxy:1; /* proxy? */ unsigned connecting:1; /* connecting? */ unsigned connected:1; /* connected? */ unsigned eof:1; /* eof? aka passive close? */ unsigned done:1; /* done? aka close? */ unsigned redis:1; /* redis? */ unsigned authenticated:1; /* authenticated? */ }; TAILQ_HEAD(conn_tqh, conn); struct context *conn_to_ctx(struct conn *conn); struct conn *conn_get(void *owner, bool client, bool redis); struct conn *conn_get_proxy(void *owner); void conn_put(struct conn *conn); ssize_t conn_recv(struct conn *conn, void *buf, size_t size); ssize_t conn_sendv(struct conn *conn, struct array *sendv, size_t nsend); void conn_init(void); void conn_deinit(void); uint32_t conn_ncurr_conn(void); uint64_t conn_ntotal_conn(void); uint32_t conn_ncurr_cconn(void); bool conn_authenticated(struct conn *conn); #endif nutcracker-0.4.1+dfsg/src/nc_queue.h0000664000000000000000000011362612542173515016056 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 * $FreeBSD: src/sys/sys/queue.h,v 1.73 2010/02/20 01:05:30 emaste Exp $ */ #ifndef _NC_QUEUE_H_ #define _NC_QUEUE_H_ #include #ifndef __offsetof #define __offsetof(type, field) ((size_t)(&((type *)NULL)->field)) #endif /* * This file defines five types of data structures: singly-linked lists, * singly-linked tail queues, lists, tail queues, and circular queues. * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A singly-linked tail queue is headed by a pair of pointers, one to the * head of the list and the other to the tail of the list. The elements are * singly linked for minimum space and pointer manipulation overhead at the * expense of O(n) removal for arbitrary elements. New elements can be added * to the list after an existing element, at the head of the list, or at the * end of the list. Elements being removed from the head of the tail queue * should use the explicit macro for this purpose for optimum efficiency. * A singly-linked tail queue may only be traversed in the forward direction. * Singly-linked tail queues are ideal for applications with large datasets * and few or no removals or for implementing a FIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * A circle queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or after * an existing element, at the head of the list, or at the end of the list. * A circle queue may be traversed in either direction, but has a more * complex end of list detection. * * For details on the use of these macros, see the queue(3) manual page. * * * SLIST LIST STAILQ TAILQ CIRCLEQ * _HEAD + + + + + * _HEAD_INITIALIZER + + + + + * _ENTRY + + + + + * _INIT + + + + + * _EMPTY + + + + + * _FIRST + + + + + * _NEXT + + + + + * _PREV - - - + + * _LAST - - + + + * _FOREACH + + + + + * _FOREACH_REVERSE - - - + + * _INSERT_HEAD + + + + + * _INSERT_BEFORE - + - + + * _INSERT_AFTER + + + + + * _INSERT_TAIL - - + + + * _REMOVE_HEAD + - + - - * _REMOVE + + + + + * */ #define QUEUE_MACRO_SCRUB 1 #ifdef NC_ASSERT_PANIC # define QUEUE_MACRO_TRACE 1 # define QUEUE_MACRO_ASSERT 1 #endif #ifdef QUEUE_MACRO_SCRUB #define QMD_SAVELINK(name, link) void **name = (void *)&(link) #define TRASHIT(x) do { \ (x) = (void *) NULL; \ } while (0) #else #define QMD_SAVELINK(name, link) #define TRASHIT(x) #endif /* QUEUE_MACRO_SCRUB */ #ifdef QUEUE_MACRO_TRACE /* Store the last 2 places the queue element or head was altered */ struct qm_trace { char *lastfile; int lastline; char *prevfile; int prevline; }; #define TRACEBUF struct qm_trace trace; #define QMD_TRACE_HEAD(head) do { \ (head)->trace.prevline = (head)->trace.lastline; \ (head)->trace.prevfile = (head)->trace.lastfile; \ (head)->trace.lastline = __LINE__; \ (head)->trace.lastfile = __FILE__; \ } while (0) #define QMD_TRACE_ELEM(elem) do { \ (elem)->trace.prevline = (elem)->trace.lastline; \ (elem)->trace.prevfile = (elem)->trace.lastfile; \ (elem)->trace.lastline = __LINE__; \ (elem)->trace.lastfile = __FILE__; \ } while (0) #else #define QMD_TRACE_ELEM(elem) #define QMD_TRACE_HEAD(head) #define TRACEBUF #endif /* QUEUE_MACRO_TRACE */ /* * Singly-linked List declarations. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List functions. */ #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_FOREACH(var, head, field) \ for ((var) = SLIST_FIRST((head)); \ (var); \ (var) = SLIST_NEXT((var), field)) #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SLIST_FIRST((head)); \ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ (var) = (tvar)) #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ for ((varp) = &SLIST_FIRST((head)); \ ((var) = *(varp)) != NULL; \ (varp) = &SLIST_NEXT((var), field)) #define SLIST_INIT(head) do { \ SLIST_FIRST((head)) = NULL; \ } while (0) #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ SLIST_NEXT((slistelm), field) = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ SLIST_FIRST((head)) = (elm); \ } while (0) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_REMOVE(head, elm, type, field) do { \ if (SLIST_FIRST((head)) == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } else { \ struct type *curelm = SLIST_FIRST((head)); \ while (SLIST_NEXT(curelm, field) != (elm)) { \ curelm = SLIST_NEXT(curelm, field); \ } \ SLIST_REMOVE_AFTER(curelm, field); \ } \ } while (0) #define SLIST_REMOVE_AFTER(elm, field) do { \ QMD_SAVELINK(oldnext, SLIST_NEXT(SLIST_NEXT(elm, field), field)); \ SLIST_NEXT(elm, field) = SLIST_NEXT(SLIST_NEXT(elm, field), field); \ TRASHIT(*oldnext); \ } while (0) #define SLIST_REMOVE_HEAD(head, field) do { \ QMD_SAVELINK(oldnext, SLIST_NEXT(SLIST_FIRST((head)), field)); \ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ TRASHIT(*oldnext); \ } while (0) /* * Singly-linked Tail queue declarations. */ #define STAILQ_HEAD(name, type) \ struct name { \ struct type *stqh_first; /* first element */ \ struct type **stqh_last; /* addr of last next element */ \ } #define STAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).stqh_first } #define STAILQ_ENTRY(type) \ struct { \ struct type *stqe_next; /* next element */ \ } /* * Singly-linked Tail queue functions. */ #define STAILQ_CONCAT(head1, head2) do { \ if (!STAILQ_EMPTY((head2))) { \ *(head1)->stqh_last = (head2)->stqh_first; \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_INIT((head2)); \ } \ } while (0) #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) #define STAILQ_FIRST(head) ((head)->stqh_first) #define STAILQ_FOREACH(var, head, field) \ for ((var) = STAILQ_FIRST((head)); \ (var); \ (var) = STAILQ_NEXT((var), field)) #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = STAILQ_FIRST((head)); \ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define STAILQ_INIT(head) do { \ STAILQ_FIRST((head)) = NULL; \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } while (0) #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_NEXT((tqelm), field) = (elm); \ } while (0) #define STAILQ_INSERT_HEAD(head, elm, field) do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_FIRST((head)) = (elm); \ } while (0) #define STAILQ_INSERT_TAIL(head, elm, field) do { \ STAILQ_NEXT((elm), field) = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } while (0) #define STAILQ_LAST(head, type, field) \ (STAILQ_EMPTY((head)) ? \ NULL : \ ((struct type *)(void *) \ ((char *)((head)->stqh_last) - __offsetof(struct type, field)))) #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) #define STAILQ_REMOVE(head, elm, type, field) do { \ if (STAILQ_FIRST((head)) == (elm)) { \ STAILQ_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = STAILQ_FIRST((head)); \ while (STAILQ_NEXT(curelm, field) != (elm)) \ curelm = STAILQ_NEXT(curelm, field); \ STAILQ_REMOVE_AFTER(head, curelm, field); \ } \ } while (0) #define STAILQ_REMOVE_HEAD(head, field) do { \ QMD_SAVELINK(oldnext, STAILQ_NEXT(STAILQ_FIRST((head)), field)); \ if ((STAILQ_FIRST((head)) = \ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) { \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } \ TRASHIT(*oldnext); \ } while (0) #define STAILQ_REMOVE_AFTER(head, elm, field) do { \ QMD_SAVELINK(oldnext, STAILQ_NEXT(STAILQ_NEXT(elm, field), field)); \ if ((STAILQ_NEXT(elm, field) = \ STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) { \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } \ TRASHIT(*oldnext); \ } while (0) #define STAILQ_SWAP(head1, head2, type) do { \ struct type *swap_first = STAILQ_FIRST(head1); \ struct type **swap_last = (head1)->stqh_last; \ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_FIRST(head2) = swap_first; \ (head2)->stqh_last = swap_last; \ if (STAILQ_EMPTY(head1)) \ (head1)->stqh_last = &STAILQ_FIRST(head1); \ if (STAILQ_EMPTY(head2)) \ (head2)->stqh_last = &STAILQ_FIRST(head2); \ } while (0) /* * List declarations. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List functions. */ #ifdef QUEUE_MACRO_ASSERT #define QMD_LIST_CHECK_HEAD(head, field) do { \ if (LIST_FIRST((head)) != NULL && \ LIST_FIRST((head))->field.le_prev != &LIST_FIRST((head))) { \ log_panic("Bad list head %p first->prev != head", (void *)(head)); \ } \ } while (0) #define QMD_LIST_CHECK_NEXT(elm, field) do { \ if (LIST_NEXT((elm), field) != NULL && \ LIST_NEXT((elm), field)->field.le_prev != &((elm)->field.le_next)) {\ log_panic("Bad link elm %p next->prev != elm",(void *)(elm)); \ } \ } while (0) #define QMD_LIST_CHECK_PREV(elm, field) do { \ if (*(elm)->field.le_prev != (elm)) { \ log_panic("Bad link elm %p prev->next != elm",(void *)(elm)); \ } \ } while (0) #else #define QMD_LIST_CHECK_HEAD(head, field) #define QMD_LIST_CHECK_NEXT(elm, field) #define QMD_LIST_CHECK_PREV(elm, field) #endif /* QUEUE_MACRO_ASSERT */ #define LIST_EMPTY(head) ((head)->lh_first == NULL) #define LIST_FIRST(head) ((head)->lh_first) #define LIST_FOREACH(var, head, field) \ for ((var) = LIST_FIRST((head)); \ (var); \ (var) = LIST_NEXT((var), field)) #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST((head)); \ (var) && ((tvar) = LIST_NEXT((var), field), 1); \ (var) = (tvar)) #define LIST_INIT(head) do { \ LIST_FIRST((head)) = NULL; \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ QMD_LIST_CHECK_NEXT(listelm, field); \ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ LIST_NEXT((listelm), field)->field.le_prev = \ &LIST_NEXT((elm), field); \ LIST_NEXT((listelm), field) = (elm); \ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ QMD_LIST_CHECK_PREV(listelm, field); \ (elm)->field.le_prev = (listelm)->field.le_prev; \ LIST_NEXT((elm), field) = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) do { \ QMD_LIST_CHECK_HEAD((head), field); \ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field); \ LIST_FIRST((head)) = (elm); \ (elm)->field.le_prev = &LIST_FIRST((head)); \ } while (0) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_REMOVE(elm, field) do { \ QMD_SAVELINK(oldnext, (elm)->field.le_next); \ QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ QMD_LIST_CHECK_NEXT(elm, field); \ QMD_LIST_CHECK_PREV(elm, field); \ if (LIST_NEXT((elm), field) != NULL) \ LIST_NEXT((elm), field)->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = LIST_NEXT((elm), field); \ TRASHIT(*oldnext); \ TRASHIT(*oldprev); \ } while (0) #define LIST_SWAP(head1, head2, type, field) do { \ struct type *swap_tmp = LIST_FIRST((head1)); \ LIST_FIRST((head1)) = LIST_FIRST((head2)); \ LIST_FIRST((head2)) = swap_tmp; \ if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ } while (0) /* * Tail queue declarations. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ TRACEBUF \ } #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ TRACEBUF \ } /* * Tail queue functions. */ #ifdef QUEUE_MACRO_ASSERT #define QMD_TAILQ_CHECK_HEAD(head, field) do { \ if (!TAILQ_EMPTY(head) && \ TAILQ_FIRST((head))->field.tqe_prev != &TAILQ_FIRST((head))) { \ log_panic("Bad tailq head %p first->prev != head", (void *)(head)); \ } \ } while (0) #define QMD_TAILQ_CHECK_TAIL(head, field) do { \ if (*(head)->tqh_last != NULL) { \ log_panic("Bad tailq NEXT(%p->tqh_last) != NULL",(void *)(head)); \ } \ } while (0) #define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ if (TAILQ_NEXT((elm), field) != NULL && \ TAILQ_NEXT((elm), field)->field.tqe_prev != &((elm)->field.tqe_next)) {\ log_panic("Bad link elm %p next->prev != elm",(void *)(elm)); \ } \ } while (0) #define QMD_TAILQ_CHECK_PREV(elm, field) do { \ if (*(elm)->field.tqe_prev != (elm)) { \ log_panic("Bad link elm %p prev->next != elm",(void *)(elm)); \ } \ } while (0) #else #define QMD_TAILQ_CHECK_HEAD(head, field) #define QMD_TAILQ_CHECK_TAIL(head, headname) #define QMD_TAILQ_CHECK_NEXT(elm, field) #define QMD_TAILQ_CHECK_PREV(elm, field) #endif /* QUEUE_MACRO_ASSERT */ #define TAILQ_CONCAT(head1, head2, field) do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ QMD_TRACE_HEAD(head1); \ QMD_TRACE_HEAD(head2); \ } \ } while (0) #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_FOREACH(var, head, field) \ for ((var) = TAILQ_FIRST((head)); \ (var); \ (var) = TAILQ_NEXT((var), field)) #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST((head)); \ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = TAILQ_LAST((head), headname); \ (var); \ (var) = TAILQ_PREV((var), headname, field)) #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ for ((var) = TAILQ_LAST((head), headname); \ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ (var) = (tvar)) #define TAILQ_INIT(head) do { \ TAILQ_FIRST((head)) = NULL; \ (head)->tqh_last = &TAILQ_FIRST((head)); \ QMD_TRACE_HEAD(head); \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ QMD_TAILQ_CHECK_NEXT(listelm, field); \ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL) { \ TAILQ_NEXT((elm), field)->field.tqe_prev = &TAILQ_NEXT((elm), field);\ } else { \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ QMD_TRACE_HEAD(head); \ } \ TAILQ_NEXT((listelm), field) = (elm); \ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ QMD_TRACE_ELEM(&(elm)->field); \ QMD_TRACE_ELEM(&listelm->field); \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ QMD_TAILQ_CHECK_PREV(listelm, field); \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ TAILQ_NEXT((elm), field) = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ QMD_TRACE_ELEM(&(elm)->field); \ QMD_TRACE_ELEM(&listelm->field); \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ QMD_TAILQ_CHECK_HEAD(head, field); \ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ TAILQ_FIRST((head))->field.tqe_prev = \ &TAILQ_NEXT((elm), field); \ else \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ TAILQ_FIRST((head)) = (elm); \ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ QMD_TRACE_HEAD(head); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ QMD_TAILQ_CHECK_TAIL(head, field); \ TAILQ_NEXT((elm), field) = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ QMD_TRACE_HEAD(head); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_REMOVE(head, elm, field) do { \ QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ QMD_TAILQ_CHECK_NEXT(elm, field); \ QMD_TAILQ_CHECK_PREV(elm, field); \ if ((TAILQ_NEXT((elm), field)) != NULL) { \ TAILQ_NEXT((elm), field)->field.tqe_prev = \ (elm)->field.tqe_prev; \ } else { \ (head)->tqh_last = (elm)->field.tqe_prev; \ QMD_TRACE_HEAD(head); \ } \ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ TRASHIT(*oldnext); \ TRASHIT(*oldprev); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_SWAP(head1, head2, type, field) do { \ struct type *swap_first = (head1)->tqh_first; \ struct type **swap_last = (head1)->tqh_last; \ (head1)->tqh_first = (head2)->tqh_first; \ (head1)->tqh_last = (head2)->tqh_last; \ (head2)->tqh_first = swap_first; \ (head2)->tqh_last = swap_last; \ if ((swap_first = (head1)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head1)->tqh_first; \ else \ (head1)->tqh_last = &(head1)->tqh_first; \ if ((swap_first = (head2)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head2)->tqh_first; \ else \ (head2)->tqh_last = &(head2)->tqh_first; \ } while (0) /* * Circular queue declarations. */ #define CIRCLEQ_HEAD(name, type) \ struct name { \ struct type *cqh_first; /* first element */ \ struct type *cqh_last; /* last element */ \ } #define CIRCLEQ_HEAD_INITIALIZER(head) \ { (void *)&(head), (void *)&(head) } #define CIRCLEQ_ENTRY(type) \ struct { \ struct type *cqe_next; /* next element */ \ struct type *cqe_prev; /* previous element */ \ } /* * Circular queue functions. */ #define CIRCLEQ_EMPTY(head) ((head)->cqh_first == (void *)(head)) #define CIRCLEQ_FIRST(head) ((head)->cqh_first) #define CIRCLEQ_FOREACH(var, head, field) \ for ((var) = CIRCLEQ_FIRST((head)); \ (var) != (void *)(head) || ((var) = NULL); \ (var) = CIRCLEQ_NEXT((var), field)) #define CIRCLEQ_FOREACH_REVERSE(var, head, field) \ for ((var) = CIRCLEQ_LAST((head)); \ (var) != (void *)(head) || ((var) = NULL); \ (var) = CIRCLEQ_PREV((var), field)) #define CIRCLEQ_INIT(head) do { \ CIRCLEQ_FIRST((head)) = (void *)(head); \ CIRCLEQ_LAST((head)) = (void *)(head); \ } while (0) #define CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do { \ CIRCLEQ_NEXT((elm), field) = CIRCLEQ_NEXT((listelm), field); \ CIRCLEQ_PREV((elm), field) = (listelm); \ if (CIRCLEQ_NEXT((listelm), field) == (void *)(head)) \ CIRCLEQ_LAST((head)) = (elm); \ else \ CIRCLEQ_PREV(CIRCLEQ_NEXT((listelm), field), field) = (elm); \ CIRCLEQ_NEXT((listelm), field) = (elm); \ } while (0) #define CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do { \ CIRCLEQ_NEXT((elm), field) = (listelm); \ CIRCLEQ_PREV((elm), field) = CIRCLEQ_PREV((listelm), field); \ if (CIRCLEQ_PREV((listelm), field) == (void *)(head)) \ CIRCLEQ_FIRST((head)) = (elm); \ else \ CIRCLEQ_NEXT(CIRCLEQ_PREV((listelm), field), field) = (elm); \ CIRCLEQ_PREV((listelm), field) = (elm); \ } while (0) #define CIRCLEQ_INSERT_HEAD(head, elm, field) do { \ CIRCLEQ_NEXT((elm), field) = CIRCLEQ_FIRST((head)); \ CIRCLEQ_PREV((elm), field) = (void *)(head); \ if (CIRCLEQ_LAST((head)) == (void *)(head)) \ CIRCLEQ_LAST((head)) = (elm); \ else \ CIRCLEQ_PREV(CIRCLEQ_FIRST((head)), field) = (elm); \ CIRCLEQ_FIRST((head)) = (elm); \ } while (0) #define CIRCLEQ_INSERT_TAIL(head, elm, field) do { \ CIRCLEQ_NEXT((elm), field) = (void *)(head); \ CIRCLEQ_PREV((elm), field) = CIRCLEQ_LAST((head)); \ if (CIRCLEQ_FIRST((head)) == (void *)(head)) \ CIRCLEQ_FIRST((head)) = (elm); \ else \ CIRCLEQ_NEXT(CIRCLEQ_LAST((head)), field) = (elm); \ CIRCLEQ_LAST((head)) = (elm); \ } while (0) #define CIRCLEQ_LAST(head) ((head)->cqh_last) #define CIRCLEQ_NEXT(elm,field) ((elm)->field.cqe_next) #define CIRCLEQ_PREV(elm,field) ((elm)->field.cqe_prev) #define CIRCLEQ_REMOVE(head, elm, field) do { \ if (CIRCLEQ_NEXT((elm), field) == (void *)(head)) \ CIRCLEQ_LAST((head)) = CIRCLEQ_PREV((elm), field); \ else \ CIRCLEQ_PREV(CIRCLEQ_NEXT((elm), field), field) = \ CIRCLEQ_PREV((elm), field); \ if (CIRCLEQ_PREV((elm), field) == (void *)(head)) \ CIRCLEQ_FIRST((head)) = CIRCLEQ_NEXT((elm), field); \ else \ CIRCLEQ_NEXT(CIRCLEQ_PREV((elm), field), field) = \ CIRCLEQ_NEXT((elm), field); \ } while (0) #endif nutcracker-0.4.1+dfsg/src/nc_log.h0000664000000000000000000001243712542173515015511 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_LOG_H_ #define _NC_LOG_H_ struct logger { char *name; /* log file name */ int level; /* log level */ int fd; /* log file descriptor */ int nerror; /* # log error */ }; #define LOG_EMERG 0 /* system in unusable */ #define LOG_ALERT 1 /* action must be taken immediately */ #define LOG_CRIT 2 /* critical conditions */ #define LOG_ERR 3 /* error conditions */ #define LOG_WARN 4 /* warning conditions */ #define LOG_NOTICE 5 /* normal but significant condition (default) */ #define LOG_INFO 6 /* informational */ #define LOG_DEBUG 7 /* debug messages */ #define LOG_VERB 8 /* verbose messages */ #define LOG_VVERB 9 /* verbose messages on crack */ #define LOG_VVVERB 10 /* verbose messages on ganga */ #define LOG_PVERB 11 /* periodic verbose messages on crack */ #define LOG_MAX_LEN 256 /* max length of log message */ /* * log_stderr - log to stderr * loga - log always * loga_hexdump - log hexdump always * log_error - error log messages * log_warn - warning log messages * log_panic - log messages followed by a panic * ... * log_debug - debug log messages based on a log level * log_hexdump - hexadump -C of a log buffer */ #ifdef NC_DEBUG_LOG #define log_debug(_level, ...) do { \ if (log_loggable(_level) != 0) { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ } \ } while (0) #define log_hexdump(_level, _data, _datalen, ...) do { \ if (log_loggable(_level) != 0) { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen), \ __VA_ARGS__); \ } \ } while (0) #else #define log_debug(_level, ...) #define log_hexdump(_level, _data, _datalen, ...) #endif #define log_stderr(...) do { \ _log_stderr(__VA_ARGS__); \ } while (0) #define log_safe(...) do { \ _log_safe(__VA_ARGS__); \ } while (0) #define log_stderr_safe(...) do { \ _log_stderr_safe(__VA_ARGS__); \ } while (0) #define loga(...) do { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ } while (0) #define loga_hexdump(_data, _datalen, ...) do { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen), \ __VA_ARGS__); \ } while (0) \ #define log_error(...) do { \ if (log_loggable(LOG_ALERT) != 0) { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ } \ } while (0) #define log_warn(...) do { \ if (log_loggable(LOG_WARN) != 0) { \ _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ } \ } while (0) #define log_panic(...) do { \ if (log_loggable(LOG_EMERG) != 0) { \ _log(__FILE__, __LINE__, 1, __VA_ARGS__); \ } \ } while (0) int log_init(int level, char *filename); void log_deinit(void); void log_level_up(void); void log_level_down(void); void log_level_set(int level); void log_stacktrace(void); void log_reopen(void); int log_loggable(int level); void _log(const char *file, int line, int panic, const char *fmt, ...); void _log_stderr(const char *fmt, ...); void _log_safe(const char *fmt, ...); void _log_stderr_safe(const char *fmt, ...); void _log_hexdump(const char *file, int line, char *data, int datalen, const char *fmt, ...); #endif nutcracker-0.4.1+dfsg/src/nc_array.c0000664000000000000000000000712612542173515016040 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include struct array * array_create(uint32_t n, size_t size) { struct array *a; ASSERT(n != 0 && size != 0); a = nc_alloc(sizeof(*a)); if (a == NULL) { return NULL; } a->elem = nc_alloc(n * size); if (a->elem == NULL) { nc_free(a); return NULL; } a->nelem = 0; a->size = size; a->nalloc = n; return a; } void array_destroy(struct array *a) { array_deinit(a); nc_free(a); } rstatus_t array_init(struct array *a, uint32_t n, size_t size) { ASSERT(n != 0 && size != 0); a->elem = nc_alloc(n * size); if (a->elem == NULL) { return NC_ENOMEM; } a->nelem = 0; a->size = size; a->nalloc = n; return NC_OK; } void array_deinit(struct array *a) { ASSERT(a->nelem == 0); if (a->elem != NULL) { nc_free(a->elem); } } uint32_t array_idx(struct array *a, void *elem) { uint8_t *p, *q; uint32_t off, idx; ASSERT(elem >= a->elem); p = a->elem; q = elem; off = (uint32_t)(q - p); ASSERT(off % (uint32_t)a->size == 0); idx = off / (uint32_t)a->size; return idx; } void * array_push(struct array *a) { void *elem, *new; size_t size; if (a->nelem == a->nalloc) { /* the array is full; allocate new array */ size = a->size * a->nalloc; new = nc_realloc(a->elem, 2 * size); if (new == NULL) { return NULL; } a->elem = new; a->nalloc *= 2; } elem = (uint8_t *)a->elem + a->size * a->nelem; a->nelem++; return elem; } void * array_pop(struct array *a) { void *elem; ASSERT(a->nelem != 0); a->nelem--; elem = (uint8_t *)a->elem + a->size * a->nelem; return elem; } void * array_get(struct array *a, uint32_t idx) { void *elem; ASSERT(a->nelem != 0); ASSERT(idx < a->nelem); elem = (uint8_t *)a->elem + (a->size * idx); return elem; } void * array_top(struct array *a) { ASSERT(a->nelem != 0); return array_get(a, a->nelem - 1); } void array_swap(struct array *a, struct array *b) { struct array tmp; tmp = *a; *a = *b; *b = tmp; } /* * Sort nelem elements of the array in ascending order based on the * compare comparator. */ void array_sort(struct array *a, array_compare_t compare) { ASSERT(a->nelem != 0); qsort(a->elem, a->nelem, a->size, compare); } /* * Calls the func once for each element in the array as long as func returns * success. On failure short-circuits and returns the error status. */ rstatus_t array_each(struct array *a, array_each_t func, void *data) { uint32_t i, nelem; ASSERT(array_n(a) != 0); ASSERT(func != NULL); for (i = 0, nelem = array_n(a); i < nelem; i++) { void *elem = array_get(a, i); rstatus_t status; status = func(elem, data); if (status != NC_OK) { return status; } } return NC_OK; } nutcracker-0.4.1+dfsg/src/nc.c0000664000000000000000000003426212542173515014643 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #define NC_CONF_PATH "conf/nutcracker.yml" #define NC_LOG_DEFAULT LOG_NOTICE #define NC_LOG_MIN LOG_EMERG #define NC_LOG_MAX LOG_PVERB #define NC_LOG_PATH NULL #define NC_STATS_PORT STATS_PORT #define NC_STATS_ADDR STATS_ADDR #define NC_STATS_INTERVAL STATS_INTERVAL #define NC_PID_FILE NULL #define NC_MBUF_SIZE MBUF_SIZE #define NC_MBUF_MIN_SIZE MBUF_MIN_SIZE #define NC_MBUF_MAX_SIZE MBUF_MAX_SIZE static int show_help; static int show_version; static int test_conf; static int daemonize; static int describe_stats; static struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "test-conf", no_argument, NULL, 't' }, { "daemonize", no_argument, NULL, 'd' }, { "describe-stats", no_argument, NULL, 'D' }, { "verbose", required_argument, NULL, 'v' }, { "output", required_argument, NULL, 'o' }, { "conf-file", required_argument, NULL, 'c' }, { "stats-port", required_argument, NULL, 's' }, { "stats-interval", required_argument, NULL, 'i' }, { "stats-addr", required_argument, NULL, 'a' }, { "pid-file", required_argument, NULL, 'p' }, { "mbuf-size", required_argument, NULL, 'm' }, { NULL, 0, NULL, 0 } }; static char short_options[] = "hVtdDv:o:c:s:i:a:p:m:"; static rstatus_t nc_daemonize(int dump_core) { rstatus_t status; pid_t pid, sid; int fd; pid = fork(); switch (pid) { case -1: log_error("fork() failed: %s", strerror(errno)); return NC_ERROR; case 0: break; default: /* parent terminates */ _exit(0); } /* 1st child continues and becomes the session leader */ sid = setsid(); if (sid < 0) { log_error("setsid() failed: %s", strerror(errno)); return NC_ERROR; } if (signal(SIGHUP, SIG_IGN) == SIG_ERR) { log_error("signal(SIGHUP, SIG_IGN) failed: %s", strerror(errno)); return NC_ERROR; } pid = fork(); switch (pid) { case -1: log_error("fork() failed: %s", strerror(errno)); return NC_ERROR; case 0: break; default: /* 1st child terminates */ _exit(0); } /* 2nd child continues */ /* change working directory */ if (dump_core == 0) { status = chdir("/"); if (status < 0) { log_error("chdir(\"/\") failed: %s", strerror(errno)); return NC_ERROR; } } /* clear file mode creation mask */ umask(0); /* redirect stdin, stdout and stderr to "/dev/null" */ fd = open("/dev/null", O_RDWR); if (fd < 0) { log_error("open(\"/dev/null\") failed: %s", strerror(errno)); return NC_ERROR; } status = dup2(fd, STDIN_FILENO); if (status < 0) { log_error("dup2(%d, STDIN) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } status = dup2(fd, STDOUT_FILENO); if (status < 0) { log_error("dup2(%d, STDOUT) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } status = dup2(fd, STDERR_FILENO); if (status < 0) { log_error("dup2(%d, STDERR) failed: %s", fd, strerror(errno)); close(fd); return NC_ERROR; } if (fd > STDERR_FILENO) { status = close(fd); if (status < 0) { log_error("close(%d) failed: %s", fd, strerror(errno)); return NC_ERROR; } } return NC_OK; } static void nc_print_run(struct instance *nci) { int status; struct utsname name; status = uname(&name); if (status < 0) { loga("nutcracker-%s started on pid %d", NC_VERSION_STRING, nci->pid); } else { loga("nutcracker-%s built for %s %s %s started on pid %d", NC_VERSION_STRING, name.sysname, name.release, name.machine, nci->pid); } loga("run, rabbit run / dig that hole, forget the sun / " "and when at last the work is done / don't sit down / " "it's time to dig another one"); } static void nc_print_done(void) { loga("done, rabbit done"); } static void nc_show_usage(void) { log_stderr( "Usage: nutcracker [-?hVdDt] [-v verbosity level] [-o output file]" CRLF " [-c conf file] [-s stats port] [-a stats addr]" CRLF " [-i stats interval] [-p pid file] [-m mbuf size]" CRLF ""); log_stderr( "Options:" CRLF " -h, --help : this help" CRLF " -V, --version : show version and exit" CRLF " -t, --test-conf : test configuration for syntax errors and exit" CRLF " -d, --daemonize : run as a daemon" CRLF " -D, --describe-stats : print stats description and exit"); log_stderr( " -v, --verbose=N : set logging level (default: %d, min: %d, max: %d)" CRLF " -o, --output=S : set logging file (default: %s)" CRLF " -c, --conf-file=S : set configuration file (default: %s)" CRLF " -s, --stats-port=N : set stats monitoring port (default: %d)" CRLF " -a, --stats-addr=S : set stats monitoring ip (default: %s)" CRLF " -i, --stats-interval=N : set stats aggregation interval in msec (default: %d msec)" CRLF " -p, --pid-file=S : set pid file (default: %s)" CRLF " -m, --mbuf-size=N : set size of mbuf chunk in bytes (default: %d bytes)" CRLF "", NC_LOG_DEFAULT, NC_LOG_MIN, NC_LOG_MAX, NC_LOG_PATH != NULL ? NC_LOG_PATH : "stderr", NC_CONF_PATH, NC_STATS_PORT, NC_STATS_ADDR, NC_STATS_INTERVAL, NC_PID_FILE != NULL ? NC_PID_FILE : "off", NC_MBUF_SIZE); } static rstatus_t nc_create_pidfile(struct instance *nci) { char pid[NC_UINTMAX_MAXLEN]; int fd, pid_len; ssize_t n; fd = open(nci->pid_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { log_error("opening pid file '%s' failed: %s", nci->pid_filename, strerror(errno)); return NC_ERROR; } nci->pidfile = 1; pid_len = nc_snprintf(pid, NC_UINTMAX_MAXLEN, "%d", nci->pid); n = nc_write(fd, pid, pid_len); if (n < 0) { log_error("write to pid file '%s' failed: %s", nci->pid_filename, strerror(errno)); return NC_ERROR; } close(fd); return NC_OK; } static void nc_remove_pidfile(struct instance *nci) { int status; status = unlink(nci->pid_filename); if (status < 0) { log_error("unlink of pid file '%s' failed, ignored: %s", nci->pid_filename, strerror(errno)); } } static void nc_set_default_options(struct instance *nci) { int status; nci->ctx = NULL; nci->log_level = NC_LOG_DEFAULT; nci->log_filename = NC_LOG_PATH; nci->conf_filename = NC_CONF_PATH; nci->stats_port = NC_STATS_PORT; nci->stats_addr = NC_STATS_ADDR; nci->stats_interval = NC_STATS_INTERVAL; status = nc_gethostname(nci->hostname, NC_MAXHOSTNAMELEN); if (status < 0) { log_warn("gethostname failed, ignored: %s", strerror(errno)); nc_snprintf(nci->hostname, NC_MAXHOSTNAMELEN, "unknown"); } nci->hostname[NC_MAXHOSTNAMELEN - 1] = '\0'; nci->mbuf_chunk_size = NC_MBUF_SIZE; nci->pid = (pid_t)-1; nci->pid_filename = NULL; nci->pidfile = 0; } static rstatus_t nc_get_options(int argc, char **argv, struct instance *nci) { int c, value; opterr = 0; for (;;) { c = getopt_long(argc, argv, short_options, long_options, NULL); if (c == -1) { /* no more options */ break; } switch (c) { case 'h': show_version = 1; show_help = 1; break; case 'V': show_version = 1; break; case 't': test_conf = 1; break; case 'd': daemonize = 1; break; case 'D': describe_stats = 1; show_version = 1; break; case 'v': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -v requires a number"); return NC_ERROR; } nci->log_level = value; break; case 'o': nci->log_filename = optarg; break; case 'c': nci->conf_filename = optarg; break; case 's': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -s requires a number"); return NC_ERROR; } if (!nc_valid_port(value)) { log_stderr("nutcracker: option -s value %d is not a valid " "port", value); return NC_ERROR; } nci->stats_port = (uint16_t)value; break; case 'i': value = nc_atoi(optarg, strlen(optarg)); if (value < 0) { log_stderr("nutcracker: option -i requires a number"); return NC_ERROR; } nci->stats_interval = value; break; case 'a': nci->stats_addr = optarg; break; case 'p': nci->pid_filename = optarg; break; case 'm': value = nc_atoi(optarg, strlen(optarg)); if (value <= 0) { log_stderr("nutcracker: option -m requires a non-zero number"); return NC_ERROR; } if (value < NC_MBUF_MIN_SIZE || value > NC_MBUF_MAX_SIZE) { log_stderr("nutcracker: mbuf chunk size must be between %zu and" " %zu bytes", NC_MBUF_MIN_SIZE, NC_MBUF_MAX_SIZE); return NC_ERROR; } nci->mbuf_chunk_size = (size_t)value; break; case '?': switch (optopt) { case 'o': case 'c': case 'p': log_stderr("nutcracker: option -%c requires a file name", optopt); break; case 'm': case 'v': case 's': case 'i': log_stderr("nutcracker: option -%c requires a number", optopt); break; case 'a': log_stderr("nutcracker: option -%c requires a string", optopt); break; default: log_stderr("nutcracker: invalid option -- '%c'", optopt); break; } return NC_ERROR; default: log_stderr("nutcracker: invalid option -- '%c'", optopt); return NC_ERROR; } } return NC_OK; } /* * Returns true if configuration file has a valid syntax, otherwise * returns false */ static bool nc_test_conf(struct instance *nci) { struct conf *cf; cf = conf_create(nci->conf_filename); if (cf == NULL) { log_stderr("nutcracker: configuration file '%s' syntax is invalid", nci->conf_filename); return false; } conf_destroy(cf); log_stderr("nutcracker: configuration file '%s' syntax is ok", nci->conf_filename); return true; } static rstatus_t nc_pre_run(struct instance *nci) { rstatus_t status; status = log_init(nci->log_level, nci->log_filename); if (status != NC_OK) { return status; } if (daemonize) { status = nc_daemonize(1); if (status != NC_OK) { return status; } } nci->pid = getpid(); status = signal_init(); if (status != NC_OK) { return status; } if (nci->pid_filename) { status = nc_create_pidfile(nci); if (status != NC_OK) { return status; } } nc_print_run(nci); return NC_OK; } static void nc_post_run(struct instance *nci) { if (nci->pidfile) { nc_remove_pidfile(nci); } signal_deinit(); nc_print_done(); log_deinit(); } static void nc_run(struct instance *nci) { rstatus_t status; struct context *ctx; ctx = core_start(nci); if (ctx == NULL) { return; } /* run rabbit run */ for (;;) { status = core_loop(ctx); if (status != NC_OK) { break; } } core_stop(ctx); } int main(int argc, char **argv) { rstatus_t status; struct instance nci; nc_set_default_options(&nci); status = nc_get_options(argc, argv, &nci); if (status != NC_OK) { nc_show_usage(); exit(1); } if (show_version) { log_stderr("This is nutcracker-%s" CRLF, NC_VERSION_STRING); if (show_help) { nc_show_usage(); } if (describe_stats) { stats_describe(); } exit(0); } if (test_conf) { if (!nc_test_conf(&nci)) { exit(1); } exit(0); } status = nc_pre_run(&nci); if (status != NC_OK) { nc_post_run(&nci); exit(1); } nc_run(&nci); nc_post_run(&nci); exit(1); } nutcracker-0.4.1+dfsg/src/nc_client.h0000664000000000000000000000163612542173515016205 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_CLIENT_H_ #define _NC_CLIENT_H_ #include bool client_active(struct conn *conn); void client_ref(struct conn *conn, void *owner); void client_unref(struct conn *conn); void client_close(struct context *ctx, struct conn *conn); #endif nutcracker-0.4.1+dfsg/src/nc_connection.c0000664000000000000000000003042112542173515017053 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include /* * nc_connection.[ch] * Connection (struct conn) * + + + * | | | * | Proxy | * | nc_proxy.[ch] | * / \ * Client Server * nc_client.[ch] nc_server.[ch] * * Nutcracker essentially multiplexes m client connections over n server * connections. Usually m >> n, so that nutcracker can pipeline requests * from several clients over a server connection and hence use the connection * bandwidth to the server efficiently * * Client and server connection maintain two fifo queues for requests: * * 1). in_q (imsg_q): queue of incoming requests * 2). out_q (omsg_q): queue of outstanding (outgoing) requests * * Request received over the client connection are forwarded to the server by * enqueuing the request in the chosen server's in_q. From the client's * perspective once the request is forwarded, it is outstanding and is tracked * in the client's out_q (unless the request was tagged as noreply). The server * in turn picks up requests from its own in_q in fifo order and puts them on * the wire. Once the request is outstanding on the wire, and a response is * expected for it, the server keeps track of outstanding requests it in its * own out_q. * * The server's out_q enables us to pair a request with a response while the * client's out_q enables us to pair request and response in the order in * which they are received from the client. * * * Clients Servers * . * in_q: . * out_q: req11 -> req12 . in_q: req22 * (client1) . out_q: req11 -> req21 -> req12 * . (server1) * in_q: . * out_q: req21 -> req22 -> req23 . * (client2) . * . in_q: req23 * . out_q: * . (server2) * * In the above example, client1 has two pipelined requests req11 and req12 * both of which are outstanding on the server connection server1. On the * other hand, client2 has three requests req21, req22 and req23, of which * only req21 is outstanding on the server connection while req22 and * req23 are still waiting to be put on the wire. The fifo of client's * out_q ensures that we always send back the response of request at the head * of the queue, before sending out responses of other completed requests in * the queue. */ static uint32_t nfree_connq; /* # free conn q */ static struct conn_tqh free_connq; /* free conn q */ static uint64_t ntotal_conn; /* total # connections counter from start */ static uint32_t ncurr_conn; /* current # connections */ static uint32_t ncurr_cconn; /* current # client connections */ /* * Return the context associated with this connection. */ struct context * conn_to_ctx(struct conn *conn) { struct server_pool *pool; if (conn->proxy || conn->client) { pool = conn->owner; } else { struct server *server = conn->owner; pool = server->owner; } return pool->ctx; } static struct conn * _conn_get(void) { struct conn *conn; if (!TAILQ_EMPTY(&free_connq)) { ASSERT(nfree_connq > 0); conn = TAILQ_FIRST(&free_connq); nfree_connq--; TAILQ_REMOVE(&free_connq, conn, conn_tqe); } else { conn = nc_alloc(sizeof(*conn)); if (conn == NULL) { return NULL; } } conn->owner = NULL; conn->sd = -1; /* {family, addrlen, addr} are initialized in enqueue handler */ TAILQ_INIT(&conn->imsg_q); TAILQ_INIT(&conn->omsg_q); conn->rmsg = NULL; conn->smsg = NULL; /* * Callbacks {recv, recv_next, recv_done}, {send, send_next, send_done}, * {close, active}, parse, {ref, unref}, {enqueue_inq, dequeue_inq} and * {enqueue_outq, dequeue_outq} are initialized by the wrapper. */ conn->send_bytes = 0; conn->recv_bytes = 0; conn->events = 0; conn->err = 0; conn->recv_active = 0; conn->recv_ready = 0; conn->send_active = 0; conn->send_ready = 0; conn->client = 0; conn->proxy = 0; conn->connecting = 0; conn->connected = 0; conn->eof = 0; conn->done = 0; conn->redis = 0; conn->authenticated = 0; ntotal_conn++; ncurr_conn++; return conn; } struct conn * conn_get(void *owner, bool client, bool redis) { struct conn *conn; conn = _conn_get(); if (conn == NULL) { return NULL; } /* connection either handles redis or memcache messages */ conn->redis = redis ? 1 : 0; conn->client = client ? 1 : 0; if (conn->client) { /* * client receives a request, possibly parsing it, and sends a * response downstream. */ conn->recv = msg_recv; conn->recv_next = req_recv_next; conn->recv_done = req_recv_done; conn->send = msg_send; conn->send_next = rsp_send_next; conn->send_done = rsp_send_done; conn->close = client_close; conn->active = client_active; conn->ref = client_ref; conn->unref = client_unref; conn->enqueue_inq = NULL; conn->dequeue_inq = NULL; conn->enqueue_outq = req_client_enqueue_omsgq; conn->dequeue_outq = req_client_dequeue_omsgq; conn->post_connect = NULL; conn->swallow_msg = NULL; ncurr_cconn++; } else { /* * server receives a response, possibly parsing it, and sends a * request upstream. */ conn->recv = msg_recv; conn->recv_next = rsp_recv_next; conn->recv_done = rsp_recv_done; conn->send = msg_send; conn->send_next = req_send_next; conn->send_done = req_send_done; conn->close = server_close; conn->active = server_active; conn->ref = server_ref; conn->unref = server_unref; conn->enqueue_inq = req_server_enqueue_imsgq; conn->dequeue_inq = req_server_dequeue_imsgq; conn->enqueue_outq = req_server_enqueue_omsgq; conn->dequeue_outq = req_server_dequeue_omsgq; if (redis) { conn->post_connect = redis_post_connect; conn->swallow_msg = redis_swallow_msg; } else { conn->post_connect = memcache_post_connect; conn->swallow_msg = memcache_swallow_msg; } } conn->ref(conn, owner); log_debug(LOG_VVERB, "get conn %p client %d", conn, conn->client); return conn; } struct conn * conn_get_proxy(void *owner) { struct server_pool *pool = owner; struct conn *conn; conn = _conn_get(); if (conn == NULL) { return NULL; } conn->redis = pool->redis; conn->proxy = 1; conn->recv = proxy_recv; conn->recv_next = NULL; conn->recv_done = NULL; conn->send = NULL; conn->send_next = NULL; conn->send_done = NULL; conn->close = proxy_close; conn->active = NULL; conn->ref = proxy_ref; conn->unref = proxy_unref; conn->enqueue_inq = NULL; conn->dequeue_inq = NULL; conn->enqueue_outq = NULL; conn->dequeue_outq = NULL; conn->ref(conn, owner); log_debug(LOG_VVERB, "get conn %p proxy %d", conn, conn->proxy); return conn; } static void conn_free(struct conn *conn) { log_debug(LOG_VVERB, "free conn %p", conn); nc_free(conn); } void conn_put(struct conn *conn) { ASSERT(conn->sd < 0); ASSERT(conn->owner == NULL); log_debug(LOG_VVERB, "put conn %p", conn); nfree_connq++; TAILQ_INSERT_HEAD(&free_connq, conn, conn_tqe); if (conn->client) { ncurr_cconn--; } ncurr_conn--; } void conn_init(void) { log_debug(LOG_DEBUG, "conn size %d", sizeof(struct conn)); nfree_connq = 0; TAILQ_INIT(&free_connq); } void conn_deinit(void) { struct conn *conn, *nconn; /* current and next connection */ for (conn = TAILQ_FIRST(&free_connq); conn != NULL; conn = nconn, nfree_connq--) { ASSERT(nfree_connq > 0); nconn = TAILQ_NEXT(conn, conn_tqe); conn_free(conn); } ASSERT(nfree_connq == 0); } ssize_t conn_recv(struct conn *conn, void *buf, size_t size) { ssize_t n; ASSERT(buf != NULL); ASSERT(size > 0); ASSERT(conn->recv_ready); for (;;) { n = nc_read(conn->sd, buf, size); log_debug(LOG_VERB, "recv on sd %d %zd of %zu", conn->sd, n, size); if (n > 0) { if (n < (ssize_t) size) { conn->recv_ready = 0; } conn->recv_bytes += (size_t)n; return n; } if (n == 0) { conn->recv_ready = 0; conn->eof = 1; log_debug(LOG_INFO, "recv on sd %d eof rb %zu sb %zu", conn->sd, conn->recv_bytes, conn->send_bytes); return n; } if (errno == EINTR) { log_debug(LOG_VERB, "recv on sd %d not ready - eintr", conn->sd); continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { conn->recv_ready = 0; log_debug(LOG_VERB, "recv on sd %d not ready - eagain", conn->sd); return NC_EAGAIN; } else { conn->recv_ready = 0; conn->err = errno; log_error("recv on sd %d failed: %s", conn->sd, strerror(errno)); return NC_ERROR; } } NOT_REACHED(); return NC_ERROR; } ssize_t conn_sendv(struct conn *conn, struct array *sendv, size_t nsend) { ssize_t n; ASSERT(array_n(sendv) > 0); ASSERT(nsend != 0); ASSERT(conn->send_ready); for (;;) { n = nc_writev(conn->sd, sendv->elem, sendv->nelem); log_debug(LOG_VERB, "sendv on sd %d %zd of %zu in %"PRIu32" buffers", conn->sd, n, nsend, sendv->nelem); if (n > 0) { if (n < (ssize_t) nsend) { conn->send_ready = 0; } conn->send_bytes += (size_t)n; return n; } if (n == 0) { log_warn("sendv on sd %d returned zero", conn->sd); conn->send_ready = 0; return 0; } if (errno == EINTR) { log_debug(LOG_VERB, "sendv on sd %d not ready - eintr", conn->sd); continue; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { conn->send_ready = 0; log_debug(LOG_VERB, "sendv on sd %d not ready - eagain", conn->sd); return NC_EAGAIN; } else { conn->send_ready = 0; conn->err = errno; log_error("sendv on sd %d failed: %s", conn->sd, strerror(errno)); return NC_ERROR; } } NOT_REACHED(); return NC_ERROR; } uint32_t conn_ncurr_conn(void) { return ncurr_conn; } uint64_t conn_ntotal_conn(void) { return ntotal_conn; } uint32_t conn_ncurr_cconn(void) { return ncurr_cconn; } /* * Returns true if the connection is authenticated or doesn't require * authentication, otherwise return false */ bool conn_authenticated(struct conn *conn) { struct server_pool *pool; ASSERT(!conn->proxy); pool = conn->client ? conn->owner : ((struct server *)conn->owner)->owner; if (!pool->require_auth) { return true; } if (!conn->authenticated) { return false; } return true; } nutcracker-0.4.1+dfsg/src/nc_log.c0000664000000000000000000001607312542173515015504 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include static struct logger logger; int log_init(int level, char *name) { struct logger *l = &logger; l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB)); l->name = name; if (name == NULL || !strlen(name)) { l->fd = STDERR_FILENO; } else { l->fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0644); if (l->fd < 0) { log_stderr("opening log file '%s' failed: %s", name, strerror(errno)); return -1; } } return 0; } void log_deinit(void) { struct logger *l = &logger; if (l->fd < 0 || l->fd == STDERR_FILENO) { return; } close(l->fd); } void log_reopen(void) { struct logger *l = &logger; if (l->fd != STDERR_FILENO) { close(l->fd); l->fd = open(l->name, O_WRONLY | O_APPEND | O_CREAT, 0644); if (l->fd < 0) { log_stderr_safe("reopening log file '%s' failed, ignored: %s", l->name, strerror(errno)); } } } void log_level_up(void) { struct logger *l = &logger; if (l->level < LOG_PVERB) { l->level++; log_safe("up log level to %d", l->level); } } void log_level_down(void) { struct logger *l = &logger; if (l->level > LOG_EMERG) { l->level--; log_safe("down log level to %d", l->level); } } void log_level_set(int level) { struct logger *l = &logger; l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB)); loga("set log level to %d", l->level); } void log_stacktrace(void) { struct logger *l = &logger; if (l->fd < 0) { return; } nc_stacktrace_fd(l->fd); } int log_loggable(int level) { struct logger *l = &logger; if (level > l->level) { return 0; } return 1; } void _log(const char *file, int line, int panic, const char *fmt, ...) { struct logger *l = &logger; int len, size, errno_save; char buf[LOG_MAX_LEN]; va_list args; ssize_t n; struct timeval tv; if (l->fd < 0) { return; } errno_save = errno; len = 0; /* length of output buffer */ size = LOG_MAX_LEN; /* size of output buffer */ gettimeofday(&tv, NULL); buf[len++] = '['; len += nc_strftime(buf + len, size - len, "%Y-%m-%d %H:%M:%S.", localtime(&tv.tv_sec)); len += nc_scnprintf(buf + len, size - len, "%03ld", tv.tv_usec/1000); len += nc_scnprintf(buf + len, size - len, "] %s:%d ", file, line); va_start(args, fmt); len += nc_vscnprintf(buf + len, size - len, fmt, args); va_end(args); buf[len++] = '\n'; n = nc_write(l->fd, buf, len); if (n < 0) { l->nerror++; } errno = errno_save; if (panic) { abort(); } } void _log_stderr(const char *fmt, ...) { struct logger *l = &logger; int len, size, errno_save; char buf[4 * LOG_MAX_LEN]; va_list args; ssize_t n; errno_save = errno; len = 0; /* length of output buffer */ size = 4 * LOG_MAX_LEN; /* size of output buffer */ va_start(args, fmt); len += nc_vscnprintf(buf, size, fmt, args); va_end(args); buf[len++] = '\n'; n = nc_write(STDERR_FILENO, buf, len); if (n < 0) { l->nerror++; } errno = errno_save; } /* * Hexadecimal dump in the canonical hex + ascii display * See -C option in man hexdump */ void _log_hexdump(const char *file, int line, char *data, int datalen, const char *fmt, ...) { struct logger *l = &logger; char buf[8 * LOG_MAX_LEN]; int i, off, len, size, errno_save; ssize_t n; if (l->fd < 0) { return; } /* log hexdump */ errno_save = errno; off = 0; /* data offset */ len = 0; /* length of output buffer */ size = 8 * LOG_MAX_LEN; /* size of output buffer */ while (datalen != 0 && (len < size - 1)) { char *save, *str; unsigned char c; int savelen; len += nc_scnprintf(buf + len, size - len, "%08x ", off); save = data; savelen = datalen; for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) { c = (unsigned char)(*data); str = (i == 7) ? " " : " "; len += nc_scnprintf(buf + len, size - len, "%02x%s", c, str); } for ( ; i < 16; i++) { str = (i == 7) ? " " : " "; len += nc_scnprintf(buf + len, size - len, " %s", str); } data = save; datalen = savelen; len += nc_scnprintf(buf + len, size - len, " |"); for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) { c = (unsigned char)(isprint(*data) ? *data : '.'); len += nc_scnprintf(buf + len, size - len, "%c", c); } len += nc_scnprintf(buf + len, size - len, "|\n"); off += 16; } n = nc_write(l->fd, buf, len); if (n < 0) { l->nerror++; } if (len >= size - 1) { n = nc_write(l->fd, "\n", 1); if (n < 0) { l->nerror++; } } errno = errno_save; } void _log_safe(const char *fmt, ...) { struct logger *l = &logger; int len, size, errno_save; char buf[LOG_MAX_LEN]; va_list args; ssize_t n; if (l->fd < 0) { return; } errno_save = errno; len = 0; /* length of output buffer */ size = LOG_MAX_LEN; /* size of output buffer */ len += nc_safe_snprintf(buf + len, size - len, "[.......................] "); va_start(args, fmt); len += nc_safe_vsnprintf(buf + len, size - len, fmt, args); va_end(args); buf[len++] = '\n'; n = nc_write(l->fd, buf, len); if (n < 0) { l->nerror++; } errno = errno_save; } void _log_stderr_safe(const char *fmt, ...) { struct logger *l = &logger; int len, size, errno_save; char buf[LOG_MAX_LEN]; va_list args; ssize_t n; errno_save = errno; len = 0; /* length of output buffer */ size = LOG_MAX_LEN; /* size of output buffer */ len += nc_safe_snprintf(buf + len, size - len, "[.......................] "); va_start(args, fmt); len += nc_safe_vsnprintf(buf + len, size - len, fmt, args); va_end(args); buf[len++] = '\n'; n = nc_write(STDERR_FILENO, buf, len); if (n < 0) { l->nerror++; } errno = errno_save; } nutcracker-0.4.1+dfsg/src/nc_core.c0000664000000000000000000002152512542173515015651 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include static uint32_t ctx_id; /* context generation */ static rstatus_t core_calc_connections(struct context *ctx) { int status; struct rlimit limit; status = getrlimit(RLIMIT_NOFILE, &limit); if (status < 0) { log_error("getrlimit failed: %s", strerror(errno)); return NC_ERROR; } ctx->max_nfd = (uint32_t)limit.rlim_cur; ctx->max_ncconn = ctx->max_nfd - ctx->max_nsconn - RESERVED_FDS; log_debug(LOG_NOTICE, "max fds %"PRIu32" max client conns %"PRIu32" " "max server conns %"PRIu32"", ctx->max_nfd, ctx->max_ncconn, ctx->max_nsconn); return NC_OK; } static struct context * core_ctx_create(struct instance *nci) { rstatus_t status; struct context *ctx; ctx = nc_alloc(sizeof(*ctx)); if (ctx == NULL) { return NULL; } ctx->id = ++ctx_id; ctx->cf = NULL; ctx->stats = NULL; ctx->evb = NULL; array_null(&ctx->pool); ctx->max_timeout = nci->stats_interval; ctx->timeout = ctx->max_timeout; ctx->max_nfd = 0; ctx->max_ncconn = 0; ctx->max_nsconn = 0; /* parse and create configuration */ ctx->cf = conf_create(nci->conf_filename); if (ctx->cf == NULL) { nc_free(ctx); return NULL; } /* initialize server pool from configuration */ status = server_pool_init(&ctx->pool, &ctx->cf->pool, ctx); if (status != NC_OK) { conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* * Get rlimit and calculate max client connections after we have * calculated max server connections */ status = core_calc_connections(ctx); if (status != NC_OK) { server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* create stats per server pool */ ctx->stats = stats_create(nci->stats_port, nci->stats_addr, nci->stats_interval, nci->hostname, &ctx->pool); if (ctx->stats == NULL) { server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* initialize event handling for client, proxy and server */ ctx->evb = event_base_create(EVENT_SIZE, &core_core); if (ctx->evb == NULL) { stats_destroy(ctx->stats); server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* preconnect? servers in server pool */ status = server_pool_preconnect(ctx); if (status != NC_OK) { server_pool_disconnect(ctx); event_base_destroy(ctx->evb); stats_destroy(ctx->stats); server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } /* initialize proxy per server pool */ status = proxy_init(ctx); if (status != NC_OK) { server_pool_disconnect(ctx); event_base_destroy(ctx->evb); stats_destroy(ctx->stats); server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); return NULL; } log_debug(LOG_VVERB, "created ctx %p id %"PRIu32"", ctx, ctx->id); return ctx; } static void core_ctx_destroy(struct context *ctx) { log_debug(LOG_VVERB, "destroy ctx %p id %"PRIu32"", ctx, ctx->id); proxy_deinit(ctx); server_pool_disconnect(ctx); event_base_destroy(ctx->evb); stats_destroy(ctx->stats); server_pool_deinit(&ctx->pool); conf_destroy(ctx->cf); nc_free(ctx); } struct context * core_start(struct instance *nci) { struct context *ctx; mbuf_init(nci); msg_init(); conn_init(); ctx = core_ctx_create(nci); if (ctx != NULL) { nci->ctx = ctx; return ctx; } conn_deinit(); msg_deinit(); mbuf_deinit(); return NULL; } void core_stop(struct context *ctx) { conn_deinit(); msg_deinit(); mbuf_deinit(); core_ctx_destroy(ctx); } static rstatus_t core_recv(struct context *ctx, struct conn *conn) { rstatus_t status; status = conn->recv(ctx, conn); if (status != NC_OK) { log_debug(LOG_INFO, "recv on %c %d failed: %s", conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd, strerror(errno)); } return status; } static rstatus_t core_send(struct context *ctx, struct conn *conn) { rstatus_t status; status = conn->send(ctx, conn); if (status != NC_OK) { log_debug(LOG_INFO, "send on %c %d failed: status: %d errno: %d %s", conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd, status, errno, strerror(errno)); } return status; } static void core_close(struct context *ctx, struct conn *conn) { rstatus_t status; char type, *addrstr; ASSERT(conn->sd > 0); if (conn->client) { type = 'c'; addrstr = nc_unresolve_peer_desc(conn->sd); } else { type = conn->proxy ? 'p' : 's'; addrstr = nc_unresolve_addr(conn->addr, conn->addrlen); } log_debug(LOG_NOTICE, "close %c %d '%s' on event %04"PRIX32" eof %d done " "%d rb %zu sb %zu%c %s", type, conn->sd, addrstr, conn->events, conn->eof, conn->done, conn->recv_bytes, conn->send_bytes, conn->err ? ':' : ' ', conn->err ? strerror(conn->err) : ""); status = event_del_conn(ctx->evb, conn); if (status < 0) { log_warn("event del conn %c %d failed, ignored: %s", type, conn->sd, strerror(errno)); } conn->close(ctx, conn); } static void core_error(struct context *ctx, struct conn *conn) { rstatus_t status; char type = conn->client ? 'c' : (conn->proxy ? 'p' : 's'); status = nc_get_soerror(conn->sd); if (status < 0) { log_warn("get soerr on %c %d failed, ignored: %s", type, conn->sd, strerror(errno)); } conn->err = errno; core_close(ctx, conn); } static void core_timeout(struct context *ctx) { for (;;) { struct msg *msg; struct conn *conn; int64_t now, then; msg = msg_tmo_min(); if (msg == NULL) { ctx->timeout = ctx->max_timeout; return; } /* skip over req that are in-error or done */ if (msg->error || msg->done) { msg_tmo_delete(msg); continue; } /* * timeout expired req and all the outstanding req on the timing * out server */ conn = msg->tmo_rbe.data; then = msg->tmo_rbe.key; now = nc_msec_now(); if (now < then) { int delta = (int)(then - now); ctx->timeout = MIN(delta, ctx->max_timeout); return; } log_debug(LOG_INFO, "req %"PRIu64" on s %d timedout", msg->id, conn->sd); msg_tmo_delete(msg); conn->err = ETIMEDOUT; core_close(ctx, conn); } } rstatus_t core_core(void *arg, uint32_t events) { rstatus_t status; struct conn *conn = arg; struct context *ctx; if (conn->owner == NULL) { log_warn("conn is already unrefed!"); return NC_OK; } ctx = conn_to_ctx(conn); log_debug(LOG_VVERB, "event %04"PRIX32" on %c %d", events, conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd); conn->events = events; /* error takes precedence over read | write */ if (events & EVENT_ERR) { core_error(ctx, conn); return NC_ERROR; } /* read takes precedence over write */ if (events & EVENT_READ) { status = core_recv(ctx, conn); if (status != NC_OK || conn->done || conn->err) { core_close(ctx, conn); return NC_ERROR; } } if (events & EVENT_WRITE) { status = core_send(ctx, conn); if (status != NC_OK || conn->done || conn->err) { core_close(ctx, conn); return NC_ERROR; } } return NC_OK; } rstatus_t core_loop(struct context *ctx) { int nsd; nsd = event_wait(ctx->evb, ctx->timeout); if (nsd < 0) { return nsd; } core_timeout(ctx); stats_swap(ctx->stats); return NC_OK; } nutcracker-0.4.1+dfsg/src/hashkit/0000775000000000000000000000000012542173515015523 5ustar rootrootnutcracker-0.4.1+dfsg/src/hashkit/nc_random.c0000664000000000000000000001142212542173515017627 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #define RANDOM_CONTINUUM_ADDITION 10 /* # extra slots to build into continuum */ #define RANDOM_POINTS_PER_SERVER 1 rstatus_t random_update(struct server_pool *pool) { uint32_t nserver; /* # server - live and dead */ uint32_t nlive_server; /* # live server */ uint32_t pointer_per_server; /* pointers per server proportional to weight */ uint32_t pointer_counter; /* # pointers on continuum */ uint32_t points_per_server; /* points per server */ uint32_t continuum_index; /* continuum index */ uint32_t continuum_addition; /* extra space in the continuum */ uint32_t server_index; /* server index */ int64_t now; /* current timestamp in usec */ now = nc_usec_now(); if (now < 0) { return NC_ERROR; } nserver = array_n(&pool->server); nlive_server = 0; pool->next_rebuild = 0LL; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts) { if (server->next_retry <= now) { server->next_retry = 0LL; nlive_server++; } else if (pool->next_rebuild == 0LL || server->next_retry < pool->next_rebuild) { pool->next_rebuild = server->next_retry; } } else { nlive_server++; } } pool->nlive_server = nlive_server; if (nlive_server == 0) { ASSERT(pool->continuum != NULL); ASSERT(pool->ncontinuum != 0); log_debug(LOG_DEBUG, "no live servers for pool %"PRIu32" '%.*s'", pool->idx, pool->name.len, pool->name.data); return NC_OK; } log_debug(LOG_DEBUG, "%"PRIu32" of %"PRIu32" servers are live for pool " "%"PRIu32" '%.*s'", nlive_server, nserver, pool->idx, pool->name.len, pool->name.data); continuum_addition = RANDOM_CONTINUUM_ADDITION; points_per_server = RANDOM_POINTS_PER_SERVER; /* * Allocate the continuum for the pool, the first time, and every time we * add a new server to the pool */ if (nlive_server > pool->nserver_continuum) { struct continuum *continuum; uint32_t nserver_continuum = nlive_server + RANDOM_CONTINUUM_ADDITION; uint32_t ncontinuum = nserver_continuum * RANDOM_POINTS_PER_SERVER; continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum); if (continuum == NULL) { return NC_ENOMEM; } srandom((uint32_t)time(NULL)); pool->continuum = continuum; pool->nserver_continuum = nserver_continuum; /* pool->ncontinuum is initialized later as it could be <= ncontinuum */ } /* update the continuum with the servers that are live */ continuum_index = 0; pointer_counter = 0; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts && server->next_retry > now) { continue; } pointer_per_server = 1; pool->continuum[continuum_index].index = server_index; pool->continuum[continuum_index++].value = 0; pointer_counter += pointer_per_server; } pool->ncontinuum = pointer_counter; log_debug(LOG_VERB, "updated pool %"PRIu32" '%.*s' with %"PRIu32" of " "%"PRIu32" servers live in %"PRIu32" slots and %"PRIu32" " "active points in %"PRIu32" slots", pool->idx, pool->name.len, pool->name.data, nlive_server, nserver, pool->nserver_continuum, pool->ncontinuum, (pool->nserver_continuum + continuum_addition) * points_per_server); return NC_OK; } uint32_t random_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash) { struct continuum *c; ASSERT(continuum != NULL); ASSERT(ncontinuum != 0); c = continuum + random() % ncontinuum; return c->index; } nutcracker-0.4.1+dfsg/src/hashkit/nc_fnv.c0000664000000000000000000000362012542173515017141 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include static uint64_t FNV_64_INIT = UINT64_C(0xcbf29ce484222325); static uint64_t FNV_64_PRIME = UINT64_C(0x100000001b3); static uint32_t FNV_32_INIT = 2166136261UL; static uint32_t FNV_32_PRIME = 16777619; uint32_t hash_fnv1_64(const char *key, size_t key_length) { uint64_t hash = FNV_64_INIT; size_t x; for (x = 0; x < key_length; x++) { hash *= FNV_64_PRIME; hash ^= (uint64_t)key[x]; } return (uint32_t)hash; } uint32_t hash_fnv1a_64(const char *key, size_t key_length) { uint32_t hash = (uint32_t) FNV_64_INIT; size_t x; for (x = 0; x < key_length; x++) { uint32_t val = (uint32_t)key[x]; hash ^= val; hash *= (uint32_t) FNV_64_PRIME; } return hash; } uint32_t hash_fnv1_32(const char *key, size_t key_length) { uint32_t hash = FNV_32_INIT; size_t x; for (x = 0; x < key_length; x++) { uint32_t val = (uint32_t)key[x]; hash *= FNV_32_PRIME; hash ^= val; } return hash; } uint32_t hash_fnv1a_32(const char *key, size_t key_length) { uint32_t hash = FNV_32_INIT; size_t x; for (x= 0; x < key_length; x++) { uint32_t val = (uint32_t)key[x]; hash ^= val; hash *= FNV_32_PRIME; } return hash; } nutcracker-0.4.1+dfsg/src/hashkit/nc_murmur.c0000664000000000000000000000444612542173515017706 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * "Murmur" hash provided by Austin, tanjent@gmail.com * http://murmurhash.googlepages.com/ * * Note - This code makes a few assumptions about how your machine behaves - * * 1. We can read a 4-byte value from any address without crashing * 2. sizeof(int) == 4 * * And it has a few limitations - * 1. It will not work incrementally. * 2. It will not produce the same results on little-endian and big-endian * machines. * * Updated to murmur2 hash - BP */ #include uint32_t hash_murmur(const char *key, size_t length) { /* * 'm' and 'r' are mixing constants generated offline. They're not * really 'magic', they just happen to work well. */ const unsigned int m = 0x5bd1e995; const uint32_t seed = (0xdeadbeef * (uint32_t)length); const int r = 24; /* Initialize the hash to a 'random' value */ uint32_t h = seed ^ (uint32_t)length; /* Mix 4 bytes at a time into the hash */ const unsigned char * data = (const unsigned char *)key; while (length >= 4) { unsigned int k = *(unsigned int *)data; k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; data += 4; length -= 4; } /* Handle the last few bytes of the input array */ switch(length) { case 3: h ^= ((uint32_t)data[2]) << 16; case 2: h ^= ((uint32_t)data[1]) << 8; case 1: h ^= data[0]; h *= m; default: break; }; /* * Do a few final mixes of the hash to ensure the last few bytes are * well-incorporated. */ h ^= h >> 13; h *= m; h ^= h >> 15; return h; } nutcracker-0.4.1+dfsg/src/hashkit/nc_md5.c0000664000000000000000000002243412542173515017041 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include /* * This is an OpenSSL-compatible implementation of the RSA Data Security, Inc. * MD5 Message-Digest Algorithm (RFC 1321). * * Homepage: http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5 * * Author: Alexander Peslyak, better known as Solar Designer */ #include typedef unsigned int MD5_u32plus; typedef struct { MD5_u32plus lo, hi; MD5_u32plus a, b, c, d; unsigned char buffer[64]; MD5_u32plus block[16]; } MD5_CTX; /* * The basic MD5 functions. * * F and G are optimized compared to their RFC 1321 definitions for * architectures that lack an AND-NOT instruction, just like in Colin Plumb's * implementation. */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) ((y) ^ ((z) & ((x) ^ (y)))) #define H(x, y, z) ((x) ^ (y) ^ (z)) #define I(x, y, z) ((y) ^ ((x) | ~(z))) /* * The MD5 transformation for all four rounds. */ #define STEP(f, a, b, c, d, x, t, s) \ (a) += f((b), (c), (d)) + (x) + (t); \ (a) = (((a) << (s)) | (((a) & 0xffffffff) >> (32 - (s)))); \ (a) += (b); /* * SET reads 4 input bytes in little-endian byte order and stores them * in a properly aligned word in host byte order. * * The check for little-endian architectures that tolerate unaligned * memory accesses is just an optimization. Nothing will break if it * doesn't work. */ #if defined(__i386__) || defined(__x86_64__) || defined(__vax__) #define SET(n) \ (*(MD5_u32plus *)&ptr[(n) * 4]) #define GET(n) \ SET(n) #else #define SET(n) \ (ctx->block[(n)] = \ (MD5_u32plus)ptr[(n) * 4] | \ ((MD5_u32plus)ptr[(n) * 4 + 1] << 8) | \ ((MD5_u32plus)ptr[(n) * 4 + 2] << 16) | \ ((MD5_u32plus)ptr[(n) * 4 + 3] << 24)) #define GET(n) \ (ctx->block[(n)]) #endif /* * This processes one or more 64-byte data blocks, but does NOT update * the bit counters. There are no alignment requirements. */ static void * body(MD5_CTX *ctx, void *data, unsigned long size) { unsigned char *ptr; MD5_u32plus a, b, c, d; MD5_u32plus saved_a, saved_b, saved_c, saved_d; ptr = data; a = ctx->a; b = ctx->b; c = ctx->c; d = ctx->d; do { saved_a = a; saved_b = b; saved_c = c; saved_d = d; /* Round 1 */ STEP(F, a, b, c, d, SET(0), 0xd76aa478, 7) STEP(F, d, a, b, c, SET(1), 0xe8c7b756, 12) STEP(F, c, d, a, b, SET(2), 0x242070db, 17) STEP(F, b, c, d, a, SET(3), 0xc1bdceee, 22) STEP(F, a, b, c, d, SET(4), 0xf57c0faf, 7) STEP(F, d, a, b, c, SET(5), 0x4787c62a, 12) STEP(F, c, d, a, b, SET(6), 0xa8304613, 17) STEP(F, b, c, d, a, SET(7), 0xfd469501, 22) STEP(F, a, b, c, d, SET(8), 0x698098d8, 7) STEP(F, d, a, b, c, SET(9), 0x8b44f7af, 12) STEP(F, c, d, a, b, SET(10), 0xffff5bb1, 17) STEP(F, b, c, d, a, SET(11), 0x895cd7be, 22) STEP(F, a, b, c, d, SET(12), 0x6b901122, 7) STEP(F, d, a, b, c, SET(13), 0xfd987193, 12) STEP(F, c, d, a, b, SET(14), 0xa679438e, 17) STEP(F, b, c, d, a, SET(15), 0x49b40821, 22) /* Round 2 */ STEP(G, a, b, c, d, GET(1), 0xf61e2562, 5) STEP(G, d, a, b, c, GET(6), 0xc040b340, 9) STEP(G, c, d, a, b, GET(11), 0x265e5a51, 14) STEP(G, b, c, d, a, GET(0), 0xe9b6c7aa, 20) STEP(G, a, b, c, d, GET(5), 0xd62f105d, 5) STEP(G, d, a, b, c, GET(10), 0x02441453, 9) STEP(G, c, d, a, b, GET(15), 0xd8a1e681, 14) STEP(G, b, c, d, a, GET(4), 0xe7d3fbc8, 20) STEP(G, a, b, c, d, GET(9), 0x21e1cde6, 5) STEP(G, d, a, b, c, GET(14), 0xc33707d6, 9) STEP(G, c, d, a, b, GET(3), 0xf4d50d87, 14) STEP(G, b, c, d, a, GET(8), 0x455a14ed, 20) STEP(G, a, b, c, d, GET(13), 0xa9e3e905, 5) STEP(G, d, a, b, c, GET(2), 0xfcefa3f8, 9) STEP(G, c, d, a, b, GET(7), 0x676f02d9, 14) STEP(G, b, c, d, a, GET(12), 0x8d2a4c8a, 20) /* Round 3 */ STEP(H, a, b, c, d, GET(5), 0xfffa3942, 4) STEP(H, d, a, b, c, GET(8), 0x8771f681, 11) STEP(H, c, d, a, b, GET(11), 0x6d9d6122, 16) STEP(H, b, c, d, a, GET(14), 0xfde5380c, 23) STEP(H, a, b, c, d, GET(1), 0xa4beea44, 4) STEP(H, d, a, b, c, GET(4), 0x4bdecfa9, 11) STEP(H, c, d, a, b, GET(7), 0xf6bb4b60, 16) STEP(H, b, c, d, a, GET(10), 0xbebfbc70, 23) STEP(H, a, b, c, d, GET(13), 0x289b7ec6, 4) STEP(H, d, a, b, c, GET(0), 0xeaa127fa, 11) STEP(H, c, d, a, b, GET(3), 0xd4ef3085, 16) STEP(H, b, c, d, a, GET(6), 0x04881d05, 23) STEP(H, a, b, c, d, GET(9), 0xd9d4d039, 4) STEP(H, d, a, b, c, GET(12), 0xe6db99e5, 11) STEP(H, c, d, a, b, GET(15), 0x1fa27cf8, 16) STEP(H, b, c, d, a, GET(2), 0xc4ac5665, 23) /* Round 4 */ STEP(I, a, b, c, d, GET(0), 0xf4292244, 6) STEP(I, d, a, b, c, GET(7), 0x432aff97, 10) STEP(I, c, d, a, b, GET(14), 0xab9423a7, 15) STEP(I, b, c, d, a, GET(5), 0xfc93a039, 21) STEP(I, a, b, c, d, GET(12), 0x655b59c3, 6) STEP(I, d, a, b, c, GET(3), 0x8f0ccc92, 10) STEP(I, c, d, a, b, GET(10), 0xffeff47d, 15) STEP(I, b, c, d, a, GET(1), 0x85845dd1, 21) STEP(I, a, b, c, d, GET(8), 0x6fa87e4f, 6) STEP(I, d, a, b, c, GET(15), 0xfe2ce6e0, 10) STEP(I, c, d, a, b, GET(6), 0xa3014314, 15) STEP(I, b, c, d, a, GET(13), 0x4e0811a1, 21) STEP(I, a, b, c, d, GET(4), 0xf7537e82, 6) STEP(I, d, a, b, c, GET(11), 0xbd3af235, 10) STEP(I, c, d, a, b, GET(2), 0x2ad7d2bb, 15) STEP(I, b, c, d, a, GET(9), 0xeb86d391, 21) a += saved_a; b += saved_b; c += saved_c; d += saved_d; ptr += 64; } while (size -= 64); ctx->a = a; ctx->b = b; ctx->c = c; ctx->d = d; return ptr; } void MD5_Init(MD5_CTX *ctx) { ctx->a = 0x67452301; ctx->b = 0xefcdab89; ctx->c = 0x98badcfe; ctx->d = 0x10325476; ctx->lo = 0; ctx->hi = 0; } void MD5_Update(MD5_CTX *ctx, void *data, unsigned long size) { MD5_u32plus saved_lo; unsigned long used, free; saved_lo = ctx->lo; if ((ctx->lo = (saved_lo + size) & 0x1fffffff) < saved_lo) { ctx->hi++; } ctx->hi += size >> 29; used = saved_lo & 0x3f; if (used) { free = 64 - used; if (size < free) { memcpy(&ctx->buffer[used], data, size); return; } memcpy(&ctx->buffer[used], data, free); data = (unsigned char *)data + free; size -= free; body(ctx, ctx->buffer, 64); } if (size >= 64) { data = body(ctx, data, size & ~(unsigned long)0x3f); size &= 0x3f; } memcpy(ctx->buffer, data, size); } void MD5_Final(unsigned char *result, MD5_CTX *ctx) { unsigned long used, free; used = ctx->lo & 0x3f; ctx->buffer[used++] = 0x80; free = 64 - used; if (free < 8) { memset(&ctx->buffer[used], 0, free); body(ctx, ctx->buffer, 64); used = 0; free = 64; } memset(&ctx->buffer[used], 0, free - 8); ctx->lo <<= 3; ctx->buffer[56] = ctx->lo; ctx->buffer[57] = ctx->lo >> 8; ctx->buffer[58] = ctx->lo >> 16; ctx->buffer[59] = ctx->lo >> 24; ctx->buffer[60] = ctx->hi; ctx->buffer[61] = ctx->hi >> 8; ctx->buffer[62] = ctx->hi >> 16; ctx->buffer[63] = ctx->hi >> 24; body(ctx, ctx->buffer, 64); result[0] = ctx->a; result[1] = ctx->a >> 8; result[2] = ctx->a >> 16; result[3] = ctx->a >> 24; result[4] = ctx->b; result[5] = ctx->b >> 8; result[6] = ctx->b >> 16; result[7] = ctx->b >> 24; result[8] = ctx->c; result[9] = ctx->c >> 8; result[10] = ctx->c >> 16; result[11] = ctx->c >> 24; result[12] = ctx->d; result[13] = ctx->d >> 8; result[14] = ctx->d >> 16; result[15] = ctx->d >> 24; memset(ctx, 0, sizeof(*ctx)); } /* * Just a simple method for getting the signature * result must be == 16 */ void md5_signature(unsigned char *key, unsigned long length, unsigned char *result) { MD5_CTX my_md5; MD5_Init(&my_md5); (void)MD5_Update(&my_md5, key, length); MD5_Final(result, &my_md5); } uint32_t hash_md5(const char *key, size_t key_length) { unsigned char results[16]; md5_signature((unsigned char*)key, (unsigned long)key_length, results); return ((uint32_t) (results[3] & 0xFF) << 24) | ((uint32_t) (results[2] & 0xFF) << 16) | ((uint32_t) (results[1] & 0xFF) << 8) | (results[0] & 0xFF); } nutcracker-0.4.1+dfsg/src/hashkit/nc_crc16.c0000664000000000000000000000600012542173515017261 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include static const uint16_t crc16tab[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, }; uint32_t hash_crc16(const char *key, size_t key_length) { uint64_t x; uint32_t crc = 0; for (x=0; x < key_length; x++) { crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *key++) & 0x00ff]; } return crc; } nutcracker-0.4.1+dfsg/src/hashkit/nc_modula.c0000664000000000000000000001225512542173515017635 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #define MODULA_CONTINUUM_ADDITION 10 /* # extra slots to build into continuum */ #define MODULA_POINTS_PER_SERVER 1 rstatus_t modula_update(struct server_pool *pool) { uint32_t nserver; /* # server - live and dead */ uint32_t nlive_server; /* # live server */ uint32_t pointer_per_server; /* pointers per server proportional to weight */ uint32_t pointer_counter; /* # pointers on continuum */ uint32_t points_per_server; /* points per server */ uint32_t continuum_index; /* continuum index */ uint32_t continuum_addition; /* extra space in the continuum */ uint32_t server_index; /* server index */ uint32_t weight_index; /* weight index */ uint32_t total_weight; /* total live server weight */ int64_t now; /* current timestamp in usec */ now = nc_usec_now(); if (now < 0) { return NC_ERROR; } nserver = array_n(&pool->server); nlive_server = 0; total_weight = 0; pool->next_rebuild = 0LL; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts) { if (server->next_retry <= now) { server->next_retry = 0LL; nlive_server++; } else if (pool->next_rebuild == 0LL || server->next_retry < pool->next_rebuild) { pool->next_rebuild = server->next_retry; } } else { nlive_server++; } ASSERT(server->weight > 0); /* count weight only for live servers */ if (!pool->auto_eject_hosts || server->next_retry <= now) { total_weight += server->weight; } } pool->nlive_server = nlive_server; if (nlive_server == 0) { ASSERT(pool->continuum != NULL); ASSERT(pool->ncontinuum != 0); log_debug(LOG_DEBUG, "no live servers for pool %"PRIu32" '%.*s'", pool->idx, pool->name.len, pool->name.data); return NC_OK; } log_debug(LOG_DEBUG, "%"PRIu32" of %"PRIu32" servers are live for pool " "%"PRIu32" '%.*s'", nlive_server, nserver, pool->idx, pool->name.len, pool->name.data); continuum_addition = MODULA_CONTINUUM_ADDITION; points_per_server = MODULA_POINTS_PER_SERVER; /* * Allocate the continuum for the pool, the first time, and every time we * add a new server to the pool */ if (total_weight > pool->nserver_continuum) { struct continuum *continuum; uint32_t nserver_continuum = total_weight + MODULA_CONTINUUM_ADDITION; uint32_t ncontinuum = nserver_continuum * MODULA_POINTS_PER_SERVER; continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum); if (continuum == NULL) { return NC_ENOMEM; } pool->continuum = continuum; pool->nserver_continuum = nserver_continuum; /* pool->ncontinuum is initialized later as it could be <= ncontinuum */ } /* update the continuum with the servers that are live */ continuum_index = 0; pointer_counter = 0; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts && server->next_retry > now) { continue; } for (weight_index = 0; weight_index < server->weight; weight_index++) { pointer_per_server = 1; pool->continuum[continuum_index].index = server_index; pool->continuum[continuum_index++].value = 0; pointer_counter += pointer_per_server; } } pool->ncontinuum = pointer_counter; log_debug(LOG_VERB, "updated pool %"PRIu32" '%.*s' with %"PRIu32" of " "%"PRIu32" servers live in %"PRIu32" slots and %"PRIu32" " "active points in %"PRIu32" slots", pool->idx, pool->name.len, pool->name.data, nlive_server, nserver, pool->nserver_continuum, pool->ncontinuum, (pool->nserver_continuum + continuum_addition) * points_per_server); return NC_OK; } uint32_t modula_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash) { struct continuum *c; ASSERT(continuum != NULL); ASSERT(ncontinuum != 0); c = continuum + hash % ncontinuum; return c->index; } nutcracker-0.4.1+dfsg/src/hashkit/nc_crc32.c0000664000000000000000000001151612542173515017267 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * The crc32 functions and data was originally written by Spencer * Garrett and was gleaned from the PostgreSQL source * tree via the files contrib/ltree/crc32.[ch] and from FreeBSD at * src/usr.bin/cksum/crc32.c. */ #include static const uint32_t crc32tab[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, }; /* * CRC-32 implementation compatible with libmemcached library. Unfortunately * this implementation does not return CRC-32 as per spec. */ uint32_t hash_crc32(const char *key, size_t key_length) { uint64_t x; uint32_t crc = UINT32_MAX; for (x = 0; x < key_length; x++) { crc = (crc >> 8) ^ crc32tab[(crc ^ (uint64_t)key[x]) & 0xff]; } return ((~crc) >> 16) & 0x7fff; } uint32_t hash_crc32a(const char *key, size_t key_length) { const uint8_t *p = key; uint32_t crc; crc = ~0U; while (key_length--) { crc = crc32tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); } return crc ^ ~0U; } nutcracker-0.4.1+dfsg/src/hashkit/nc_hsieh.c0000664000000000000000000000445412542173515017456 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * By Paul Hsieh (C) 2004, 2005. Covered under the Paul Hsieh * derivative license. * See: http://www.azillionmonkeys.com/qed/weblicense.html for license * details. * http://www.azillionmonkeys.com/qed/hash.html */ #include #undef get16bits #if (defined(__GNUC__) && defined(__i386__)) #define get16bits(d) (*((const uint16_t *) (d))) #endif #if !defined (get16bits) #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ +(uint32_t)(((const uint8_t *)(d))[0]) ) #endif uint32_t hash_hsieh(const char *key, size_t key_length) { uint32_t hash = 0, tmp; int rem; if (key_length <= 0 || key == NULL) { return 0; } rem = key_length & 3; key_length >>= 2; /* Main loop */ for (;key_length > 0; key_length--) { hash += get16bits (key); tmp = (get16bits (key+2) << 11) ^ hash; hash = (hash << 16) ^ tmp; key += 2*sizeof (uint16_t); hash += hash >> 11; } /* Handle end cases */ switch (rem) { case 3: hash += get16bits (key); hash ^= hash << 16; hash ^= (uint32_t)key[sizeof (uint16_t)] << 18; hash += hash >> 11; break; case 2: hash += get16bits (key); hash ^= hash << 11; hash += hash >> 17; break; case 1: hash += (unsigned char)(*key); hash ^= hash << 10; hash += hash >> 1; default: break; } /* Force "avalanching" of final 127 bits */ hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } nutcracker-0.4.1+dfsg/src/hashkit/nc_ketama.c0000664000000000000000000001754512542173515017625 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #define KETAMA_CONTINUUM_ADDITION 10 /* # extra slots to build into continuum */ #define KETAMA_POINTS_PER_SERVER 160 /* 40 points per hash */ #define KETAMA_MAX_HOSTLEN 86 static uint32_t ketama_hash(const char *key, size_t key_length, uint32_t alignment) { unsigned char results[16]; md5_signature((unsigned char*)key, key_length, results); return ((uint32_t) (results[3 + alignment * 4] & 0xFF) << 24) | ((uint32_t) (results[2 + alignment * 4] & 0xFF) << 16) | ((uint32_t) (results[1 + alignment * 4] & 0xFF) << 8) | (results[0 + alignment * 4] & 0xFF); } static int ketama_item_cmp(const void *t1, const void *t2) { const struct continuum *ct1 = t1, *ct2 = t2; if (ct1->value == ct2->value) { return 0; } else if (ct1->value > ct2->value) { return 1; } else { return -1; } } rstatus_t ketama_update(struct server_pool *pool) { uint32_t nserver; /* # server - live and dead */ uint32_t nlive_server; /* # live server */ uint32_t pointer_per_server; /* pointers per server proportional to weight */ uint32_t pointer_per_hash; /* pointers per hash */ uint32_t pointer_counter; /* # pointers on continuum */ uint32_t pointer_index; /* pointer index */ uint32_t points_per_server; /* points per server */ uint32_t continuum_index; /* continuum index */ uint32_t continuum_addition; /* extra space in the continuum */ uint32_t server_index; /* server index */ uint32_t value; /* continuum value */ uint32_t total_weight; /* total live server weight */ int64_t now; /* current timestamp in usec */ ASSERT(array_n(&pool->server) > 0); now = nc_usec_now(); if (now < 0) { return NC_ERROR; } /* * Count live servers and total weight, and also update the next time to * rebuild the distribution */ nserver = array_n(&pool->server); nlive_server = 0; total_weight = 0; pool->next_rebuild = 0LL; for (server_index = 0; server_index < nserver; server_index++) { struct server *server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts) { if (server->next_retry <= now) { server->next_retry = 0LL; nlive_server++; } else if (pool->next_rebuild == 0LL || server->next_retry < pool->next_rebuild) { pool->next_rebuild = server->next_retry; } } else { nlive_server++; } ASSERT(server->weight > 0); /* count weight only for live servers */ if (!pool->auto_eject_hosts || server->next_retry <= now) { total_weight += server->weight; } } pool->nlive_server = nlive_server; if (nlive_server == 0) { log_debug(LOG_DEBUG, "no live servers for pool %"PRIu32" '%.*s'", pool->idx, pool->name.len, pool->name.data); return NC_OK; } log_debug(LOG_DEBUG, "%"PRIu32" of %"PRIu32" servers are live for pool " "%"PRIu32" '%.*s'", nlive_server, nserver, pool->idx, pool->name.len, pool->name.data); continuum_addition = KETAMA_CONTINUUM_ADDITION; points_per_server = KETAMA_POINTS_PER_SERVER; /* * Allocate the continuum for the pool, the first time, and every time we * add a new server to the pool */ if (nlive_server > pool->nserver_continuum) { struct continuum *continuum; uint32_t nserver_continuum = nlive_server + continuum_addition; uint32_t ncontinuum = nserver_continuum * points_per_server; continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum); if (continuum == NULL) { return NC_ENOMEM; } pool->continuum = continuum; pool->nserver_continuum = nserver_continuum; /* pool->ncontinuum is initialized later as it could be <= ncontinuum */ } /* * Build a continuum with the servers that are live and points from * these servers that are proportial to their weight */ continuum_index = 0; pointer_counter = 0; for (server_index = 0; server_index < nserver; server_index++) { struct server *server; float pct; server = array_get(&pool->server, server_index); if (pool->auto_eject_hosts && server->next_retry > now) { continue; } pct = (float)server->weight / (float)total_weight; pointer_per_server = (uint32_t) ((floorf((float) (pct * KETAMA_POINTS_PER_SERVER / 4 * (float)nlive_server + 0.0000000001))) * 4); pointer_per_hash = 4; log_debug(LOG_VERB, "%.*s weight %"PRIu32" of %"PRIu32" " "pct %0.5f points per server %"PRIu32"", server->name.len, server->name.data, server->weight, total_weight, pct, pointer_per_server); for (pointer_index = 1; pointer_index <= pointer_per_server / pointer_per_hash; pointer_index++) { char host[KETAMA_MAX_HOSTLEN]= ""; size_t hostlen; uint32_t x; hostlen = snprintf(host, KETAMA_MAX_HOSTLEN, "%.*s-%u", server->name.len, server->name.data, pointer_index - 1); for (x = 0; x < pointer_per_hash; x++) { value = ketama_hash(host, hostlen, x); pool->continuum[continuum_index].index = server_index; pool->continuum[continuum_index++].value = value; } } pointer_counter += pointer_per_server; } pool->ncontinuum = pointer_counter; qsort(pool->continuum, pool->ncontinuum, sizeof(*pool->continuum), ketama_item_cmp); for (pointer_index = 0; pointer_index < ((nlive_server * KETAMA_POINTS_PER_SERVER) - 1); pointer_index++) { if (pointer_index + 1 >= pointer_counter) { break; } ASSERT(pool->continuum[pointer_index].value <= pool->continuum[pointer_index + 1].value); } log_debug(LOG_VERB, "updated pool %"PRIu32" '%.*s' with %"PRIu32" of " "%"PRIu32" servers live in %"PRIu32" slots and %"PRIu32" " "active points in %"PRIu32" slots", pool->idx, pool->name.len, pool->name.data, nlive_server, nserver, pool->nserver_continuum, pool->ncontinuum, (pool->nserver_continuum + continuum_addition) * points_per_server); return NC_OK; } uint32_t ketama_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash) { struct continuum *begin, *end, *left, *right, *middle; ASSERT(continuum != NULL); ASSERT(ncontinuum != 0); begin = left = continuum; end = right = continuum + ncontinuum; while (left < right) { middle = left + (right - left) / 2; if (middle->value < hash) { left = middle + 1; } else { right = middle; } } if (right == end) { right = begin; } return right->index; } nutcracker-0.4.1+dfsg/src/hashkit/nc_one_at_a_time.c0000664000000000000000000000254112542173515021134 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * HashKit * Copyright (C) 2009 Brian Aker * All rights reserved. * * Use and distribution licensed under the BSD license. See * the COPYING file in the parent directory for full text. */ /* * This has is Jenkin's "One at A time Hash". * http://en.wikipedia.org/wiki/Jenkins_hash_function */ #include uint32_t hash_one_at_a_time(const char *key, size_t key_length) { const char *ptr = key; uint32_t value = 0; while (key_length--) { uint32_t val = (uint32_t) *ptr++; value += val; value += (value << 10); value ^= (value >> 6); } value += (value << 3); value ^= (value >> 11); value += (value << 15); return value; } nutcracker-0.4.1+dfsg/src/hashkit/nc_hashkit.h0000664000000000000000000000610512542173515020011 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_HASHKIT_H_ #define _NC_HASHKIT_H_ #include #include #define HASH_CODEC(ACTION) \ ACTION( HASH_ONE_AT_A_TIME, one_at_a_time ) \ ACTION( HASH_MD5, md5 ) \ ACTION( HASH_CRC16, crc16 ) \ ACTION( HASH_CRC32, crc32 ) \ ACTION( HASH_CRC32A, crc32a ) \ ACTION( HASH_FNV1_64, fnv1_64 ) \ ACTION( HASH_FNV1A_64, fnv1a_64 ) \ ACTION( HASH_FNV1_32, fnv1_32 ) \ ACTION( HASH_FNV1A_32, fnv1a_32 ) \ ACTION( HASH_HSIEH, hsieh ) \ ACTION( HASH_MURMUR, murmur ) \ ACTION( HASH_JENKINS, jenkins ) \ #define DIST_CODEC(ACTION) \ ACTION( DIST_KETAMA, ketama ) \ ACTION( DIST_MODULA, modula ) \ ACTION( DIST_RANDOM, random ) \ #define DEFINE_ACTION(_hash, _name) _hash, typedef enum hash_type { HASH_CODEC( DEFINE_ACTION ) HASH_SENTINEL } hash_type_t; #undef DEFINE_ACTION #define DEFINE_ACTION(_dist, _name) _dist, typedef enum dist_type { DIST_CODEC( DEFINE_ACTION ) DIST_SENTINEL } dist_type_t; #undef DEFINE_ACTION uint32_t hash_one_at_a_time(const char *key, size_t key_length); void md5_signature(const unsigned char *key, unsigned int length, unsigned char *result); uint32_t hash_md5(const char *key, size_t key_length); uint32_t hash_crc16(const char *key, size_t key_length); uint32_t hash_crc32(const char *key, size_t key_length); uint32_t hash_crc32a(const char *key, size_t key_length); uint32_t hash_fnv1_64(const char *key, size_t key_length); uint32_t hash_fnv1a_64(const char *key, size_t key_length); uint32_t hash_fnv1_32(const char *key, size_t key_length); uint32_t hash_fnv1a_32(const char *key, size_t key_length); uint32_t hash_hsieh(const char *key, size_t key_length); uint32_t hash_jenkins(const char *key, size_t length); uint32_t hash_murmur(const char *key, size_t length); rstatus_t ketama_update(struct server_pool *pool); uint32_t ketama_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); rstatus_t modula_update(struct server_pool *pool); uint32_t modula_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); rstatus_t random_update(struct server_pool *pool); uint32_t random_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); #endif nutcracker-0.4.1+dfsg/src/hashkit/nc_jenkins.c0000664000000000000000000001634712542173515020023 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this * code any way you wish, private, educational, or commercial. It's free. * Use for hash table lookup, or anything where one collision in 2^^32 is * acceptable. Do NOT use for cryptographic purposes. * http://burtleburtle.net/bob/hash/index.html * * Modified by Brian Pontz for libmemcached * TODO: * Add big endian support */ #include #define hashsize(n) ((uint32_t)1<<(n)) #define hashmask(n) (hashsize(n)-1) #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) #define mix(a,b,c) \ { \ a -= c; a ^= rot(c, 4); c += b; \ b -= a; b ^= rot(a, 6); a += c; \ c -= b; c ^= rot(b, 8); b += a; \ a -= c; a ^= rot(c,16); c += b; \ b -= a; b ^= rot(a,19); a += c; \ c -= b; c ^= rot(b, 4); b += a; \ } #define final(a,b,c) \ { \ c ^= b; c -= rot(b,14); \ a ^= c; a -= rot(c,11); \ b ^= a; b -= rot(a,25); \ c ^= b; c -= rot(b,16); \ a ^= c; a -= rot(c,4); \ b ^= a; b -= rot(a,14); \ c ^= b; c -= rot(b,24); \ } #define JENKINS_INITVAL 13 /* * jenkins_hash() -- hash a variable-length key into a 32-bit value * k : the key (the unaligned variable-length array of bytes) * length : the length of the key, counting by bytes * initval : can be any 4-byte value * Returns a 32-bit value. Every bit of the key affects every bit of * the return value. Two keys differing by one or two bits will have * totally different hash values. * The best hash table sizes are powers of 2. There is no need to do * mod a prime (mod is sooo slow!). If you need less than 32 bits, * use a bitmask. For example, if you need only 10 bits, do * h = (h & hashmask(10)); * In which case, the hash table should have hashsize(10) elements. */ uint32_t hash_jenkins(const char *key, size_t length) { uint32_t a,b,c; /* internal state */ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ /* Set up the internal state */ a = b = c = 0xdeadbeef + ((uint32_t)length) + JENKINS_INITVAL; u.ptr = key; #ifndef WORDS_BIGENDIAN if ((u.i & 0x3) == 0) { const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; b += k[1]; c += k[2]; mix(a,b,c); length -= 12; k += 3; } /*----------------------------- handle the last (probably partial) block */ /* * "k[2]&0xffffff" actually reads beyond the end of the string, but * then masks off the part it's not allowed to read. Because the * string is aligned, the masked-off tail is in the same word as the * rest of the string. Every machine with memory protection I've seen * does it on word boundaries, so is OK with this. But VALGRIND will * still catch it and complain. The masking trick does make the hash * noticeably faster for short strings (like English words). */ switch(length) { case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; case 8 : b+=k[1]; a+=k[0]; break; case 7 : b+=k[1]&0xffffff; a+=k[0]; break; case 6 : b+=k[1]&0xffff; a+=k[0]; break; case 5 : b+=k[1]&0xff; a+=k[0]; break; case 4 : a+=k[0]; break; case 3 : a+=k[0]&0xffffff; break; case 2 : a+=k[0]&0xffff; break; case 1 : a+=k[0]&0xff; break; case 0 : return c; /* zero length strings require no mixing */ default: return c; } } else if ((u.i & 0x1) == 0) { const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ const uint8_t *k8; /*--------------- all but last block: aligned reads and different mixing */ while (length > 12) { a += k[0] + (((uint32_t)k[1])<<16); b += k[2] + (((uint32_t)k[3])<<16); c += k[4] + (((uint32_t)k[5])<<16); mix(a,b,c); length -= 12; k += 6; } /*----------------------------- handle the last (probably partial) block */ k8 = (const uint8_t *)k; switch(length) { case 12: c+=k[4]+(((uint32_t)k[5])<<16); b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ case 10: c+=k[4]; b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 9 : c+=k8[8]; /* fall through */ case 8 : b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ case 6 : b+=k[2]; a+=k[0]+(((uint32_t)k[1])<<16); break; case 5 : b+=k8[4]; /* fall through */ case 4 : a+=k[0]+(((uint32_t)k[1])<<16); break; case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ case 2 : a+=k[0]; break; case 1 : a+=k8[0]; break; case 0 : return c; /* zero length requires no mixing */ default: return c; } } else { /* need to read the key one byte at a time */ #endif /* little endian */ const uint8_t *k = (const uint8_t *)key; /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; a += ((uint32_t)k[1])<<8; a += ((uint32_t)k[2])<<16; a += ((uint32_t)k[3])<<24; b += k[4]; b += ((uint32_t)k[5])<<8; b += ((uint32_t)k[6])<<16; b += ((uint32_t)k[7])<<24; c += k[8]; c += ((uint32_t)k[9])<<8; c += ((uint32_t)k[10])<<16; c += ((uint32_t)k[11])<<24; mix(a,b,c); length -= 12; k += 12; } /*-------------------------------- last block: affect all 32 bits of (c) */ switch(length) /* all the case statements fall through */ { case 12: c+=((uint32_t)k[11])<<24; case 11: c+=((uint32_t)k[10])<<16; case 10: c+=((uint32_t)k[9])<<8; case 9 : c+=k[8]; case 8 : b+=((uint32_t)k[7])<<24; case 7 : b+=((uint32_t)k[6])<<16; case 6 : b+=((uint32_t)k[5])<<8; case 5 : b+=k[4]; case 4 : a+=((uint32_t)k[3])<<24; case 3 : a+=((uint32_t)k[2])<<16; case 2 : a+=((uint32_t)k[1])<<8; case 1 : a+=k[0]; break; case 0 : return c; default : return c; } #ifndef WORDS_BIGENDIAN } #endif final(a,b,c); return c; } nutcracker-0.4.1+dfsg/src/hashkit/Makefile.am0000664000000000000000000000063612542173515017564 0ustar rootrootMAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I $(top_srcdir)/src AM_CFLAGS = -Wall -Wshadow AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value noinst_LIBRARIES = libhashkit.a noinst_HEADERS = nc_hashkit.h libhashkit_a_SOURCES = \ nc_crc16.c \ nc_crc32.c \ nc_fnv.c \ nc_hsieh.c \ nc_jenkins.c \ nc_ketama.c \ nc_md5.c \ nc_modula.c \ nc_murmur.c \ nc_one_at_a_time.c \ nc_random.c nutcracker-0.4.1+dfsg/src/nc_stats.c0000664000000000000000000006755512542173515016074 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include struct stats_desc { char *name; /* stats name */ char *desc; /* stats description */ }; #define DEFINE_ACTION(_name, _type, _desc) { .type = _type, .name = string(#_name) }, static struct stats_metric stats_pool_codec[] = { STATS_POOL_CODEC( DEFINE_ACTION ) }; static struct stats_metric stats_server_codec[] = { STATS_SERVER_CODEC( DEFINE_ACTION ) }; #undef DEFINE_ACTION #define DEFINE_ACTION(_name, _type, _desc) { .name = #_name, .desc = _desc }, static struct stats_desc stats_pool_desc[] = { STATS_POOL_CODEC( DEFINE_ACTION ) }; static struct stats_desc stats_server_desc[] = { STATS_SERVER_CODEC( DEFINE_ACTION ) }; #undef DEFINE_ACTION void stats_describe(void) { uint32_t i; log_stderr("pool stats:"); for (i = 0; i < NELEMS(stats_pool_desc); i++) { log_stderr(" %-20s\"%s\"", stats_pool_desc[i].name, stats_pool_desc[i].desc); } log_stderr(""); log_stderr("server stats:"); for (i = 0; i < NELEMS(stats_server_desc); i++) { log_stderr(" %-20s\"%s\"", stats_server_desc[i].name, stats_server_desc[i].desc); } } static void stats_metric_init(struct stats_metric *stm) { switch (stm->type) { case STATS_COUNTER: stm->value.counter = 0LL; break; case STATS_GAUGE: stm->value.counter = 0LL; break; case STATS_TIMESTAMP: stm->value.timestamp = 0LL; break; default: NOT_REACHED(); } } static void stats_metric_reset(struct array *stats_metric) { uint32_t i, nmetric; nmetric = array_n(stats_metric); ASSERT(nmetric == STATS_POOL_NFIELD || nmetric == STATS_SERVER_NFIELD); for (i = 0; i < nmetric; i++) { struct stats_metric *stm = array_get(stats_metric, i); stats_metric_init(stm); } } static rstatus_t stats_pool_metric_init(struct array *stats_metric) { rstatus_t status; uint32_t i, nfield = STATS_POOL_NFIELD; status = array_init(stats_metric, nfield, sizeof(struct stats_metric)); if (status != NC_OK) { return status; } for (i = 0; i < nfield; i++) { struct stats_metric *stm = array_push(stats_metric); /* initialize from pool codec first */ *stm = stats_pool_codec[i]; /* initialize individual metric */ stats_metric_init(stm); } return NC_OK; } static rstatus_t stats_server_metric_init(struct stats_server *sts) { rstatus_t status; uint32_t i, nfield = STATS_SERVER_NFIELD; status = array_init(&sts->metric, nfield, sizeof(struct stats_metric)); if (status != NC_OK) { return status; } for (i = 0; i < nfield; i++) { struct stats_metric *stm = array_push(&sts->metric); /* initialize from server codec first */ *stm = stats_server_codec[i]; /* initialize individual metric */ stats_metric_init(stm); } return NC_OK; } static void stats_metric_deinit(struct array *metric) { uint32_t i, nmetric; nmetric = array_n(metric); for (i = 0; i < nmetric; i++) { array_pop(metric); } array_deinit(metric); } static rstatus_t stats_server_init(struct stats_server *sts, struct server *s) { rstatus_t status; sts->name = s->name; array_null(&sts->metric); status = stats_server_metric_init(sts); if (status != NC_OK) { return status; } log_debug(LOG_VVVERB, "init stats server '%.*s' with %"PRIu32" metric", sts->name.len, sts->name.data, array_n(&sts->metric)); return NC_OK; } static rstatus_t stats_server_map(struct array *stats_server, struct array *server) { rstatus_t status; uint32_t i, nserver; nserver = array_n(server); ASSERT(nserver != 0); status = array_init(stats_server, nserver, sizeof(struct stats_server)); if (status != NC_OK) { return status; } for (i = 0; i < nserver; i++) { struct server *s = array_get(server, i); struct stats_server *sts = array_push(stats_server); status = stats_server_init(sts, s); if (status != NC_OK) { return status; } } log_debug(LOG_VVVERB, "map %"PRIu32" stats servers", nserver); return NC_OK; } static void stats_server_unmap(struct array *stats_server) { uint32_t i, nserver; nserver = array_n(stats_server); for (i = 0; i < nserver; i++) { struct stats_server *sts = array_pop(stats_server); stats_metric_deinit(&sts->metric); } array_deinit(stats_server); log_debug(LOG_VVVERB, "unmap %"PRIu32" stats servers", nserver); } static rstatus_t stats_pool_init(struct stats_pool *stp, struct server_pool *sp) { rstatus_t status; stp->name = sp->name; array_null(&stp->metric); array_null(&stp->server); status = stats_pool_metric_init(&stp->metric); if (status != NC_OK) { return status; } status = stats_server_map(&stp->server, &sp->server); if (status != NC_OK) { stats_metric_deinit(&stp->metric); return status; } log_debug(LOG_VVVERB, "init stats pool '%.*s' with %"PRIu32" metric and " "%"PRIu32" server", stp->name.len, stp->name.data, array_n(&stp->metric), array_n(&stp->metric)); return NC_OK; } static void stats_pool_reset(struct array *stats_pool) { uint32_t i, npool; npool = array_n(stats_pool); for (i = 0; i < npool; i++) { struct stats_pool *stp = array_get(stats_pool, i); uint32_t j, nserver; stats_metric_reset(&stp->metric); nserver = array_n(&stp->server); for (j = 0; j < nserver; j++) { struct stats_server *sts = array_get(&stp->server, j); stats_metric_reset(&sts->metric); } } } static rstatus_t stats_pool_map(struct array *stats_pool, struct array *server_pool) { rstatus_t status; uint32_t i, npool; npool = array_n(server_pool); ASSERT(npool != 0); status = array_init(stats_pool, npool, sizeof(struct stats_pool)); if (status != NC_OK) { return status; } for (i = 0; i < npool; i++) { struct server_pool *sp = array_get(server_pool, i); struct stats_pool *stp = array_push(stats_pool); status = stats_pool_init(stp, sp); if (status != NC_OK) { return status; } } log_debug(LOG_VVVERB, "map %"PRIu32" stats pools", npool); return NC_OK; } static void stats_pool_unmap(struct array *stats_pool) { uint32_t i, npool; npool = array_n(stats_pool); for (i = 0; i < npool; i++) { struct stats_pool *stp = array_pop(stats_pool); stats_metric_deinit(&stp->metric); stats_server_unmap(&stp->server); } array_deinit(stats_pool); log_debug(LOG_VVVERB, "unmap %"PRIu32" stats pool", npool); } static rstatus_t stats_create_buf(struct stats *st) { uint32_t int64_max_digits = 20; /* INT64_MAX = 9223372036854775807 */ uint32_t key_value_extra = 8; /* "key": "value", */ uint32_t pool_extra = 8; /* '"pool_name": { ' + ' }' */ uint32_t server_extra = 8; /* '"server_name": { ' + ' }' */ size_t size = 0; uint32_t i; ASSERT(st->buf.data == NULL && st->buf.size == 0); /* header */ size += 1; size += st->service_str.len; size += st->service.len; size += key_value_extra; size += st->source_str.len; size += st->source.len; size += key_value_extra; size += st->version_str.len; size += st->version.len; size += key_value_extra; size += st->uptime_str.len; size += int64_max_digits; size += key_value_extra; size += st->timestamp_str.len; size += int64_max_digits; size += key_value_extra; size += st->ntotal_conn_str.len; size += int64_max_digits; size += key_value_extra; size += st->ncurr_conn_str.len; size += int64_max_digits; size += key_value_extra; /* server pools */ for (i = 0; i < array_n(&st->sum); i++) { struct stats_pool *stp = array_get(&st->sum, i); uint32_t j; size += stp->name.len; size += pool_extra; for (j = 0; j < array_n(&stp->metric); j++) { struct stats_metric *stm = array_get(&stp->metric, j); size += stm->name.len; size += int64_max_digits; size += key_value_extra; } /* servers per pool */ for (j = 0; j < array_n(&stp->server); j++) { struct stats_server *sts = array_get(&stp->server, j); uint32_t k; size += sts->name.len; size += server_extra; for (k = 0; k < array_n(&sts->metric); k++) { struct stats_metric *stm = array_get(&sts->metric, k); size += stm->name.len; size += int64_max_digits; size += key_value_extra; } } } /* footer */ size += 2; size = NC_ALIGN(size, NC_ALIGNMENT); st->buf.data = nc_alloc(size); if (st->buf.data == NULL) { log_error("create stats buffer of size %zu failed: %s", size, strerror(errno)); return NC_ENOMEM; } st->buf.size = size; log_debug(LOG_DEBUG, "stats buffer size %zu", size); return NC_OK; } static void stats_destroy_buf(struct stats *st) { if (st->buf.size != 0) { ASSERT(st->buf.data != NULL); nc_free(st->buf.data); st->buf.size = 0; } } static rstatus_t stats_add_string(struct stats *st, struct string *key, struct string *val) { struct stats_buffer *buf; uint8_t *pos; size_t room; int n; buf = &st->buf; pos = buf->data + buf->len; room = buf->size - buf->len - 1; n = nc_snprintf(pos, room, "\"%.*s\":\"%.*s\", ", key->len, key->data, val->len, val->data); if (n < 0 || n >= (int)room) { return NC_ERROR; } buf->len += (size_t)n; return NC_OK; } static rstatus_t stats_add_num(struct stats *st, struct string *key, int64_t val) { struct stats_buffer *buf; uint8_t *pos; size_t room; int n; buf = &st->buf; pos = buf->data + buf->len; room = buf->size - buf->len - 1; n = nc_snprintf(pos, room, "\"%.*s\":%"PRId64", ", key->len, key->data, val); if (n < 0 || n >= (int)room) { return NC_ERROR; } buf->len += (size_t)n; return NC_OK; } static rstatus_t stats_add_header(struct stats *st) { rstatus_t status; struct stats_buffer *buf; int64_t cur_ts, uptime; buf = &st->buf; buf->data[0] = '{'; buf->len = 1; cur_ts = (int64_t)time(NULL); uptime = cur_ts - st->start_ts; status = stats_add_string(st, &st->service_str, &st->service); if (status != NC_OK) { return status; } status = stats_add_string(st, &st->source_str, &st->source); if (status != NC_OK) { return status; } status = stats_add_string(st, &st->version_str, &st->version); if (status != NC_OK) { return status; } status = stats_add_num(st, &st->uptime_str, uptime); if (status != NC_OK) { return status; } status = stats_add_num(st, &st->timestamp_str, cur_ts); if (status != NC_OK) { return status; } status = stats_add_num(st, &st->ntotal_conn_str, conn_ntotal_conn()); if (status != NC_OK) { return status; } status = stats_add_num(st, &st->ncurr_conn_str, conn_ncurr_conn()); if (status != NC_OK) { return status; } return NC_OK; } static rstatus_t stats_add_footer(struct stats *st) { struct stats_buffer *buf; uint8_t *pos; buf = &st->buf; if (buf->len == buf->size) { return NC_ERROR; } /* overwrite the last byte and add a new byte */ pos = buf->data + buf->len - 1; pos[0] = '}'; pos[1] = '\n'; buf->len += 1; return NC_OK; } static rstatus_t stats_begin_nesting(struct stats *st, struct string *key) { struct stats_buffer *buf; uint8_t *pos; size_t room; int n; buf = &st->buf; pos = buf->data + buf->len; room = buf->size - buf->len - 1; n = nc_snprintf(pos, room, "\"%.*s\": {", key->len, key->data); if (n < 0 || n >= (int)room) { return NC_ERROR; } buf->len += (size_t)n; return NC_OK; } static rstatus_t stats_end_nesting(struct stats *st) { struct stats_buffer *buf; uint8_t *pos; buf = &st->buf; pos = buf->data + buf->len; pos -= 2; /* go back by 2 bytes */ switch (pos[0]) { case ',': /* overwrite last two bytes; len remains unchanged */ ASSERT(pos[1] == ' '); pos[0] = '}'; pos[1] = ','; break; case '}': if (buf->len == buf->size) { return NC_ERROR; } /* overwrite the last byte and add a new byte */ ASSERT(pos[1] == ','); pos[1] = '}'; pos[2] = ','; buf->len += 1; break; default: NOT_REACHED(); } return NC_OK; } static rstatus_t stats_copy_metric(struct stats *st, struct array *metric) { rstatus_t status; uint32_t i; for (i = 0; i < array_n(metric); i++) { struct stats_metric *stm = array_get(metric, i); status = stats_add_num(st, &stm->name, stm->value.counter); if (status != NC_OK) { return status; } } return NC_OK; } static void stats_aggregate_metric(struct array *dst, struct array *src) { uint32_t i; for (i = 0; i < array_n(src); i++) { struct stats_metric *stm1, *stm2; stm1 = array_get(src, i); stm2 = array_get(dst, i); ASSERT(stm1->type == stm2->type); switch (stm1->type) { case STATS_COUNTER: stm2->value.counter += stm1->value.counter; break; case STATS_GAUGE: stm2->value.counter += stm1->value.counter; break; case STATS_TIMESTAMP: if (stm1->value.timestamp) { stm2->value.timestamp = stm1->value.timestamp; } break; default: NOT_REACHED(); } } } static void stats_aggregate(struct stats *st) { uint32_t i; if (st->aggregate == 0) { log_debug(LOG_PVERB, "skip aggregate of shadow %p to sum %p as " "generator is slow", st->shadow.elem, st->sum.elem); return; } log_debug(LOG_PVERB, "aggregate stats shadow %p to sum %p", st->shadow.elem, st->sum.elem); for (i = 0; i < array_n(&st->shadow); i++) { struct stats_pool *stp1, *stp2; uint32_t j; stp1 = array_get(&st->shadow, i); stp2 = array_get(&st->sum, i); stats_aggregate_metric(&stp2->metric, &stp1->metric); for (j = 0; j < array_n(&stp1->server); j++) { struct stats_server *sts1, *sts2; sts1 = array_get(&stp1->server, j); sts2 = array_get(&stp2->server, j); stats_aggregate_metric(&sts2->metric, &sts1->metric); } } st->aggregate = 0; } static rstatus_t stats_make_rsp(struct stats *st) { rstatus_t status; uint32_t i; status = stats_add_header(st); if (status != NC_OK) { return status; } for (i = 0; i < array_n(&st->sum); i++) { struct stats_pool *stp = array_get(&st->sum, i); uint32_t j; status = stats_begin_nesting(st, &stp->name); if (status != NC_OK) { return status; } /* copy pool metric from sum(c) to buffer */ status = stats_copy_metric(st, &stp->metric); if (status != NC_OK) { return status; } for (j = 0; j < array_n(&stp->server); j++) { struct stats_server *sts = array_get(&stp->server, j); status = stats_begin_nesting(st, &sts->name); if (status != NC_OK) { return status; } /* copy server metric from sum(c) to buffer */ status = stats_copy_metric(st, &sts->metric); if (status != NC_OK) { return status; } status = stats_end_nesting(st); if (status != NC_OK) { return status; } } status = stats_end_nesting(st); if (status != NC_OK) { return status; } } status = stats_add_footer(st); if (status != NC_OK) { return status; } return NC_OK; } static rstatus_t stats_send_rsp(struct stats *st) { rstatus_t status; ssize_t n; int sd; status = stats_make_rsp(st); if (status != NC_OK) { return status; } sd = accept(st->sd, NULL, NULL); if (sd < 0) { log_error("accept on m %d failed: %s", st->sd, strerror(errno)); return NC_ERROR; } log_debug(LOG_VERB, "send stats on sd %d %d bytes", sd, st->buf.len); n = nc_sendn(sd, st->buf.data, st->buf.len); if (n < 0) { log_error("send stats on sd %d failed: %s", sd, strerror(errno)); close(sd); return NC_ERROR; } close(sd); return NC_OK; } static void stats_loop_callback(void *arg1, void *arg2) { struct stats *st = arg1; int n = *((int *)arg2); /* aggregate stats from shadow (b) -> sum (c) */ stats_aggregate(st); if (n == 0) { return; } /* send aggregate stats sum (c) to collector */ stats_send_rsp(st); } static void * stats_loop(void *arg) { event_loop_stats(stats_loop_callback, arg); return NULL; } static rstatus_t stats_listen(struct stats *st) { rstatus_t status; struct sockinfo si; status = nc_resolve(&st->addr, st->port, &si); if (status < 0) { return status; } st->sd = socket(si.family, SOCK_STREAM, 0); if (st->sd < 0) { log_error("socket failed: %s", strerror(errno)); return NC_ERROR; } status = nc_set_reuseaddr(st->sd); if (status < 0) { log_error("set reuseaddr on m %d failed: %s", st->sd, strerror(errno)); return NC_ERROR; } status = bind(st->sd, (struct sockaddr *)&si.addr, si.addrlen); if (status < 0) { log_error("bind on m %d to addr '%.*s:%u' failed: %s", st->sd, st->addr.len, st->addr.data, st->port, strerror(errno)); return NC_ERROR; } status = listen(st->sd, SOMAXCONN); if (status < 0) { log_error("listen on m %d failed: %s", st->sd, strerror(errno)); return NC_ERROR; } log_debug(LOG_NOTICE, "m %d listening on '%.*s:%u'", st->sd, st->addr.len, st->addr.data, st->port); return NC_OK; } static rstatus_t stats_start_aggregator(struct stats *st) { rstatus_t status; if (!stats_enabled) { return NC_OK; } status = stats_listen(st); if (status != NC_OK) { return status; } status = pthread_create(&st->tid, NULL, stats_loop, st); if (status < 0) { log_error("stats aggregator create failed: %s", strerror(status)); return NC_ERROR; } return NC_OK; } static void stats_stop_aggregator(struct stats *st) { if (!stats_enabled) { return; } close(st->sd); } struct stats * stats_create(uint16_t stats_port, char *stats_ip, int stats_interval, char *source, struct array *server_pool) { rstatus_t status; struct stats *st; st = nc_alloc(sizeof(*st)); if (st == NULL) { return NULL; } st->port = stats_port; st->interval = stats_interval; string_set_raw(&st->addr, stats_ip); st->start_ts = (int64_t)time(NULL); st->buf.len = 0; st->buf.data = NULL; st->buf.size = 0; array_null(&st->current); array_null(&st->shadow); array_null(&st->sum); st->tid = (pthread_t) -1; st->sd = -1; string_set_text(&st->service_str, "service"); string_set_text(&st->service, "nutcracker"); string_set_text(&st->source_str, "source"); string_set_raw(&st->source, source); string_set_text(&st->version_str, "version"); string_set_text(&st->version, NC_VERSION_STRING); string_set_text(&st->uptime_str, "uptime"); string_set_text(&st->timestamp_str, "timestamp"); string_set_text(&st->ntotal_conn_str, "total_connections"); string_set_text(&st->ncurr_conn_str, "curr_connections"); st->updated = 0; st->aggregate = 0; /* map server pool to current (a), shadow (b) and sum (c) */ status = stats_pool_map(&st->current, server_pool); if (status != NC_OK) { goto error; } status = stats_pool_map(&st->shadow, server_pool); if (status != NC_OK) { goto error; } status = stats_pool_map(&st->sum, server_pool); if (status != NC_OK) { goto error; } status = stats_create_buf(st); if (status != NC_OK) { goto error; } status = stats_start_aggregator(st); if (status != NC_OK) { goto error; } return st; error: stats_destroy(st); return NULL; } void stats_destroy(struct stats *st) { stats_stop_aggregator(st); stats_pool_unmap(&st->sum); stats_pool_unmap(&st->shadow); stats_pool_unmap(&st->current); stats_destroy_buf(st); nc_free(st); } void stats_swap(struct stats *st) { if (!stats_enabled) { return; } if (st->aggregate == 1) { log_debug(LOG_PVERB, "skip swap of current %p shadow %p as aggregator " "is busy", st->current.elem, st->shadow.elem); return; } if (st->updated == 0) { log_debug(LOG_PVERB, "skip swap of current %p shadow %p as there is " "nothing new", st->current.elem, st->shadow.elem); return; } log_debug(LOG_PVERB, "swap stats current %p shadow %p", st->current.elem, st->shadow.elem); array_swap(&st->current, &st->shadow); /* * Reset current (a) stats before giving it back to generator to keep * stats addition idempotent */ stats_pool_reset(&st->current); st->updated = 0; st->aggregate = 1; } static struct stats_metric * stats_pool_to_metric(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx) { struct stats *st; struct stats_pool *stp; struct stats_metric *stm; uint32_t pidx; pidx = pool->idx; st = ctx->stats; stp = array_get(&st->current, pidx); stm = array_get(&stp->metric, fidx); st->updated = 1; log_debug(LOG_VVVERB, "metric '%.*s' in pool %"PRIu32"", stm->name.len, stm->name.data, pidx); return stm; } void _stats_pool_incr(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE); stm->value.counter++; log_debug(LOG_VVVERB, "incr field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_pool_decr(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_GAUGE); stm->value.counter--; log_debug(LOG_VVVERB, "decr field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_pool_incr_by(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE); stm->value.counter += val; log_debug(LOG_VVVERB, "incr by field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_pool_decr_by(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_GAUGE); stm->value.counter -= val; log_debug(LOG_VVVERB, "decr by field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_pool_set_ts(struct context *ctx, struct server_pool *pool, stats_pool_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_pool_to_metric(ctx, pool, fidx); ASSERT(stm->type == STATS_TIMESTAMP); stm->value.timestamp = val; log_debug(LOG_VVVERB, "set ts field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.timestamp); } static struct stats_metric * stats_server_to_metric(struct context *ctx, struct server *server, stats_server_field_t fidx) { struct stats *st; struct stats_pool *stp; struct stats_server *sts; struct stats_metric *stm; uint32_t pidx, sidx; sidx = server->idx; pidx = server->owner->idx; st = ctx->stats; stp = array_get(&st->current, pidx); sts = array_get(&stp->server, sidx); stm = array_get(&sts->metric, fidx); st->updated = 1; log_debug(LOG_VVVERB, "metric '%.*s' in pool %"PRIu32" server %"PRIu32"", stm->name.len, stm->name.data, pidx, sidx); return stm; } void _stats_server_incr(struct context *ctx, struct server *server, stats_server_field_t fidx) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE); stm->value.counter++; log_debug(LOG_VVVERB, "incr field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_server_decr(struct context *ctx, struct server *server, stats_server_field_t fidx) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_GAUGE); stm->value.counter--; log_debug(LOG_VVVERB, "decr field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_server_incr_by(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_COUNTER || stm->type == STATS_GAUGE); stm->value.counter += val; log_debug(LOG_VVVERB, "incr by field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_server_decr_by(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_GAUGE); stm->value.counter -= val; log_debug(LOG_VVVERB, "decr by field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.counter); } void _stats_server_set_ts(struct context *ctx, struct server *server, stats_server_field_t fidx, int64_t val) { struct stats_metric *stm; stm = stats_server_to_metric(ctx, server, fidx); ASSERT(stm->type == STATS_TIMESTAMP); stm->value.timestamp = val; log_debug(LOG_VVVERB, "set ts field '%.*s' to %"PRId64"", stm->name.len, stm->name.data, stm->value.timestamp); } nutcracker-0.4.1+dfsg/src/nc_mbuf.h0000664000000000000000000000402412542173515015652 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_MBUF_H_ #define _NC_MBUF_H_ #include typedef void (*mbuf_copy_t)(struct mbuf *, void *); struct mbuf { uint32_t magic; /* mbuf magic (const) */ STAILQ_ENTRY(mbuf) next; /* next mbuf */ uint8_t *pos; /* read marker */ uint8_t *last; /* write marker */ uint8_t *start; /* start of buffer (const) */ uint8_t *end; /* end of buffer (const) */ }; STAILQ_HEAD(mhdr, mbuf); #define MBUF_MAGIC 0xdeadbeef #define MBUF_MIN_SIZE 512 #define MBUF_MAX_SIZE 16777216 #define MBUF_SIZE 16384 #define MBUF_HSIZE sizeof(struct mbuf) static inline bool mbuf_empty(struct mbuf *mbuf) { return mbuf->pos == mbuf->last ? true : false; } static inline bool mbuf_full(struct mbuf *mbuf) { return mbuf->last == mbuf->end ? true : false; } void mbuf_init(struct instance *nci); void mbuf_deinit(void); struct mbuf *mbuf_get(void); void mbuf_put(struct mbuf *mbuf); void mbuf_rewind(struct mbuf *mbuf); uint32_t mbuf_length(struct mbuf *mbuf); uint32_t mbuf_size(struct mbuf *mbuf); size_t mbuf_data_size(void); void mbuf_insert(struct mhdr *mhdr, struct mbuf *mbuf); void mbuf_remove(struct mhdr *mhdr, struct mbuf *mbuf); void mbuf_copy(struct mbuf *mbuf, uint8_t *pos, size_t n); struct mbuf *mbuf_split(struct mhdr *h, uint8_t *pos, mbuf_copy_t cb, void *cbarg); #endif nutcracker-0.4.1+dfsg/src/nc_conf.h0000664000000000000000000001326312542173515015653 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_CONF_H_ #define _NC_CONF_H_ #include #include #include #include #include #include #define CONF_OK (void *) NULL #define CONF_ERROR (void *) "has an invalid value" #define CONF_ROOT_DEPTH 1 #define CONF_MAX_DEPTH CONF_ROOT_DEPTH + 1 #define CONF_DEFAULT_ARGS 3 #define CONF_DEFAULT_POOL 8 #define CONF_DEFAULT_SERVERS 8 #define CONF_UNSET_NUM -1 #define CONF_UNSET_PTR NULL #define CONF_UNSET_HASH (hash_type_t) -1 #define CONF_UNSET_DIST (dist_type_t) -1 #define CONF_DEFAULT_HASH HASH_FNV1A_64 #define CONF_DEFAULT_DIST DIST_KETAMA #define CONF_DEFAULT_TIMEOUT -1 #define CONF_DEFAULT_LISTEN_BACKLOG 512 #define CONF_DEFAULT_CLIENT_CONNECTIONS 0 #define CONF_DEFAULT_REDIS false #define CONF_DEFAULT_REDIS_DB 0 #define CONF_DEFAULT_PRECONNECT false #define CONF_DEFAULT_AUTO_EJECT_HOSTS false #define CONF_DEFAULT_SERVER_RETRY_TIMEOUT 30 * 1000 /* in msec */ #define CONF_DEFAULT_SERVER_FAILURE_LIMIT 2 #define CONF_DEFAULT_SERVER_CONNECTIONS 1 #define CONF_DEFAULT_KETAMA_PORT 11211 struct conf_listen { struct string pname; /* listen: as "hostname:port" */ struct string name; /* hostname:port */ int port; /* port */ mode_t perm; /* socket permissions */ struct sockinfo info; /* listen socket info */ unsigned valid:1; /* valid? */ }; struct conf_server { struct string pname; /* server: as "hostname:port:weight" */ struct string name; /* hostname:port or [name] */ struct string addrstr; /* hostname */ int port; /* port */ int weight; /* weight */ struct sockinfo info; /* connect socket info */ unsigned valid:1; /* valid? */ }; struct conf_pool { struct string name; /* pool name (root node) */ struct conf_listen listen; /* listen: */ hash_type_t hash; /* hash: */ struct string hash_tag; /* hash_tag: */ dist_type_t distribution; /* distribution: */ int timeout; /* timeout: */ int backlog; /* backlog: */ int client_connections; /* client_connections: */ int redis; /* redis: */ struct string redis_auth; /* redis_auth: redis auth password (matches requirepass on redis) */ int redis_db; /* redis_db: redis db */ int preconnect; /* preconnect: */ int auto_eject_hosts; /* auto_eject_hosts: */ int server_connections; /* server_connections: */ int server_retry_timeout; /* server_retry_timeout: in msec */ int server_failure_limit; /* server_failure_limit: */ struct array server; /* servers: conf_server[] */ unsigned valid:1; /* valid? */ }; struct conf { char *fname; /* file name (ref in argv[]) */ FILE *fh; /* file handle */ struct array arg; /* string[] (parsed {key, value} pairs) */ struct array pool; /* conf_pool[] (parsed pools) */ uint32_t depth; /* parsed tree depth */ yaml_parser_t parser; /* yaml parser */ yaml_event_t event; /* yaml event */ yaml_token_t token; /* yaml token */ unsigned seq:1; /* sequence? */ unsigned valid_parser:1; /* valid parser? */ unsigned valid_event:1; /* valid event? */ unsigned valid_token:1; /* valid token? */ unsigned sound:1; /* sound? */ unsigned parsed:1; /* parsed? */ unsigned valid:1; /* valid? */ }; struct command { struct string name; char *(*set)(struct conf *cf, struct command *cmd, void *data); int offset; }; #define null_command { null_string, NULL, 0 } char *conf_set_string(struct conf *cf, struct command *cmd, void *conf); char *conf_set_listen(struct conf *cf, struct command *cmd, void *conf); char *conf_add_server(struct conf *cf, struct command *cmd, void *conf); char *conf_set_num(struct conf *cf, struct command *cmd, void *conf); char *conf_set_bool(struct conf *cf, struct command *cmd, void *conf); char *conf_set_hash(struct conf *cf, struct command *cmd, void *conf); char *conf_set_distribution(struct conf *cf, struct command *cmd, void *conf); char *conf_set_hashtag(struct conf *cf, struct command *cmd, void *conf); rstatus_t conf_server_each_transform(void *elem, void *data); rstatus_t conf_pool_each_transform(void *elem, void *data); struct conf *conf_create(char *filename); void conf_destroy(struct conf *cf); #endif nutcracker-0.4.1+dfsg/src/nc_conf.c0000664000000000000000000012377412542173515015657 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #define DEFINE_ACTION(_hash, _name) string(#_name), static struct string hash_strings[] = { HASH_CODEC( DEFINE_ACTION ) null_string }; #undef DEFINE_ACTION #define DEFINE_ACTION(_hash, _name) hash_##_name, static hash_t hash_algos[] = { HASH_CODEC( DEFINE_ACTION ) NULL }; #undef DEFINE_ACTION #define DEFINE_ACTION(_dist, _name) string(#_name), static struct string dist_strings[] = { DIST_CODEC( DEFINE_ACTION ) null_string }; #undef DEFINE_ACTION static struct command conf_commands[] = { { string("listen"), conf_set_listen, offsetof(struct conf_pool, listen) }, { string("hash"), conf_set_hash, offsetof(struct conf_pool, hash) }, { string("hash_tag"), conf_set_hashtag, offsetof(struct conf_pool, hash_tag) }, { string("distribution"), conf_set_distribution, offsetof(struct conf_pool, distribution) }, { string("timeout"), conf_set_num, offsetof(struct conf_pool, timeout) }, { string("backlog"), conf_set_num, offsetof(struct conf_pool, backlog) }, { string("client_connections"), conf_set_num, offsetof(struct conf_pool, client_connections) }, { string("redis"), conf_set_bool, offsetof(struct conf_pool, redis) }, { string("redis_auth"), conf_set_string, offsetof(struct conf_pool, redis_auth) }, { string("redis_db"), conf_set_num, offsetof(struct conf_pool, redis_db) }, { string("preconnect"), conf_set_bool, offsetof(struct conf_pool, preconnect) }, { string("auto_eject_hosts"), conf_set_bool, offsetof(struct conf_pool, auto_eject_hosts) }, { string("server_connections"), conf_set_num, offsetof(struct conf_pool, server_connections) }, { string("server_retry_timeout"), conf_set_num, offsetof(struct conf_pool, server_retry_timeout) }, { string("server_failure_limit"), conf_set_num, offsetof(struct conf_pool, server_failure_limit) }, { string("servers"), conf_add_server, offsetof(struct conf_pool, server) }, null_command }; static void conf_server_init(struct conf_server *cs) { string_init(&cs->pname); string_init(&cs->name); string_init(&cs->addrstr); cs->port = 0; cs->weight = 0; memset(&cs->info, 0, sizeof(cs->info)); cs->valid = 0; log_debug(LOG_VVERB, "init conf server %p", cs); } static void conf_server_deinit(struct conf_server *cs) { string_deinit(&cs->pname); string_deinit(&cs->name); string_deinit(&cs->addrstr); cs->valid = 0; log_debug(LOG_VVERB, "deinit conf server %p", cs); } rstatus_t conf_server_each_transform(void *elem, void *data) { struct conf_server *cs = elem; struct array *server = data; struct server *s; ASSERT(cs->valid); s = array_push(server); ASSERT(s != NULL); s->idx = array_idx(server, s); s->owner = NULL; s->pname = cs->pname; s->name = cs->name; s->addrstr = cs->addrstr; s->port = (uint16_t)cs->port; s->weight = (uint32_t)cs->weight; nc_memcpy(&s->info, &cs->info, sizeof(cs->info)); s->ns_conn_q = 0; TAILQ_INIT(&s->s_conn_q); s->next_retry = 0LL; s->failure_count = 0; log_debug(LOG_VERB, "transform to server %"PRIu32" '%.*s'", s->idx, s->pname.len, s->pname.data); return NC_OK; } static rstatus_t conf_pool_init(struct conf_pool *cp, struct string *name) { rstatus_t status; string_init(&cp->name); string_init(&cp->listen.pname); string_init(&cp->listen.name); string_init(&cp->redis_auth); cp->listen.port = 0; memset(&cp->listen.info, 0, sizeof(cp->listen.info)); cp->listen.valid = 0; cp->hash = CONF_UNSET_HASH; string_init(&cp->hash_tag); cp->distribution = CONF_UNSET_DIST; cp->timeout = CONF_UNSET_NUM; cp->backlog = CONF_UNSET_NUM; cp->client_connections = CONF_UNSET_NUM; cp->redis = CONF_UNSET_NUM; cp->redis_db = CONF_UNSET_NUM; cp->preconnect = CONF_UNSET_NUM; cp->auto_eject_hosts = CONF_UNSET_NUM; cp->server_connections = CONF_UNSET_NUM; cp->server_retry_timeout = CONF_UNSET_NUM; cp->server_failure_limit = CONF_UNSET_NUM; array_null(&cp->server); cp->valid = 0; status = string_duplicate(&cp->name, name); if (status != NC_OK) { return status; } status = array_init(&cp->server, CONF_DEFAULT_SERVERS, sizeof(struct conf_server)); if (status != NC_OK) { string_deinit(&cp->name); return status; } log_debug(LOG_VVERB, "init conf pool %p, '%.*s'", cp, name->len, name->data); return NC_OK; } static void conf_pool_deinit(struct conf_pool *cp) { string_deinit(&cp->name); string_deinit(&cp->listen.pname); string_deinit(&cp->listen.name); if (cp->redis_auth.len > 0) { string_deinit(&cp->redis_auth); } while (array_n(&cp->server) != 0) { conf_server_deinit(array_pop(&cp->server)); } array_deinit(&cp->server); log_debug(LOG_VVERB, "deinit conf pool %p", cp); } rstatus_t conf_pool_each_transform(void *elem, void *data) { rstatus_t status; struct conf_pool *cp = elem; struct array *server_pool = data; struct server_pool *sp; ASSERT(cp->valid); sp = array_push(server_pool); ASSERT(sp != NULL); sp->idx = array_idx(server_pool, sp); sp->ctx = NULL; sp->p_conn = NULL; sp->nc_conn_q = 0; TAILQ_INIT(&sp->c_conn_q); array_null(&sp->server); sp->ncontinuum = 0; sp->nserver_continuum = 0; sp->continuum = NULL; sp->nlive_server = 0; sp->next_rebuild = 0LL; sp->name = cp->name; sp->addrstr = cp->listen.pname; sp->port = (uint16_t)cp->listen.port; nc_memcpy(&sp->info, &cp->listen.info, sizeof(cp->listen.info)); sp->perm = cp->listen.perm; sp->key_hash_type = cp->hash; sp->key_hash = hash_algos[cp->hash]; sp->dist_type = cp->distribution; sp->hash_tag = cp->hash_tag; sp->redis = cp->redis ? 1 : 0; sp->timeout = cp->timeout; sp->backlog = cp->backlog; sp->redis_db = cp->redis_db; sp->redis_auth = cp->redis_auth; sp->require_auth = cp->redis_auth.len > 0 ? 1 : 0; sp->client_connections = (uint32_t)cp->client_connections; sp->server_connections = (uint32_t)cp->server_connections; sp->server_retry_timeout = (int64_t)cp->server_retry_timeout * 1000LL; sp->server_failure_limit = (uint32_t)cp->server_failure_limit; sp->auto_eject_hosts = cp->auto_eject_hosts ? 1 : 0; sp->preconnect = cp->preconnect ? 1 : 0; status = server_init(&sp->server, &cp->server, sp); if (status != NC_OK) { return status; } log_debug(LOG_VERB, "transform to pool %"PRIu32" '%.*s'", sp->idx, sp->name.len, sp->name.data); return NC_OK; } static void conf_dump(struct conf *cf) { uint32_t i, j, npool, nserver; struct conf_pool *cp; struct string *s; npool = array_n(&cf->pool); if (npool == 0) { return; } log_debug(LOG_VVERB, "%"PRIu32" pools in configuration file '%s'", npool, cf->fname); for (i = 0; i < npool; i++) { cp = array_get(&cf->pool, i); log_debug(LOG_VVERB, "%.*s", cp->name.len, cp->name.data); log_debug(LOG_VVERB, " listen: %.*s", cp->listen.pname.len, cp->listen.pname.data); log_debug(LOG_VVERB, " timeout: %d", cp->timeout); log_debug(LOG_VVERB, " backlog: %d", cp->backlog); log_debug(LOG_VVERB, " hash: %d", cp->hash); log_debug(LOG_VVERB, " hash_tag: \"%.*s\"", cp->hash_tag.len, cp->hash_tag.data); log_debug(LOG_VVERB, " distribution: %d", cp->distribution); log_debug(LOG_VVERB, " client_connections: %d", cp->client_connections); log_debug(LOG_VVERB, " redis: %d", cp->redis); log_debug(LOG_VVERB, " preconnect: %d", cp->preconnect); log_debug(LOG_VVERB, " auto_eject_hosts: %d", cp->auto_eject_hosts); log_debug(LOG_VVERB, " server_connections: %d", cp->server_connections); log_debug(LOG_VVERB, " server_retry_timeout: %d", cp->server_retry_timeout); log_debug(LOG_VVERB, " server_failure_limit: %d", cp->server_failure_limit); nserver = array_n(&cp->server); log_debug(LOG_VVERB, " servers: %"PRIu32"", nserver); for (j = 0; j < nserver; j++) { s = array_get(&cp->server, j); log_debug(LOG_VVERB, " %.*s", s->len, s->data); } } } static rstatus_t conf_yaml_init(struct conf *cf) { int rv; ASSERT(!cf->valid_parser); rv = fseek(cf->fh, 0L, SEEK_SET); if (rv < 0) { log_error("conf: failed to seek to the beginning of file '%s': %s", cf->fname, strerror(errno)); return NC_ERROR; } rv = yaml_parser_initialize(&cf->parser); if (!rv) { log_error("conf: failed (err %d) to initialize yaml parser", cf->parser.error); return NC_ERROR; } yaml_parser_set_input_file(&cf->parser, cf->fh); cf->valid_parser = 1; return NC_OK; } static void conf_yaml_deinit(struct conf *cf) { if (cf->valid_parser) { yaml_parser_delete(&cf->parser); cf->valid_parser = 0; } } static rstatus_t conf_token_next(struct conf *cf) { int rv; ASSERT(cf->valid_parser && !cf->valid_token); rv = yaml_parser_scan(&cf->parser, &cf->token); if (!rv) { log_error("conf: failed (err %d) to scan next token", cf->parser.error); return NC_ERROR; } cf->valid_token = 1; return NC_OK; } static void conf_token_done(struct conf *cf) { ASSERT(cf->valid_parser); if (cf->valid_token) { yaml_token_delete(&cf->token); cf->valid_token = 0; } } static rstatus_t conf_event_next(struct conf *cf) { int rv; ASSERT(cf->valid_parser && !cf->valid_event); rv = yaml_parser_parse(&cf->parser, &cf->event); if (!rv) { log_error("conf: failed (err %d) to get next event", cf->parser.error); return NC_ERROR; } cf->valid_event = 1; return NC_OK; } static void conf_event_done(struct conf *cf) { if (cf->valid_event) { yaml_event_delete(&cf->event); cf->valid_event = 0; } } static rstatus_t conf_push_scalar(struct conf *cf) { rstatus_t status; struct string *value; uint8_t *scalar; uint32_t scalar_len; scalar = cf->event.data.scalar.value; scalar_len = (uint32_t)cf->event.data.scalar.length; log_debug(LOG_VVERB, "push '%.*s'", scalar_len, scalar); value = array_push(&cf->arg); if (value == NULL) { return NC_ENOMEM; } string_init(value); status = string_copy(value, scalar, scalar_len); if (status != NC_OK) { array_pop(&cf->arg); return status; } return NC_OK; } static void conf_pop_scalar(struct conf *cf) { struct string *value; value = array_pop(&cf->arg); log_debug(LOG_VVERB, "pop '%.*s'", value->len, value->data); string_deinit(value); } static rstatus_t conf_handler(struct conf *cf, void *data) { struct command *cmd; struct string *key, *value; uint32_t narg; if (array_n(&cf->arg) == 1) { value = array_top(&cf->arg); log_debug(LOG_VVERB, "conf handler on '%.*s'", value->len, value->data); return conf_pool_init(data, value); } narg = array_n(&cf->arg); value = array_get(&cf->arg, narg - 1); key = array_get(&cf->arg, narg - 2); log_debug(LOG_VVERB, "conf handler on %.*s: %.*s", key->len, key->data, value->len, value->data); for (cmd = conf_commands; cmd->name.len != 0; cmd++) { char *rv; if (string_compare(key, &cmd->name) != 0) { continue; } rv = cmd->set(cf, cmd, data); if (rv != CONF_OK) { log_error("conf: directive \"%.*s\" %s", key->len, key->data, rv); return NC_ERROR; } return NC_OK; } log_error("conf: directive \"%.*s\" is unknown", key->len, key->data); return NC_ERROR; } static rstatus_t conf_begin_parse(struct conf *cf) { rstatus_t status; bool done; ASSERT(cf->sound && !cf->parsed); ASSERT(cf->depth == 0); status = conf_yaml_init(cf); if (status != NC_OK) { return status; } done = false; do { status = conf_event_next(cf); if (status != NC_OK) { return status; } log_debug(LOG_VVERB, "next begin event %d", cf->event.type); switch (cf->event.type) { case YAML_STREAM_START_EVENT: case YAML_DOCUMENT_START_EVENT: break; case YAML_MAPPING_START_EVENT: ASSERT(cf->depth < CONF_MAX_DEPTH); cf->depth++; done = true; break; default: NOT_REACHED(); } conf_event_done(cf); } while (!done); return NC_OK; } static rstatus_t conf_end_parse(struct conf *cf) { rstatus_t status; bool done; ASSERT(cf->sound && !cf->parsed); ASSERT(cf->depth == 0); done = false; do { status = conf_event_next(cf); if (status != NC_OK) { return status; } log_debug(LOG_VVERB, "next end event %d", cf->event.type); switch (cf->event.type) { case YAML_STREAM_END_EVENT: done = true; break; case YAML_DOCUMENT_END_EVENT: break; default: NOT_REACHED(); } conf_event_done(cf); } while (!done); conf_yaml_deinit(cf); return NC_OK; } static rstatus_t conf_parse_core(struct conf *cf, void *data) { rstatus_t status; bool done, leaf, new_pool; ASSERT(cf->sound); status = conf_event_next(cf); if (status != NC_OK) { return status; } log_debug(LOG_VVERB, "next event %d depth %"PRIu32" seq %d", cf->event.type, cf->depth, cf->seq); done = false; leaf = false; new_pool = false; switch (cf->event.type) { case YAML_MAPPING_END_EVENT: cf->depth--; if (cf->depth == 1) { conf_pop_scalar(cf); } else if (cf->depth == 0) { done = true; } break; case YAML_MAPPING_START_EVENT: cf->depth++; break; case YAML_SEQUENCE_START_EVENT: cf->seq = 1; break; case YAML_SEQUENCE_END_EVENT: conf_pop_scalar(cf); cf->seq = 0; break; case YAML_SCALAR_EVENT: status = conf_push_scalar(cf); if (status != NC_OK) { break; } /* take appropriate action */ if (cf->seq) { /* for a sequence, leaf is at CONF_MAX_DEPTH */ ASSERT(cf->depth == CONF_MAX_DEPTH); leaf = true; } else if (cf->depth == CONF_ROOT_DEPTH) { /* create new conf_pool */ data = array_push(&cf->pool); if (data == NULL) { status = NC_ENOMEM; break; } new_pool = true; } else if (array_n(&cf->arg) == cf->depth + 1) { /* for {key: value}, leaf is at CONF_MAX_DEPTH */ ASSERT(cf->depth == CONF_MAX_DEPTH); leaf = true; } break; default: NOT_REACHED(); break; } conf_event_done(cf); if (status != NC_OK) { return status; } if (done) { /* terminating condition */ return NC_OK; } if (leaf || new_pool) { status = conf_handler(cf, data); if (leaf) { conf_pop_scalar(cf); if (!cf->seq) { conf_pop_scalar(cf); } } if (status != NC_OK) { return status; } } return conf_parse_core(cf, data); } static rstatus_t conf_parse(struct conf *cf) { rstatus_t status; ASSERT(cf->sound && !cf->parsed); ASSERT(array_n(&cf->arg) == 0); status = conf_begin_parse(cf); if (status != NC_OK) { return status; } status = conf_parse_core(cf, NULL); if (status != NC_OK) { return status; } status = conf_end_parse(cf); if (status != NC_OK) { return status; } cf->parsed = 1; return NC_OK; } static struct conf * conf_open(char *filename) { rstatus_t status; struct conf *cf; FILE *fh; fh = fopen(filename, "r"); if (fh == NULL) { log_error("conf: failed to open configuration '%s': %s", filename, strerror(errno)); return NULL; } cf = nc_alloc(sizeof(*cf)); if (cf == NULL) { fclose(fh); return NULL; } status = array_init(&cf->arg, CONF_DEFAULT_ARGS, sizeof(struct string)); if (status != NC_OK) { nc_free(cf); fclose(fh); return NULL; } status = array_init(&cf->pool, CONF_DEFAULT_POOL, sizeof(struct conf_pool)); if (status != NC_OK) { array_deinit(&cf->arg); nc_free(cf); fclose(fh); return NULL; } cf->fname = filename; cf->fh = fh; cf->depth = 0; /* parser, event, and token are initialized later */ cf->seq = 0; cf->valid_parser = 0; cf->valid_event = 0; cf->valid_token = 0; cf->sound = 0; cf->parsed = 0; cf->valid = 0; log_debug(LOG_VVERB, "opened conf '%s'", filename); return cf; } static rstatus_t conf_validate_document(struct conf *cf) { rstatus_t status; uint32_t count; bool done; status = conf_yaml_init(cf); if (status != NC_OK) { return status; } count = 0; done = false; do { yaml_document_t document; yaml_node_t *node; int rv; rv = yaml_parser_load(&cf->parser, &document); if (!rv) { log_error("conf: failed (err %d) to get the next yaml document", cf->parser.error); conf_yaml_deinit(cf); return NC_ERROR; } node = yaml_document_get_root_node(&document); if (node == NULL) { done = true; } else { count++; } yaml_document_delete(&document); } while (!done); conf_yaml_deinit(cf); if (count != 1) { log_error("conf: '%s' must contain only 1 document; found %"PRIu32" " "documents", cf->fname, count); return NC_ERROR; } return NC_OK; } static rstatus_t conf_validate_tokens(struct conf *cf) { rstatus_t status; bool done, error; int type; status = conf_yaml_init(cf); if (status != NC_OK) { return status; } done = false; error = false; do { status = conf_token_next(cf); if (status != NC_OK) { return status; } type = cf->token.type; switch (type) { case YAML_NO_TOKEN: error = true; log_error("conf: no token (%d) is disallowed", type); break; case YAML_VERSION_DIRECTIVE_TOKEN: error = true; log_error("conf: version directive token (%d) is disallowed", type); break; case YAML_TAG_DIRECTIVE_TOKEN: error = true; log_error("conf: tag directive token (%d) is disallowed", type); break; case YAML_DOCUMENT_START_TOKEN: error = true; log_error("conf: document start token (%d) is disallowed", type); break; case YAML_DOCUMENT_END_TOKEN: error = true; log_error("conf: document end token (%d) is disallowed", type); break; case YAML_FLOW_SEQUENCE_START_TOKEN: error = true; log_error("conf: flow sequence start token (%d) is disallowed", type); break; case YAML_FLOW_SEQUENCE_END_TOKEN: error = true; log_error("conf: flow sequence end token (%d) is disallowed", type); break; case YAML_FLOW_MAPPING_START_TOKEN: error = true; log_error("conf: flow mapping start token (%d) is disallowed", type); break; case YAML_FLOW_MAPPING_END_TOKEN: error = true; log_error("conf: flow mapping end token (%d) is disallowed", type); break; case YAML_FLOW_ENTRY_TOKEN: error = true; log_error("conf: flow entry token (%d) is disallowed", type); break; case YAML_ALIAS_TOKEN: error = true; log_error("conf: alias token (%d) is disallowed", type); break; case YAML_ANCHOR_TOKEN: error = true; log_error("conf: anchor token (%d) is disallowed", type); break; case YAML_TAG_TOKEN: error = true; log_error("conf: tag token (%d) is disallowed", type); break; case YAML_BLOCK_SEQUENCE_START_TOKEN: case YAML_BLOCK_MAPPING_START_TOKEN: case YAML_BLOCK_END_TOKEN: case YAML_BLOCK_ENTRY_TOKEN: break; case YAML_KEY_TOKEN: case YAML_VALUE_TOKEN: case YAML_SCALAR_TOKEN: break; case YAML_STREAM_START_TOKEN: break; case YAML_STREAM_END_TOKEN: done = true; log_debug(LOG_VVERB, "conf '%s' has valid tokens", cf->fname); break; default: error = true; log_error("conf: unknown token (%d) is disallowed", type); break; } conf_token_done(cf); } while (!done && !error); conf_yaml_deinit(cf); return !error ? NC_OK : NC_ERROR; } static rstatus_t conf_validate_structure(struct conf *cf) { rstatus_t status; int type, depth; uint32_t i, count[CONF_MAX_DEPTH + 1]; bool done, error, seq; status = conf_yaml_init(cf); if (status != NC_OK) { return status; } done = false; error = false; seq = false; depth = 0; for (i = 0; i < CONF_MAX_DEPTH + 1; i++) { count[i] = 0; } /* * Validate that the configuration conforms roughly to the following * yaml tree structure: * * keyx: * key1: value1 * key2: value2 * seq: * - elem1 * - elem2 * - elem3 * key3: value3 * * keyy: * key1: value1 * key2: value2 * seq: * - elem1 * - elem2 * - elem3 * key3: value3 */ do { status = conf_event_next(cf); if (status != NC_OK) { return status; } type = cf->event.type; log_debug(LOG_VVERB, "next event %d depth %d seq %d", type, depth, seq); switch (type) { case YAML_STREAM_START_EVENT: case YAML_DOCUMENT_START_EVENT: break; case YAML_DOCUMENT_END_EVENT: break; case YAML_STREAM_END_EVENT: done = true; break; case YAML_MAPPING_START_EVENT: if (depth == CONF_ROOT_DEPTH && count[depth] != 1) { error = true; log_error("conf: '%s' has more than one \"key:value\" at depth" " %d", cf->fname, depth); } else if (depth >= CONF_MAX_DEPTH) { error = true; log_error("conf: '%s' has a depth greater than %d", cf->fname, CONF_MAX_DEPTH); } depth++; break; case YAML_MAPPING_END_EVENT: if (depth == CONF_MAX_DEPTH) { if (seq) { seq = false; } else { error = true; log_error("conf: '%s' missing sequence directive at depth " "%d", cf->fname, depth); } } depth--; count[depth] = 0; break; case YAML_SEQUENCE_START_EVENT: if (seq) { error = true; log_error("conf: '%s' has more than one sequence directive", cf->fname); } else if (depth != CONF_MAX_DEPTH) { error = true; log_error("conf: '%s' has sequence at depth %d instead of %d", cf->fname, depth, CONF_MAX_DEPTH); } else if (count[depth] != 1) { error = true; log_error("conf: '%s' has invalid \"key:value\" at depth %d", cf->fname, depth); } seq = true; break; case YAML_SEQUENCE_END_EVENT: ASSERT(depth == CONF_MAX_DEPTH); count[depth] = 0; break; case YAML_SCALAR_EVENT: if (depth == 0) { error = true; log_error("conf: '%s' has invalid empty \"key:\" at depth %d", cf->fname, depth); } else if (depth == CONF_ROOT_DEPTH && count[depth] != 0) { error = true; log_error("conf: '%s' has invalid mapping \"key:\" at depth %d", cf->fname, depth); } else if (depth == CONF_MAX_DEPTH && count[depth] == 2) { /* found a "key: value", resetting! */ count[depth] = 0; } count[depth]++; break; default: NOT_REACHED(); } conf_event_done(cf); } while (!done && !error); conf_yaml_deinit(cf); return !error ? NC_OK : NC_ERROR; } static rstatus_t conf_pre_validate(struct conf *cf) { rstatus_t status; status = conf_validate_document(cf); if (status != NC_OK) { return status; } status = conf_validate_tokens(cf); if (status != NC_OK) { return status; } status = conf_validate_structure(cf); if (status != NC_OK) { return status; } cf->sound = 1; return NC_OK; } static int conf_server_name_cmp(const void *t1, const void *t2) { const struct conf_server *s1 = t1, *s2 = t2; return string_compare(&s1->name, &s2->name); } static int conf_pool_name_cmp(const void *t1, const void *t2) { const struct conf_pool *p1 = t1, *p2 = t2; return string_compare(&p1->name, &p2->name); } static int conf_pool_listen_cmp(const void *t1, const void *t2) { const struct conf_pool *p1 = t1, *p2 = t2; return string_compare(&p1->listen.pname, &p2->listen.pname); } static rstatus_t conf_validate_server(struct conf *cf, struct conf_pool *cp) { uint32_t i, nserver; bool valid; nserver = array_n(&cp->server); if (nserver == 0) { log_error("conf: pool '%.*s' has no servers", cp->name.len, cp->name.data); return NC_ERROR; } /* * Disallow duplicate servers - servers with identical "host:port:weight" * or "name" combination are considered as duplicates. When server name * is configured, we only check for duplicate "name" and not for duplicate * "host:port:weight" */ array_sort(&cp->server, conf_server_name_cmp); for (valid = true, i = 0; i < nserver - 1; i++) { struct conf_server *cs1, *cs2; cs1 = array_get(&cp->server, i); cs2 = array_get(&cp->server, i + 1); if (string_compare(&cs1->name, &cs2->name) == 0) { log_error("conf: pool '%.*s' has servers with same name '%.*s'", cp->name.len, cp->name.data, cs1->name.len, cs1->name.data); valid = false; break; } } if (!valid) { return NC_ERROR; } return NC_OK; } static rstatus_t conf_validate_pool(struct conf *cf, struct conf_pool *cp) { rstatus_t status; ASSERT(!cp->valid); ASSERT(!string_empty(&cp->name)); if (!cp->listen.valid) { log_error("conf: directive \"listen:\" is missing"); return NC_ERROR; } /* set default values for unset directives */ if (cp->distribution == CONF_UNSET_DIST) { cp->distribution = CONF_DEFAULT_DIST; } if (cp->hash == CONF_UNSET_HASH) { cp->hash = CONF_DEFAULT_HASH; } if (cp->timeout == CONF_UNSET_NUM) { cp->timeout = CONF_DEFAULT_TIMEOUT; } if (cp->backlog == CONF_UNSET_NUM) { cp->backlog = CONF_DEFAULT_LISTEN_BACKLOG; } cp->client_connections = CONF_DEFAULT_CLIENT_CONNECTIONS; if (cp->redis == CONF_UNSET_NUM) { cp->redis = CONF_DEFAULT_REDIS; } if (cp->redis_db == CONF_UNSET_NUM) { cp->redis_db = CONF_DEFAULT_REDIS_DB; } if (cp->preconnect == CONF_UNSET_NUM) { cp->preconnect = CONF_DEFAULT_PRECONNECT; } if (cp->auto_eject_hosts == CONF_UNSET_NUM) { cp->auto_eject_hosts = CONF_DEFAULT_AUTO_EJECT_HOSTS; } if (cp->server_connections == CONF_UNSET_NUM) { cp->server_connections = CONF_DEFAULT_SERVER_CONNECTIONS; } else if (cp->server_connections == 0) { log_error("conf: directive \"server_connections:\" cannot be 0"); return NC_ERROR; } if (cp->server_retry_timeout == CONF_UNSET_NUM) { cp->server_retry_timeout = CONF_DEFAULT_SERVER_RETRY_TIMEOUT; } if (cp->server_failure_limit == CONF_UNSET_NUM) { cp->server_failure_limit = CONF_DEFAULT_SERVER_FAILURE_LIMIT; } if (!cp->redis && cp->redis_auth.len > 0) { log_error("conf: directive \"redis_auth:\" is only valid for a redis pool"); return NC_ERROR; } status = conf_validate_server(cf, cp); if (status != NC_OK) { return status; } cp->valid = 1; return NC_OK; } static rstatus_t conf_post_validate(struct conf *cf) { rstatus_t status; uint32_t i, npool; bool valid; ASSERT(cf->sound && cf->parsed); ASSERT(!cf->valid); npool = array_n(&cf->pool); if (npool == 0) { log_error("conf: '%.*s' has no pools", cf->fname); return NC_ERROR; } /* validate pool */ for (i = 0; i < npool; i++) { struct conf_pool *cp = array_get(&cf->pool, i); status = conf_validate_pool(cf, cp); if (status != NC_OK) { return status; } } /* disallow pools with duplicate listen: key values */ array_sort(&cf->pool, conf_pool_listen_cmp); for (valid = true, i = 0; i < npool - 1; i++) { struct conf_pool *p1, *p2; p1 = array_get(&cf->pool, i); p2 = array_get(&cf->pool, i + 1); if (string_compare(&p1->listen.pname, &p2->listen.pname) == 0) { log_error("conf: pools '%.*s' and '%.*s' have the same listen " "address '%.*s'", p1->name.len, p1->name.data, p2->name.len, p2->name.data, p1->listen.pname.len, p1->listen.pname.data); valid = false; break; } } if (!valid) { return NC_ERROR; } /* disallow pools with duplicate names */ array_sort(&cf->pool, conf_pool_name_cmp); for (valid = true, i = 0; i < npool - 1; i++) { struct conf_pool *p1, *p2; p1 = array_get(&cf->pool, i); p2 = array_get(&cf->pool, i + 1); if (string_compare(&p1->name, &p2->name) == 0) { log_error("conf: '%s' has pools with same name %.*s'", cf->fname, p1->name.len, p1->name.data); valid = false; break; } } if (!valid) { return NC_ERROR; } return NC_OK; } struct conf * conf_create(char *filename) { rstatus_t status; struct conf *cf; cf = conf_open(filename); if (cf == NULL) { return NULL; } /* validate configuration file before parsing */ status = conf_pre_validate(cf); if (status != NC_OK) { goto error; } /* parse the configuration file */ status = conf_parse(cf); if (status != NC_OK) { goto error; } /* validate parsed configuration */ status = conf_post_validate(cf); if (status != NC_OK) { goto error; } conf_dump(cf); fclose(cf->fh); cf->fh = NULL; return cf; error: fclose(cf->fh); cf->fh = NULL; conf_destroy(cf); return NULL; } void conf_destroy(struct conf *cf) { while (array_n(&cf->arg) != 0) { conf_pop_scalar(cf); } array_deinit(&cf->arg); while (array_n(&cf->pool) != 0) { conf_pool_deinit(array_pop(&cf->pool)); } array_deinit(&cf->pool); nc_free(cf); } char * conf_set_string(struct conf *cf, struct command *cmd, void *conf) { rstatus_t status; uint8_t *p; struct string *field, *value; p = conf; field = (struct string *)(p + cmd->offset); if (field->data != CONF_UNSET_PTR) { return "is a duplicate"; } value = array_top(&cf->arg); status = string_duplicate(field, value); if (status != NC_OK) { return CONF_ERROR; } return CONF_OK; } char * conf_set_listen(struct conf *cf, struct command *cmd, void *conf) { rstatus_t status; struct string *value; struct conf_listen *field; uint8_t *p, *name; uint32_t namelen; p = conf; field = (struct conf_listen *)(p + cmd->offset); if (field->valid == 1) { return "is a duplicate"; } value = array_top(&cf->arg); status = string_duplicate(&field->pname, value); if (status != NC_OK) { return CONF_ERROR; } if (value->data[0] == '/') { uint8_t *q, *start, *perm; uint32_t permlen; /* parse "socket_path permissions" from the end */ p = value->data + value->len -1; start = value->data; q = nc_strrchr(p, start, ' '); if (q == NULL) { /* no permissions field, so use defaults */ name = value->data; namelen = value->len; } else { perm = q + 1; permlen = (uint32_t)(p - perm + 1); p = q - 1; name = start; namelen = (uint32_t)(p - start + 1); errno = 0; field->perm = (mode_t)strtol((char *)perm, NULL, 8); if (errno || field->perm > 0777) { return "has an invalid file permission in \"socket_path permission\" format string"; } } } else { uint8_t *q, *start, *port; uint32_t portlen; /* parse "hostname:port" from the end */ p = value->data + value->len - 1; start = value->data; q = nc_strrchr(p, start, ':'); if (q == NULL) { return "has an invalid \"hostname:port\" format string"; } port = q + 1; portlen = (uint32_t)(p - port + 1); p = q - 1; name = start; namelen = (uint32_t)(p - start + 1); field->port = nc_atoi(port, portlen); if (field->port < 0 || !nc_valid_port(field->port)) { return "has an invalid port in \"hostname:port\" format string"; } } status = string_copy(&field->name, name, namelen); if (status != NC_OK) { return CONF_ERROR; } status = nc_resolve(&field->name, field->port, &field->info); if (status != NC_OK) { return CONF_ERROR; } field->valid = 1; return CONF_OK; } char * conf_add_server(struct conf *cf, struct command *cmd, void *conf) { rstatus_t status; struct array *a; struct string *value; struct conf_server *field; uint8_t *p, *q, *start; uint8_t *pname, *addr, *port, *weight, *name; uint32_t k, delimlen, pnamelen, addrlen, portlen, weightlen, namelen; char delim[] = " ::"; p = conf; a = (struct array *)(p + cmd->offset); field = array_push(a); if (field == NULL) { return CONF_ERROR; } conf_server_init(field); value = array_top(&cf->arg); /* parse "hostname:port:weight [name]" or "/path/unix_socket:weight [name]" from the end */ p = value->data + value->len - 1; start = value->data; addr = NULL; addrlen = 0; weight = NULL; weightlen = 0; port = NULL; portlen = 0; name = NULL; namelen = 0; delimlen = value->data[0] == '/' ? 2 : 3; for (k = 0; k < sizeof(delim); k++) { q = nc_strrchr(p, start, delim[k]); if (q == NULL) { if (k == 0) { /* * name in "hostname:port:weight [name]" format string is * optional */ continue; } break; } switch (k) { case 0: name = q + 1; namelen = (uint32_t)(p - name + 1); break; case 1: weight = q + 1; weightlen = (uint32_t)(p - weight + 1); break; case 2: port = q + 1; portlen = (uint32_t)(p - port + 1); break; default: NOT_REACHED(); } p = q - 1; } if (k != delimlen) { return "has an invalid \"hostname:port:weight [name]\"or \"/path/unix_socket:weight [name]\" format string"; } pname = value->data; pnamelen = namelen > 0 ? value->len - (namelen + 1) : value->len; status = string_copy(&field->pname, pname, pnamelen); if (status != NC_OK) { array_pop(a); return CONF_ERROR; } addr = start; addrlen = (uint32_t)(p - start + 1); field->weight = nc_atoi(weight, weightlen); if (field->weight < 0) { return "has an invalid weight in \"hostname:port:weight [name]\" format string"; } else if (field->weight == 0) { return "has a zero weight in \"hostname:port:weight [name]\" format string"; } if (value->data[0] != '/') { field->port = nc_atoi(port, portlen); if (field->port < 0 || !nc_valid_port(field->port)) { return "has an invalid port in \"hostname:port:weight [name]\" format string"; } } if (name == NULL) { /* * To maintain backward compatibility with libmemcached, we don't * include the port as the part of the input string to the consistent * hashing algorithm, when it is equal to 11211. */ if (field->port == CONF_DEFAULT_KETAMA_PORT) { name = addr; namelen = addrlen; } else { name = addr; namelen = addrlen + 1 + portlen; } } status = string_copy(&field->name, name, namelen); if (status != NC_OK) { return CONF_ERROR; } status = string_copy(&field->addrstr, addr, addrlen); if (status != NC_OK) { return CONF_ERROR; } /* * The address resolution of the backend server hostname is lazy. * The resolution occurs when a new connection to the server is * created, which could either be the first time or every time * the server gets re-added to the pool after an auto ejection */ field->valid = 1; return CONF_OK; } char * conf_set_num(struct conf *cf, struct command *cmd, void *conf) { uint8_t *p; int num, *np; struct string *value; p = conf; np = (int *)(p + cmd->offset); if (*np != CONF_UNSET_NUM) { return "is a duplicate"; } value = array_top(&cf->arg); num = nc_atoi(value->data, value->len); if (num < 0) { return "is not a number"; } *np = num; return CONF_OK; } char * conf_set_bool(struct conf *cf, struct command *cmd, void *conf) { uint8_t *p; int *bp; struct string *value, true_str, false_str; p = conf; bp = (int *)(p + cmd->offset); if (*bp != CONF_UNSET_NUM) { return "is a duplicate"; } value = array_top(&cf->arg); string_set_text(&true_str, "true"); string_set_text(&false_str, "false"); if (string_compare(value, &true_str) == 0) { *bp = 1; } else if (string_compare(value, &false_str) == 0) { *bp = 0; } else { return "is not \"true\" or \"false\""; } return CONF_OK; } char * conf_set_hash(struct conf *cf, struct command *cmd, void *conf) { uint8_t *p; hash_type_t *hp; struct string *value, *hash; p = conf; hp = (hash_type_t *)(p + cmd->offset); if (*hp != CONF_UNSET_HASH) { return "is a duplicate"; } value = array_top(&cf->arg); for (hash = hash_strings; hash->len != 0; hash++) { if (string_compare(value, hash) != 0) { continue; } *hp = hash - hash_strings; return CONF_OK; } return "is not a valid hash"; } char * conf_set_distribution(struct conf *cf, struct command *cmd, void *conf) { uint8_t *p; dist_type_t *dp; struct string *value, *dist; p = conf; dp = (dist_type_t *)(p + cmd->offset); if (*dp != CONF_UNSET_DIST) { return "is a duplicate"; } value = array_top(&cf->arg); for (dist = dist_strings; dist->len != 0; dist++) { if (string_compare(value, dist) != 0) { continue; } *dp = dist - dist_strings; return CONF_OK; } return "is not a valid distribution"; } char * conf_set_hashtag(struct conf *cf, struct command *cmd, void *conf) { rstatus_t status; uint8_t *p; struct string *field, *value; p = conf; field = (struct string *)(p + cmd->offset); if (field->data != CONF_UNSET_PTR) { return "is a duplicate"; } value = array_top(&cf->arg); if (value->len != 2) { return "is not a valid hash tag string with two characters"; } status = string_duplicate(field, value); if (status != NC_OK) { return CONF_ERROR; } return CONF_OK; } nutcracker-0.4.1+dfsg/src/nc_message.h0000664000000000000000000005457212542173515016362 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_MESSAGE_H_ #define _NC_MESSAGE_H_ #include typedef void (*msg_parse_t)(struct msg *); typedef rstatus_t (*msg_add_auth_t)(struct context *ctx, struct conn *c_conn, struct conn *s_conn); typedef rstatus_t (*msg_fragment_t)(struct msg *, uint32_t, struct msg_tqh *); typedef void (*msg_coalesce_t)(struct msg *r); typedef rstatus_t (*msg_reply_t)(struct msg *r); typedef bool (*msg_failure_t)(struct msg *r); typedef enum msg_parse_result { MSG_PARSE_OK, /* parsing ok */ MSG_PARSE_ERROR, /* parsing error */ MSG_PARSE_REPAIR, /* more to parse -> repair parsed & unparsed data */ MSG_PARSE_AGAIN, /* incomplete -> parse again */ } msg_parse_result_t; #define MSG_TYPE_CODEC(ACTION) \ ACTION( UNKNOWN ) \ ACTION( REQ_MC_GET ) /* memcache retrieval requests */ \ ACTION( REQ_MC_GETS ) \ ACTION( REQ_MC_DELETE ) /* memcache delete request */ \ ACTION( REQ_MC_CAS ) /* memcache cas request and storage request */ \ ACTION( REQ_MC_SET ) /* memcache storage request */ \ ACTION( REQ_MC_ADD ) \ ACTION( REQ_MC_REPLACE ) \ ACTION( REQ_MC_APPEND ) \ ACTION( REQ_MC_PREPEND ) \ ACTION( REQ_MC_INCR ) /* memcache arithmetic request */ \ ACTION( REQ_MC_DECR ) \ ACTION( REQ_MC_TOUCH ) /* memcache touch request */ \ ACTION( REQ_MC_QUIT ) /* memcache quit request */ \ ACTION( RSP_MC_NUM ) /* memcache arithmetic response */ \ ACTION( RSP_MC_STORED ) /* memcache cas and storage response */ \ ACTION( RSP_MC_NOT_STORED ) \ ACTION( RSP_MC_EXISTS ) \ ACTION( RSP_MC_NOT_FOUND ) \ ACTION( RSP_MC_END ) \ ACTION( RSP_MC_VALUE ) \ ACTION( RSP_MC_DELETED ) /* memcache delete response */ \ ACTION( RSP_MC_TOUCHED ) /* memcache touch response */ \ ACTION( RSP_MC_ERROR ) /* memcache error responses */ \ ACTION( RSP_MC_CLIENT_ERROR ) \ ACTION( RSP_MC_SERVER_ERROR ) \ ACTION( REQ_REDIS_DEL ) /* redis commands - keys */ \ ACTION( REQ_REDIS_EXISTS ) \ ACTION( REQ_REDIS_EXPIRE ) \ ACTION( REQ_REDIS_EXPIREAT ) \ ACTION( REQ_REDIS_PEXPIRE ) \ ACTION( REQ_REDIS_PEXPIREAT ) \ ACTION( REQ_REDIS_PERSIST ) \ ACTION( REQ_REDIS_PTTL ) \ ACTION( REQ_REDIS_SORT ) \ ACTION( REQ_REDIS_TTL ) \ ACTION( REQ_REDIS_TYPE ) \ ACTION( REQ_REDIS_APPEND ) /* redis requests - string */ \ ACTION( REQ_REDIS_BITCOUNT ) \ ACTION( REQ_REDIS_DECR ) \ ACTION( REQ_REDIS_DECRBY ) \ ACTION( REQ_REDIS_DUMP ) \ ACTION( REQ_REDIS_GET ) \ ACTION( REQ_REDIS_GETBIT ) \ ACTION( REQ_REDIS_GETRANGE ) \ ACTION( REQ_REDIS_GETSET ) \ ACTION( REQ_REDIS_INCR ) \ ACTION( REQ_REDIS_INCRBY ) \ ACTION( REQ_REDIS_INCRBYFLOAT ) \ ACTION( REQ_REDIS_MGET ) \ ACTION( REQ_REDIS_MSET ) \ ACTION( REQ_REDIS_PSETEX ) \ ACTION( REQ_REDIS_RESTORE ) \ ACTION( REQ_REDIS_SET ) \ ACTION( REQ_REDIS_SETBIT ) \ ACTION( REQ_REDIS_SETEX ) \ ACTION( REQ_REDIS_SETNX ) \ ACTION( REQ_REDIS_SETRANGE ) \ ACTION( REQ_REDIS_STRLEN ) \ ACTION( REQ_REDIS_HDEL ) /* redis requests - hashes */ \ ACTION( REQ_REDIS_HEXISTS ) \ ACTION( REQ_REDIS_HGET ) \ ACTION( REQ_REDIS_HGETALL ) \ ACTION( REQ_REDIS_HINCRBY ) \ ACTION( REQ_REDIS_HINCRBYFLOAT ) \ ACTION( REQ_REDIS_HKEYS ) \ ACTION( REQ_REDIS_HLEN ) \ ACTION( REQ_REDIS_HMGET ) \ ACTION( REQ_REDIS_HMSET ) \ ACTION( REQ_REDIS_HSET ) \ ACTION( REQ_REDIS_HSETNX ) \ ACTION( REQ_REDIS_HSCAN) \ ACTION( REQ_REDIS_HVALS ) \ ACTION( REQ_REDIS_LINDEX ) /* redis requests - lists */ \ ACTION( REQ_REDIS_LINSERT ) \ ACTION( REQ_REDIS_LLEN ) \ ACTION( REQ_REDIS_LPOP ) \ ACTION( REQ_REDIS_LPUSH ) \ ACTION( REQ_REDIS_LPUSHX ) \ ACTION( REQ_REDIS_LRANGE ) \ ACTION( REQ_REDIS_LREM ) \ ACTION( REQ_REDIS_LSET ) \ ACTION( REQ_REDIS_LTRIM ) \ ACTION( REQ_REDIS_PFADD ) /* redis requests - hyperloglog */ \ ACTION( REQ_REDIS_PFCOUNT ) \ ACTION( REQ_REDIS_PFMERGE ) \ ACTION( REQ_REDIS_RPOP ) \ ACTION( REQ_REDIS_RPOPLPUSH ) \ ACTION( REQ_REDIS_RPUSH ) \ ACTION( REQ_REDIS_RPUSHX ) \ ACTION( REQ_REDIS_SADD ) /* redis requests - sets */ \ ACTION( REQ_REDIS_SCARD ) \ ACTION( REQ_REDIS_SDIFF ) \ ACTION( REQ_REDIS_SDIFFSTORE ) \ ACTION( REQ_REDIS_SINTER ) \ ACTION( REQ_REDIS_SINTERSTORE ) \ ACTION( REQ_REDIS_SISMEMBER ) \ ACTION( REQ_REDIS_SMEMBERS ) \ ACTION( REQ_REDIS_SMOVE ) \ ACTION( REQ_REDIS_SPOP ) \ ACTION( REQ_REDIS_SRANDMEMBER ) \ ACTION( REQ_REDIS_SREM ) \ ACTION( REQ_REDIS_SUNION ) \ ACTION( REQ_REDIS_SUNIONSTORE ) \ ACTION( REQ_REDIS_SSCAN) \ ACTION( REQ_REDIS_ZADD ) /* redis requests - sorted sets */ \ ACTION( REQ_REDIS_ZCARD ) \ ACTION( REQ_REDIS_ZCOUNT ) \ ACTION( REQ_REDIS_ZINCRBY ) \ ACTION( REQ_REDIS_ZINTERSTORE ) \ ACTION( REQ_REDIS_ZLEXCOUNT ) \ ACTION( REQ_REDIS_ZRANGE ) \ ACTION( REQ_REDIS_ZRANGEBYLEX ) \ ACTION( REQ_REDIS_ZRANGEBYSCORE ) \ ACTION( REQ_REDIS_ZRANK ) \ ACTION( REQ_REDIS_ZREM ) \ ACTION( REQ_REDIS_ZREMRANGEBYRANK ) \ ACTION( REQ_REDIS_ZREMRANGEBYLEX ) \ ACTION( REQ_REDIS_ZREMRANGEBYSCORE ) \ ACTION( REQ_REDIS_ZREVRANGE ) \ ACTION( REQ_REDIS_ZREVRANGEBYSCORE ) \ ACTION( REQ_REDIS_ZREVRANK ) \ ACTION( REQ_REDIS_ZSCORE ) \ ACTION( REQ_REDIS_ZUNIONSTORE ) \ ACTION( REQ_REDIS_ZSCAN) \ ACTION( REQ_REDIS_EVAL ) /* redis requests - eval */ \ ACTION( REQ_REDIS_EVALSHA ) \ ACTION( REQ_REDIS_PING ) /* redis requests - ping/quit */ \ ACTION( REQ_REDIS_QUIT) \ ACTION( REQ_REDIS_AUTH) \ ACTION( REQ_REDIS_SELECT) /* only during init */ \ ACTION( RSP_REDIS_STATUS ) /* redis response */ \ ACTION( RSP_REDIS_ERROR ) \ ACTION( RSP_REDIS_ERROR_ERR ) \ ACTION( RSP_REDIS_ERROR_OOM ) \ ACTION( RSP_REDIS_ERROR_BUSY ) \ ACTION( RSP_REDIS_ERROR_NOAUTH ) \ ACTION( RSP_REDIS_ERROR_LOADING ) \ ACTION( RSP_REDIS_ERROR_BUSYKEY ) \ ACTION( RSP_REDIS_ERROR_MISCONF ) \ ACTION( RSP_REDIS_ERROR_NOSCRIPT ) \ ACTION( RSP_REDIS_ERROR_READONLY ) \ ACTION( RSP_REDIS_ERROR_WRONGTYPE ) \ ACTION( RSP_REDIS_ERROR_EXECABORT ) \ ACTION( RSP_REDIS_ERROR_MASTERDOWN ) \ ACTION( RSP_REDIS_ERROR_NOREPLICAS ) \ ACTION( RSP_REDIS_INTEGER ) \ ACTION( RSP_REDIS_BULK ) \ ACTION( RSP_REDIS_MULTIBULK ) \ ACTION( SENTINEL ) \ #define DEFINE_ACTION(_name) MSG_##_name, typedef enum msg_type { MSG_TYPE_CODEC(DEFINE_ACTION) } msg_type_t; #undef DEFINE_ACTION struct keypos { uint8_t *start; /* key start pos */ uint8_t *end; /* key end pos */ }; struct msg { TAILQ_ENTRY(msg) c_tqe; /* link in client q */ TAILQ_ENTRY(msg) s_tqe; /* link in server q */ TAILQ_ENTRY(msg) m_tqe; /* link in send q / free q */ uint64_t id; /* message id */ struct msg *peer; /* message peer */ struct conn *owner; /* message owner - client | server */ struct rbnode tmo_rbe; /* entry in rbtree */ struct mhdr mhdr; /* message mbuf header */ uint32_t mlen; /* message length */ int64_t start_ts; /* request start timestamp in usec */ int state; /* current parser state */ uint8_t *pos; /* parser position marker */ uint8_t *token; /* token marker */ msg_parse_t parser; /* message parser */ msg_parse_result_t result; /* message parsing result */ msg_fragment_t fragment; /* message fragment */ msg_reply_t reply; /* generate message reply (example: ping) */ msg_add_auth_t add_auth; /* add auth message when we forward msg */ msg_failure_t failure; /* transient failure response? */ msg_coalesce_t pre_coalesce; /* message pre-coalesce */ msg_coalesce_t post_coalesce; /* message post-coalesce */ msg_type_t type; /* message type */ struct array *keys; /* array of keypos, for req */ uint32_t vlen; /* value length (memcache) */ uint8_t *end; /* end marker (memcache) */ uint8_t *narg_start; /* narg start (redis) */ uint8_t *narg_end; /* narg end (redis) */ uint32_t narg; /* # arguments (redis) */ uint32_t rnarg; /* running # arg used by parsing fsa (redis) */ uint32_t rlen; /* running length in parsing fsa (redis) */ uint32_t integer; /* integer reply value (redis) */ struct msg *frag_owner; /* owner of fragment message */ uint32_t nfrag; /* # fragment */ uint32_t nfrag_done; /* # fragment done */ uint64_t frag_id; /* id of fragmented message */ struct msg **frag_seq; /* sequence of fragment message, map from keys to fragments*/ err_t err; /* errno on error? */ unsigned error:1; /* error? */ unsigned ferror:1; /* one or more fragments are in error? */ unsigned request:1; /* request? or response? */ unsigned quit:1; /* quit request? */ unsigned noreply:1; /* noreply? */ unsigned noforward:1; /* not need forward (example: ping) */ unsigned done:1; /* done? */ unsigned fdone:1; /* all fragments are done? */ unsigned swallow:1; /* swallow response? */ unsigned redis:1; /* redis? */ }; TAILQ_HEAD(msg_tqh, msg); struct msg *msg_tmo_min(void); void msg_tmo_insert(struct msg *msg, struct conn *conn); void msg_tmo_delete(struct msg *msg); void msg_init(void); void msg_deinit(void); struct string *msg_type_string(msg_type_t type); struct msg *msg_get(struct conn *conn, bool request, bool redis); void msg_put(struct msg *msg); struct msg *msg_get_error(bool redis, err_t err); void msg_dump(struct msg *msg, int level); bool msg_empty(struct msg *msg); rstatus_t msg_recv(struct context *ctx, struct conn *conn); rstatus_t msg_send(struct context *ctx, struct conn *conn); uint64_t msg_gen_frag_id(void); uint32_t msg_backend_idx(struct msg *msg, uint8_t *key, uint32_t keylen); struct mbuf *msg_ensure_mbuf(struct msg *msg, size_t len); rstatus_t msg_append(struct msg *msg, uint8_t *pos, size_t n); rstatus_t msg_prepend(struct msg *msg, uint8_t *pos, size_t n); rstatus_t msg_prepend_format(struct msg *msg, const char *fmt, ...); struct msg *req_get(struct conn *conn); void req_put(struct msg *msg); bool req_done(struct conn *conn, struct msg *msg); bool req_error(struct conn *conn, struct msg *msg); void req_server_enqueue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_server_enqueue_imsgq_head(struct context *ctx, struct conn *conn, struct msg *msg); void req_server_dequeue_imsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_client_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_server_enqueue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_client_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg); void req_server_dequeue_omsgq(struct context *ctx, struct conn *conn, struct msg *msg); struct msg *req_recv_next(struct context *ctx, struct conn *conn, bool alloc); void req_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg); struct msg *req_send_next(struct context *ctx, struct conn *conn); void req_send_done(struct context *ctx, struct conn *conn, struct msg *msg); struct msg *rsp_get(struct conn *conn); void rsp_put(struct msg *msg); struct msg *rsp_recv_next(struct context *ctx, struct conn *conn, bool alloc); void rsp_recv_done(struct context *ctx, struct conn *conn, struct msg *msg, struct msg *nmsg); struct msg *rsp_send_next(struct context *ctx, struct conn *conn); void rsp_send_done(struct context *ctx, struct conn *conn, struct msg *msg); #endif nutcracker-0.4.1+dfsg/src/Makefile.am0000664000000000000000000000322512542173515016126 0ustar rootrootMAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = if !OS_SOLARIS AM_CPPFLAGS += -D_GNU_SOURCE endif AM_CPPFLAGS += -I $(top_srcdir)/src/hashkit AM_CPPFLAGS += -I $(top_srcdir)/src/proto AM_CPPFLAGS += -I $(top_srcdir)/src/event AM_CPPFLAGS += -I $(top_srcdir)/contrib/yaml-0.1.4/include AM_CFLAGS = # about -fno-strict-aliasing: https://github.com/twitter/twemproxy/issues/276 AM_CFLAGS += -fno-strict-aliasing AM_CFLAGS += -Wall -Wshadow AM_CFLAGS += -Wpointer-arith AM_CFLAGS += -Winline AM_CFLAGS += -Wunused-function -Wunused-variable -Wunused-value AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value AM_CFLAGS += -Wconversion -Wsign-compare AM_CFLAGS += -Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Wmissing-declarations AM_LDFLAGS = AM_LDFLAGS += -lm -lpthread -rdynamic if OS_SOLARIS AM_LDFLAGS += -lnsl -lsocket endif if OS_FREEBSD AM_LDFLAGS += -lexecinfo endif SUBDIRS = hashkit proto event sbin_PROGRAMS = nutcracker nutcracker_SOURCES = \ nc_core.c nc_core.h \ nc_connection.c nc_connection.h \ nc_client.c nc_client.h \ nc_server.c nc_server.h \ nc_proxy.c nc_proxy.h \ nc_message.c nc_message.h \ nc_request.c \ nc_response.c \ nc_mbuf.c nc_mbuf.h \ nc_conf.c nc_conf.h \ nc_stats.c nc_stats.h \ nc_signal.c nc_signal.h \ nc_rbtree.c nc_rbtree.h \ nc_log.c nc_log.h \ nc_string.c nc_string.h \ nc_array.c nc_array.h \ nc_util.c nc_util.h \ nc_queue.h \ nc.c nutcracker_LDADD = $(top_builddir)/src/hashkit/libhashkit.a nutcracker_LDADD += $(top_builddir)/src/proto/libproto.a nutcracker_LDADD += $(top_builddir)/src/event/libevent.a nutcracker_LDADD += $(top_builddir)/contrib/yaml-0.1.4/src/.libs/libyaml.a nutcracker-0.4.1+dfsg/src/nc_core.h0000664000000000000000000001007012542173515015647 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_CORE_H_ #define _NC_CORE_H_ #ifdef HAVE_CONFIG_H # include #endif #ifdef HAVE_DEBUG_LOG # define NC_DEBUG_LOG 1 #endif #ifdef HAVE_ASSERT_PANIC # define NC_ASSERT_PANIC 1 #endif #ifdef HAVE_ASSERT_LOG # define NC_ASSERT_LOG 1 #endif #ifdef HAVE_STATS # define NC_STATS 1 #else # define NC_STATS 0 #endif #ifdef HAVE_EPOLL # define NC_HAVE_EPOLL 1 #elif HAVE_KQUEUE # define NC_HAVE_KQUEUE 1 #elif HAVE_EVENT_PORTS # define NC_HAVE_EVENT_PORTS 1 #else # error missing scalable I/O event notification mechanism #endif #ifdef HAVE_LITTLE_ENDIAN # define NC_LITTLE_ENDIAN 1 #endif #ifdef HAVE_BACKTRACE # define NC_HAVE_BACKTRACE 1 #endif #define NC_OK 0 #define NC_ERROR -1 #define NC_EAGAIN -2 #define NC_ENOMEM -3 /* reserved fds for std streams, log, stats fd, epoll etc. */ #define RESERVED_FDS 32 typedef int rstatus_t; /* return type */ typedef int err_t; /* error type */ struct array; struct string; struct context; struct conn; struct conn_tqh; struct msg; struct msg_tqh; struct server; struct server_pool; struct mbuf; struct mhdr; struct conf; struct stats; struct instance; struct event_base; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct context { uint32_t id; /* unique context id */ struct conf *cf; /* configuration */ struct stats *stats; /* stats */ struct array pool; /* server_pool[] */ struct event_base *evb; /* event base */ int max_timeout; /* max timeout in msec */ int timeout; /* timeout in msec */ uint32_t max_nfd; /* max # files */ uint32_t max_ncconn; /* max # client connections */ uint32_t max_nsconn; /* max # server connections */ }; struct instance { struct context *ctx; /* active context */ int log_level; /* log level */ char *log_filename; /* log filename */ char *conf_filename; /* configuration filename */ uint16_t stats_port; /* stats monitoring port */ int stats_interval; /* stats aggregation interval */ char *stats_addr; /* stats monitoring addr */ char hostname[NC_MAXHOSTNAMELEN]; /* hostname */ size_t mbuf_chunk_size; /* mbuf chunk size */ pid_t pid; /* process id */ char *pid_filename; /* pid filename */ unsigned pidfile:1; /* pid file created? */ }; struct context *core_start(struct instance *nci); void core_stop(struct context *ctx); rstatus_t core_core(void *arg, uint32_t events); rstatus_t core_loop(struct context *ctx); #endif nutcracker-0.4.1+dfsg/src/event/0000775000000000000000000000000012542173515015211 5ustar rootrootnutcracker-0.4.1+dfsg/src/event/nc_epoll.c0000664000000000000000000001665412542173515017164 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #ifdef NC_HAVE_EPOLL #include struct event_base * event_base_create(int nevent, event_cb_t cb) { struct event_base *evb; int status, ep; struct epoll_event *event; ASSERT(nevent > 0); ep = epoll_create(nevent); if (ep < 0) { log_error("epoll create of size %d failed: %s", nevent, strerror(errno)); return NULL; } event = nc_calloc(nevent, sizeof(*event)); if (event == NULL) { status = close(ep); if (status < 0) { log_error("close e %d failed, ignored: %s", ep, strerror(errno)); } return NULL; } evb = nc_alloc(sizeof(*evb)); if (evb == NULL) { nc_free(event); status = close(ep); if (status < 0) { log_error("close e %d failed, ignored: %s", ep, strerror(errno)); } return NULL; } evb->ep = ep; evb->event = event; evb->nevent = nevent; evb->cb = cb; log_debug(LOG_INFO, "e %d with nevent %d", evb->ep, evb->nevent); return evb; } void event_base_destroy(struct event_base *evb) { int status; if (evb == NULL) { return; } ASSERT(evb->ep > 0); nc_free(evb->event); status = close(evb->ep); if (status < 0) { log_error("close e %d failed, ignored: %s", evb->ep, strerror(errno)); } evb->ep = -1; nc_free(evb); } int event_add_in(struct event_base *evb, struct conn *c) { int status; struct epoll_event event; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); if (c->recv_active) { return 0; } event.events = (uint32_t)(EPOLLIN | EPOLLET); event.data.ptr = c; status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->recv_active = 1; } return status; } int event_del_in(struct event_base *evb, struct conn *c) { return 0; } int event_add_out(struct event_base *evb, struct conn *c) { int status; struct epoll_event event; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (c->send_active) { return 0; } event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET); event.data.ptr = c; status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->send_active = 1; } return status; } int event_del_out(struct event_base *evb, struct conn *c) { int status; struct epoll_event event; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (!c->send_active) { return 0; } event.events = (uint32_t)(EPOLLIN | EPOLLET); event.data.ptr = c; status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->send_active = 0; } return status; } int event_add_conn(struct event_base *evb, struct conn *c) { int status; struct epoll_event event; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET); event.data.ptr = c; status = epoll_ctl(ep, EPOLL_CTL_ADD, c->sd, &event); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->send_active = 1; c->recv_active = 1; } return status; } int event_del_conn(struct event_base *evb, struct conn *c) { int status; int ep = evb->ep; ASSERT(ep > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); status = epoll_ctl(ep, EPOLL_CTL_DEL, c->sd, NULL); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, strerror(errno)); } else { c->recv_active = 0; c->send_active = 0; } return status; } int event_wait(struct event_base *evb, int timeout) { int ep = evb->ep; struct epoll_event *event = evb->event; int nevent = evb->nevent; ASSERT(ep > 0); ASSERT(event != NULL); ASSERT(nevent > 0); for (;;) { int i, nsd; nsd = epoll_wait(ep, event, nevent, timeout); if (nsd > 0) { for (i = 0; i < nsd; i++) { struct epoll_event *ev = &evb->event[i]; uint32_t events = 0; log_debug(LOG_VVERB, "epoll %04"PRIX32" triggered on conn %p", ev->events, ev->data.ptr); if (ev->events & EPOLLERR) { events |= EVENT_ERR; } if (ev->events & (EPOLLIN | EPOLLHUP)) { events |= EVENT_READ; } if (ev->events & EPOLLOUT) { events |= EVENT_WRITE; } if (evb->cb != NULL) { evb->cb(ev->data.ptr, events); } } return nsd; } if (nsd == 0) { if (timeout == -1) { log_error("epoll wait on e %d with %d events and %d timeout " "returned no events", ep, nevent, timeout); return -1; } return 0; } if (errno == EINTR) { continue; } log_error("epoll wait on e %d with %d events failed: %s", ep, nevent, strerror(errno)); return -1; } NOT_REACHED(); } void event_loop_stats(event_stats_cb_t cb, void *arg) { struct stats *st = arg; int status, ep; struct epoll_event ev; ep = epoll_create(1); if (ep < 0) { log_error("epoll create failed: %s", strerror(errno)); return; } ev.data.fd = st->sd; ev.events = EPOLLIN; status = epoll_ctl(ep, EPOLL_CTL_ADD, st->sd, &ev); if (status < 0) { log_error("epoll ctl on e %d sd %d failed: %s", ep, st->sd, strerror(errno)); goto error; } for (;;) { int n; n = epoll_wait(ep, &ev, 1, st->interval); if (n < 0) { if (errno == EINTR) { continue; } log_error("epoll wait on e %d with m %d failed: %s", ep, st->sd, strerror(errno)); break; } cb(st, &n); } error: status = close(ep); if (status < 0) { log_error("close e %d failed, ignored: %s", ep, strerror(errno)); } ep = -1; } #endif /* NC_HAVE_EPOLL */ nutcracker-0.4.1+dfsg/src/event/nc_event.h0000664000000000000000000000522712542173515017171 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _NC_EVENT_H_ #define _NC_EVENT_H_ #include #define EVENT_SIZE 1024 #define EVENT_READ 0x0000ff #define EVENT_WRITE 0x00ff00 #define EVENT_ERR 0xff0000 typedef int (*event_cb_t)(void *, uint32_t); typedef void (*event_stats_cb_t)(void *, void *); #ifdef NC_HAVE_KQUEUE struct event_base { int kq; /* kernel event queue descriptor */ struct kevent *change; /* change[] - events we want to monitor */ int nchange; /* # change */ struct kevent *event; /* event[] - events that were triggered */ int nevent; /* # event */ int nreturned; /* # event placed in event[] */ int nprocessed; /* # event processed from event[] */ event_cb_t cb; /* event callback */ }; #elif NC_HAVE_EPOLL struct event_base { int ep; /* epoll descriptor */ struct epoll_event *event; /* event[] - events that were triggered */ int nevent; /* # event */ event_cb_t cb; /* event callback */ }; #elif NC_HAVE_EVENT_PORTS #include struct event_base { int evp; /* event port descriptor */ port_event_t *event; /* event[] - events that were triggered */ int nevent; /* # event */ event_cb_t cb; /* event callback */ }; #else # error missing scalable I/O event notification mechanism #endif struct event_base *event_base_create(int size, event_cb_t cb); void event_base_destroy(struct event_base *evb); int event_add_in(struct event_base *evb, struct conn *c); int event_del_in(struct event_base *evb, struct conn *c); int event_add_out(struct event_base *evb, struct conn *c); int event_del_out(struct event_base *evb, struct conn *c); int event_add_conn(struct event_base *evb, struct conn *c); int event_del_conn(struct event_base *evb, struct conn *c); int event_wait(struct event_base *evb, int timeout); void event_loop_stats(event_stats_cb_t cb, void *arg); #endif /* _NC_EVENT_H */ nutcracker-0.4.1+dfsg/src/event/nc_kqueue.c0000664000000000000000000002455512542173515017347 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2011 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #ifdef NC_HAVE_KQUEUE #include struct event_base * event_base_create(int nevent, event_cb_t cb) { struct event_base *evb; int status, kq; struct kevent *change, *event; ASSERT(nevent > 0); kq = kqueue(); if (kq < 0) { log_error("kqueue failed: %s", strerror(errno)); return NULL; } change = nc_calloc(nevent, sizeof(*change)); if (change == NULL) { status = close(kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", kq, strerror(errno)); } return NULL; } event = nc_calloc(nevent, sizeof(*event)); if (event == NULL) { nc_free(change); status = close(kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", kq, strerror(errno)); } return NULL; } evb = nc_alloc(sizeof(*evb)); if (evb == NULL) { nc_free(change); nc_free(event); status = close(kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", kq, strerror(errno)); } return NULL; } evb->kq = kq; evb->change = change; evb->nchange = 0; evb->event = event; evb->nevent = nevent; evb->nreturned = 0; evb->nprocessed = 0; evb->cb = cb; log_debug(LOG_INFO, "kq %d with nevent %d", evb->kq, evb->nevent); return evb; } void event_base_destroy(struct event_base *evb) { int status; if (evb == NULL) { return; } ASSERT(evb->kq > 0); nc_free(evb->change); nc_free(evb->event); status = close(evb->kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", evb->kq, strerror(errno)); } evb->kq = -1; nc_free(evb); } int event_add_in(struct event_base *evb, struct conn *c) { struct kevent *event; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(evb->nchange < evb->nevent); if (c->recv_active) { return 0; } event = &evb->change[evb->nchange++]; EV_SET(event, c->sd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, c); c->recv_active = 1; return 0; } int event_del_in(struct event_base *evb, struct conn *c) { struct kevent *event; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(evb->nchange < evb->nevent); if (!c->recv_active) { return 0; } event = &evb->change[evb->nchange++]; EV_SET(event, c->sd, EVFILT_READ, EV_DELETE, 0, 0, c); c->recv_active = 0; return 0; } int event_add_out(struct event_base *evb, struct conn *c) { struct kevent *event; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); ASSERT(evb->nchange < evb->nevent); if (c->send_active) { return 0; } event = &evb->change[evb->nchange++]; EV_SET(event, c->sd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, c); c->send_active = 1; return 0; } int event_del_out(struct event_base *evb, struct conn *c) { struct kevent *event; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); ASSERT(evb->nchange < evb->nevent); if (!c->send_active) { return 0; } event = &evb->change[evb->nchange++]; EV_SET(event, c->sd, EVFILT_WRITE, EV_DELETE, 0, 0, c); c->send_active = 0; return 0; } int event_add_conn(struct event_base *evb, struct conn *c) { ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(!c->recv_active); ASSERT(!c->send_active); ASSERT(evb->nchange < evb->nevent); event_add_in(evb, c); event_add_out(evb, c); return 0; } int event_del_conn(struct event_base *evb, struct conn *c) { int i; ASSERT(evb->kq > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(evb->nchange < evb->nevent); event_del_out(evb, c); event_del_in(evb, c); /* * Now, eliminate pending events for c->sd (there should be at most one * other event). This is important because we will close c->sd and free * c when we return. */ for (i = evb->nprocessed + 1; i < evb->nreturned; i++) { struct kevent *ev = &evb->event[i]; if (ev->ident == (uintptr_t)c->sd) { ev->flags = 0; ev->filter = 0; break; } } return 0; } int event_wait(struct event_base *evb, int timeout) { int kq = evb->kq; struct timespec ts, *tsp; ASSERT(kq > 0); /* kevent should block indefinitely if timeout < 0 */ if (timeout < 0) { tsp = NULL; } else { tsp = &ts; tsp->tv_sec = timeout / 1000LL; tsp->tv_nsec = (timeout % 1000LL) * 1000000LL; } for (;;) { /* * kevent() is used both to register new events with kqueue, and to * retrieve any pending events. Changes that should be applied to the * kqueue are given in the change[] and any returned events are placed * in event[], up to the maximum sized allowed by nevent. The number * of entries actually placed in event[] is returned by the kevent() * call and saved in nreturned. * * Events are registered with the system by the application via a * struct kevent, and an event is uniquely identified with the system * by a (kq, ident, filter) tuple. This means that there can be only * one (ident, filter) pair for a given kqueue. */ evb->nreturned = kevent(kq, evb->change, evb->nchange, evb->event, evb->nevent, tsp); evb->nchange = 0; if (evb->nreturned > 0) { for (evb->nprocessed = 0; evb->nprocessed < evb->nreturned; evb->nprocessed++) { struct kevent *ev = &evb->event[evb->nprocessed]; uint32_t events = 0; log_debug(LOG_VVERB, "kevent %04"PRIX32" with filter %d " "triggered on sd %d", ev->flags, ev->filter, ev->ident); /* * If an error occurs while processing an element of the * change[] and there is enough room in the event[], then the * event event will be placed in the eventlist with EV_ERROR * set in flags and the system error(errno) in data. */ if (ev->flags & EV_ERROR) { /* * Error messages that can happen, when a delete fails. * EBADF happens when the file descriptor has been closed * ENOENT when the file descriptor was closed and then * reopened. * EINVAL for some reasons not understood; EINVAL * should not be returned ever; but FreeBSD does :-\ * An error is also indicated when a callback deletes an * event we are still processing. In that case the data * field is set to ENOENT. */ if (ev->data == EBADF || ev->data == EINVAL || ev->data == ENOENT || ev->data == EINTR) { continue; } events |= EVENT_ERR; } if (ev->filter == EVFILT_READ) { events |= EVENT_READ; } if (ev->filter == EVFILT_WRITE) { events |= EVENT_WRITE; } if (evb->cb != NULL && events != 0) { evb->cb(ev->udata, events); } } return evb->nreturned; } if (evb->nreturned == 0) { if (timeout == -1) { log_error("kevent on kq %d with %d events and %d timeout " "returned no events", kq, evb->nevent, timeout); return -1; } return 0; } if (errno == EINTR) { continue; } log_error("kevent on kq %d with %d events failed: %s", kq, evb->nevent, strerror(errno)); return -1; } NOT_REACHED(); } void event_loop_stats(event_stats_cb_t cb, void *arg) { struct stats *st = arg; int status, kq; struct kevent change, event; struct timespec ts, *tsp; kq = kqueue(); if (kq < 0) { log_error("kqueue failed: %s", strerror(errno)); return; } EV_SET(&change, st->sd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, NULL); /* kevent should block indefinitely if st->interval < 0 */ if (st->interval < 0) { tsp = NULL; } else { tsp = &ts; tsp->tv_sec = st->interval / 1000LL; tsp->tv_nsec = (st->interval % 1000LL) * 1000000LL; } for (;;) { int nreturned; nreturned = kevent(kq, &change, 1, &event, 1, tsp); if (nreturned < 0) { if (errno == EINTR) { continue; } log_error("kevent on kq %d with m %d failed: %s", kq, st->sd, strerror(errno)); goto error; } ASSERT(nreturned <= 1); if (nreturned == 1) { struct kevent *ev = &event; if (ev->flags & EV_ERROR) { if (ev->data == EINTR) { continue; } log_error("kevent on kq %d with m %d failed: %s", kq, st->sd, strerror(ev->data)); goto error; } } cb(st, &nreturned); } error: status = close(kq); if (status < 0) { log_error("close kq %d failed, ignored: %s", kq, strerror(errno)); } kq = -1; } #endif /* NC_HAVE_KQUEUE */ nutcracker-0.4.1+dfsg/src/event/Makefile.am0000664000000000000000000000042712542173515017250 0ustar rootrootMAINTAINERCLEANFILES = Makefile.in AM_CPPFLAGS = -I $(top_srcdir)/src AM_CFLAGS = -Wall -Wshadow AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value noinst_LIBRARIES = libevent.a noinst_HEADERS = nc_event.h libevent_a_SOURCES = \ nc_epoll.c \ nc_kqueue.c \ nc_evport.c nutcracker-0.4.1+dfsg/src/event/nc_evport.c0000664000000000000000000002531212542173515017357 0ustar rootroot/* * twemproxy - A fast and lightweight proxy for memcached protocol. * Copyright (C) 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #ifdef NC_HAVE_EVENT_PORTS #include #include struct event_base * event_base_create(int nevent, event_cb_t cb) { struct event_base *evb; int status, evp; port_event_t *event; ASSERT(nevent > 0); evp = port_create(); if (evp < 0) { log_error("port create failed: %s", strerror(errno)); return NULL; } event = nc_calloc(nevent, sizeof(*event)); if (event == NULL) { status = close(evp); if (status < 0) { log_error("close evp %d failed, ignored: %s", evp, strerror(errno)); } return NULL; } evb = nc_alloc(sizeof(*evb)); if (evb == NULL) { nc_free(event); status = close(evp); if (status < 0) { log_error("close evp %d failed, ignored: %s", evp, strerror(errno)); } return NULL; } evb->evp = evp; evb->event = event; evb->nevent = nevent; evb->cb = cb; log_debug(LOG_INFO, "evp %d with nevent %d", evb->evp, evb->nevent); return evb; } void event_base_destroy(struct event_base *evb) { int status; if (evb == NULL) { return; } ASSERT(evb->evp >= 0); nc_free(evb->event); status = close(evb->evp); if (status < 0) { log_error("close evp %d failed, ignored: %s", evb->evp, strerror(errno)); } evb->evp = -1; nc_free(evb); } int event_add_in(struct event_base *evb, struct conn *c) { return 0; } int event_del_in(struct event_base *evb, struct conn *c) { return 0; } int event_add_out(struct event_base *evb, struct conn *c) { int status; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (c->send_active) { return 0; } status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN | POLLOUT, c); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); } else { c->send_active = 1; } return status; } int event_del_out(struct event_base *evb, struct conn *c) { int status; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (!c->send_active) { return 0; } status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN, c); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); } else { c->send_active = 0; } return status; } int event_add_conn(struct event_base *evb, struct conn *c) { int status; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(!c->recv_active); ASSERT(!c->send_active); status = port_associate(evp, PORT_SOURCE_FD, c->sd, POLLIN | POLLOUT, c); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); } else { c->send_active = 1; c->recv_active = 1; } return status; } int event_del_conn(struct event_base *evb, struct conn *c) { int status; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); if (!c->send_active && !c->recv_active) { return 0; } /* * Removes the association of an object with a port. The association * is also removed if the port gets closed. * * On failure, we check for ENOENT errno because it is likely that we * are deleting this connection after it was returned from the event * loop and before we had a chance of reactivating it by calling * port_associate() on it. */ status = port_dissociate(evp, PORT_SOURCE_FD, c->sd); if (status < 0 && errno != ENOENT) { log_error("port dissociate evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); return status; } c->recv_active = 0; c->send_active = 0; return 0; } static int event_reassociate(struct event_base *evb, struct conn *c) { int status, events; int evp = evb->evp; ASSERT(evp > 0); ASSERT(c != NULL); ASSERT(c->sd > 0); ASSERT(c->recv_active); if (c->send_active) { events = POLLIN | POLLOUT; } else { events = POLLIN; } status = port_associate(evp, PORT_SOURCE_FD, c->sd, events , c); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, c->sd, strerror(errno)); } return status; } int event_wait(struct event_base *evb, int timeout) { int evp = evb->evp; port_event_t *event = evb->event; int nevent = evb->nevent; struct timespec ts, *tsp; ASSERT(evp > 0); ASSERT(event != NULL); ASSERT(nevent > 0); /* port_getn should block indefinitely if timeout < 0 */ if (timeout < 0) { tsp = NULL; } else { tsp = &ts; tsp->tv_sec = timeout / 1000LL; tsp->tv_nsec = (timeout % 1000LL) * 1000000LL; } for (;;) { int i, status; unsigned int nreturned = 1; /* * port_getn() retrieves multiple events from a port. A port_getn() * call will block until at least nreturned events is triggered. On * a successful return event[] is populated with triggered events * up to the maximum sized allowed by nevent. The number of entries * actually placed in event[] is saved in nreturned, which may be * more than what we asked for but less than nevent. */ status = port_getn(evp, event, nevent, &nreturned, tsp); if (status < 0) { if (errno == EINTR || errno == EAGAIN) { continue; } /* * ETIME - The time interval expired before the expected number * of events have been posted to the port or nreturned is updated * with the number of returned port_event_t structures in event[] */ if (errno != ETIME) { log_error("port getn on evp %d with %d events failed: %s", evp, nevent, strerror(errno)); return -1; } } if (nreturned > 0) { for (i = 0; i < nreturned; i++) { port_event_t *ev = &evb->event[i]; uint32_t events = 0; log_debug(LOG_VVERB, "port %04"PRIX32" from source %d " "triggered on conn %p", ev->portev_events, ev->portev_source, ev->portev_user); if (ev->portev_events & POLLERR) { events |= EVENT_ERR; } if (ev->portev_events & POLLIN) { events |= EVENT_READ; } if (ev->portev_events & POLLOUT) { events |= EVENT_WRITE; } if (evb->cb != NULL && events != 0) { status = evb->cb(ev->portev_user, events); if (status < 0) { continue; } /* * When an event for a PORT_SOURCE_FD object is retrieved, * the object no longer has an association with the port. * The event can be processed without the possibility that * another thread can retrieve a subsequent event for the * same object. After processing of the file descriptor * is completed, the port_associate() function can be * called to reassociate the object with the port. * * If the descriptor is still capable of accepting data, * this reassociation is required for the reactivation of * the data detection. */ event_reassociate(evb, ev->portev_user); } } return nreturned; } if (timeout == -1) { log_error("port getn on evp %d with %d events and %d timeout " "returned no events", evp, nevent, timeout); return -1; } return 0; } NOT_REACHED(); } void event_loop_stats(event_stats_cb_t cb, void *arg) { struct stats *st = arg; int status, evp; port_event_t event; struct timespec ts, *tsp; evp = port_create(); if (evp < 0) { log_error("port create failed: %s", strerror(errno)); return; } status = port_associate(evp, PORT_SOURCE_FD, st->sd, POLLIN, NULL); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, st->sd, strerror(errno)); goto error; } /* port_getn should block indefinitely if st->interval < 0 */ if (st->interval < 0) { tsp = NULL; } else { tsp = &ts; tsp->tv_sec = st->interval / 1000LL; tsp->tv_nsec = (st->interval % 1000LL) * 1000000LL; } for (;;) { unsigned int nreturned = 1; status = port_getn(evp, &event, 1, &nreturned, tsp); if (status != NC_OK) { if (errno == EINTR || errno == EAGAIN) { continue; } if (errno != ETIME) { log_error("port getn on evp %d with m %d failed: %s", evp, st->sd, strerror(errno)); goto error; } } ASSERT(nreturned <= 1); if (nreturned == 1) { /* re-associate monitoring descriptor with the port */ status = port_associate(evp, PORT_SOURCE_FD, st->sd, POLLIN, NULL); if (status < 0) { log_error("port associate on evp %d sd %d failed: %s", evp, st->sd, strerror(errno)); } } cb(st, &nreturned); } error: status = close(evp); if (status < 0) { log_error("close evp %d failed, ignored: %s", evp, strerror(errno)); } evp = -1; } #endif /* NC_HAVE_EVENT_PORTS */ nutcracker-0.4.1+dfsg/conf/0000775000000000000000000000000012542173515014226 5ustar rootrootnutcracker-0.4.1+dfsg/conf/nutcracker.root.yml0000664000000000000000000000022712542173515020075 0ustar rootrootroot: listen: 127.0.0.1:22120 hash: fnv1a_64 distribution: ketama preconnect: true auto_eject_hosts: false servers: - 127.0.0.1:22121:1 nutcracker-0.4.1+dfsg/conf/nutcracker.leaf.yml0000664000000000000000000000032112542173515020014 0ustar rootrootleaf: listen: 127.0.0.1:22121 hash: fnv1a_64 distribution: ketama auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:11212:1 - 127.0.0.1:11213:1 nutcracker-0.4.1+dfsg/conf/nutcracker.yml0000664000000000000000000000242512542173515017115 0ustar rootrootalpha: listen: 127.0.0.1:22121 hash: fnv1a_64 distribution: ketama auto_eject_hosts: true redis: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:6379:1 beta: listen: 127.0.0.1:22122 hash: fnv1a_64 hash_tag: "{}" distribution: ketama auto_eject_hosts: false timeout: 400 redis: true servers: - 127.0.0.1:6380:1 server1 - 127.0.0.1:6381:1 server2 - 127.0.0.1:6382:1 server3 - 127.0.0.1:6383:1 server4 gamma: listen: 127.0.0.1:22123 hash: fnv1a_64 distribution: ketama timeout: 400 backlog: 1024 preconnect: true auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 3 servers: - 127.0.0.1:11212:1 - 127.0.0.1:11213:1 delta: listen: 127.0.0.1:22124 hash: fnv1a_64 distribution: ketama timeout: 100 auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:11214:1 - 127.0.0.1:11215:1 - 127.0.0.1:11216:1 - 127.0.0.1:11217:1 - 127.0.0.1:11218:1 - 127.0.0.1:11219:1 - 127.0.0.1:11220:1 - 127.0.0.1:11221:1 - 127.0.0.1:11222:1 - 127.0.0.1:11223:1 omega: listen: /tmp/gamma hash: hsieh distribution: ketama auto_eject_hosts: false servers: - 127.0.0.1:11214:100000 - 127.0.0.1:11215:1 nutcracker-0.4.1+dfsg/LICENSE0000664000000000000000000002367512542173515014323 0ustar rootroot Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONSnutcracker-0.4.1+dfsg/configure.ac0000664000000000000000000001370212542173515015572 0ustar rootroot# Define the package version numbers and the bug reporting address m4_define([NC_MAJOR], 0) m4_define([NC_MINOR], 4) m4_define([NC_PATCH], 1) m4_define([NC_BUGS], [manj@cs.stanford.edu]) # Initialize autoconf AC_PREREQ([2.64]) AC_INIT([nutcracker], [NC_MAJOR.NC_MINOR.NC_PATCH], [NC_BUGS]) AC_CONFIG_SRCDIR([src/nc.c]) AC_CONFIG_AUX_DIR([config]) AC_CONFIG_HEADERS([config.h:config.h.in]) AC_CONFIG_MACRO_DIR([m4]) # Initialize automake AM_INIT_AUTOMAKE([1.9 foreign]) # Define macro variables for the package version numbers AC_DEFINE(NC_VERSION_MAJOR, NC_MAJOR, [Define the major version number]) AC_DEFINE(NC_VERSION_MINOR, NC_MINOR, [Define the minor version number]) AC_DEFINE(NC_VERSION_PATCH, NC_PATCH, [Define the patch version number]) AC_DEFINE(NC_VERSION_STRING, "NC_MAJOR.NC_MINOR.NC_PATCH", [Define the version string]) # Checks for language AC_LANG([C]) # Checks for programs AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AC_PROG_CXX AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_PROG_RANLIB AC_PROG_LIBTOOL # Checks for typedefs, structures, and compiler characteristics AC_C_INLINE AC_TYPE_INT8_T AC_TYPE_INT16_T AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_INTMAX_T AC_TYPE_INTPTR_T AC_TYPE_UINT8_T AC_TYPE_UINT16_T AC_TYPE_UINT32_T AC_TYPE_UINT64_T AC_TYPE_UINTMAX_T AC_TYPE_UINTPTR_T AC_TYPE_OFF_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_C_BIGENDIAN( [], [AC_DEFINE(HAVE_LITTLE_ENDIAN, 1, [Define to 1 if machine is little endian])], [AC_MSG_ERROR([endianess of this machine is unknown])], [AC_MSG_ERROR([universial endianess not supported])] ) # Checks for header files AC_HEADER_STDBOOL AC_CHECK_HEADERS([fcntl.h float.h limits.h stddef.h stdlib.h string.h unistd.h]) AC_CHECK_HEADERS([inttypes.h stdint.h]) AC_CHECK_HEADERS([sys/ioctl.h sys/time.h sys/uio.h]) AC_CHECK_HEADERS([sys/socket.h sys/un.h netinet/in.h arpa/inet.h netdb.h]) AC_CHECK_HEADERS([execinfo.h], [AC_DEFINE(HAVE_BACKTRACE, [1], [Define to 1 if backtrace is supported])], []) AC_CHECK_HEADERS([sys/epoll.h], [], []) AC_CHECK_HEADERS([sys/event.h], [], []) # Checks for libraries AC_CHECK_LIB([m], [pow]) AC_CHECK_LIB([pthread], [pthread_create]) # Checks for library functions AC_FUNC_FORK AC_FUNC_MALLOC AC_FUNC_REALLOC AC_CHECK_FUNCS([dup2 gethostname gettimeofday strerror]) AC_CHECK_FUNCS([socket]) AC_CHECK_FUNCS([memchr memmove memset]) AC_CHECK_FUNCS([strchr strndup strtoul]) AC_CACHE_CHECK([if epoll works], [ac_cv_epoll_works], AC_TRY_RUN([ #include #include #include int main(int argc, char **argv) { int fd; fd = epoll_create(256); if (fd < 0) { perror("epoll_create:"); exit(1); } exit(0); } ], [ac_cv_epoll_works=yes], [ac_cv_epoll_works=no])) AS_IF([test "x$ac_cv_epoll_works" = "xyes"], [AC_DEFINE([HAVE_EPOLL], [1], [Define to 1 if epoll is supported])], []) AC_CACHE_CHECK([if kqueue works], [ac_cv_kqueue_works], AC_TRY_RUN([ #include #include #include #include #include int main(int argc, char **argv) { int fd; fd = kqueue(); if (fd < 0) { perror("kqueue:"); exit(1); } exit(0); } ], [ac_cv_kqueue_works=yes], [ac_cv_kqueue_works=no])) AS_IF([test "x$ac_cv_kqueue_works" = "xyes"], [AC_DEFINE([HAVE_KQUEUE], [1], [Define to 1 if kqueue is supported])], []) AC_CACHE_CHECK([if event ports works], [ac_cv_evports_works], AC_TRY_RUN([ #include #include #include int main(int argc, char **argv) { int fd; fd = port_create(); if (fd < 0) { perror("port_create:"); exit(1); } exit(0); } ], [ac_cv_evports_works=yes], [ac_cv_evports_works=no])) AS_IF([test "x$ac_cv_evports_works" = "xyes"], [AC_DEFINE([HAVE_EVENT_PORTS], [1], [Define to 1 if event ports is supported])], []) AS_IF([test "x$ac_cv_epoll_works" = "xno" && test "x$ac_cv_kqueue_works" = "xno" && test "x$ac_cv_evports_works" = "xno"], [AC_MSG_ERROR([either epoll or kqueue or event ports support is required])], []) AM_CONDITIONAL([OS_LINUX], [test "x$ac_cv_epoll_works" = "xyes"]) AM_CONDITIONAL([OS_BSD], [test "x$ac_cv_kqueue_works" = "xyes"]) AM_CONDITIONAL([OS_SOLARIS], [test "x$ac_cv_evports_works" = "xyes"]) AM_CONDITIONAL([OS_FREEBSD], [test "$(uname -v | cut -c 1-10)" == "FreeBSD 10"]) # Package options AC_MSG_CHECKING([whether to enable debug logs and asserts]) AC_ARG_ENABLE([debug], [AS_HELP_STRING( [--enable-debug=@<:@full|yes|log|no@:>@], [enable debug logs and asserts @<:@default=no@:>@]) ], [], [enable_debug=no]) AS_CASE([x$enable_debug], [xfull], [AC_DEFINE([HAVE_ASSERT_PANIC], [1], [Define to 1 if panic on an assert is enabled]) AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled]) ], [xyes], [AC_DEFINE([HAVE_ASSERT_LOG], [1], [Define to 1 if log on an assert is enabled]) AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled]) ], [xlog], [AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled])], [xno], [], [AC_MSG_FAILURE([invalid value ${enable_debug} for --enable-debug])]) AC_MSG_RESULT($enable_debug) AC_MSG_CHECKING([whether to disable stats]) AC_ARG_ENABLE([stats], [AS_HELP_STRING( [--disable-stats], [disable stats]) ], [disable_stats=yes], [disable_stats=no]) AS_IF([test "x$disable_stats" = xyes], [], [AC_DEFINE([HAVE_STATS], [1], [Define to 1 if stats is not disabled])]) AC_MSG_RESULT($disable_stats) # Untar the yaml-0.1.4 in contrib/ before config.status is rerun AC_CONFIG_COMMANDS_PRE([tar xvfz contrib/yaml-0.1.4.tar.gz -C contrib]) # Call yaml-0.1.4 ./configure recursively AC_CONFIG_SUBDIRS([contrib/yaml-0.1.4]) # Define Makefiles AC_CONFIG_FILES([Makefile contrib/Makefile src/Makefile src/hashkit/Makefile src/proto/Makefile src/event/Makefile]) # Generate the "configure" script AC_OUTPUT nutcracker-0.4.1+dfsg/README.md0000664000000000000000000003753512542173515014575 0ustar rootroot# twemproxy (nutcracker) [![Build Status](https://secure.travis-ci.org/twitter/twemproxy.png)](http://travis-ci.org/twitter/twemproxy) **twemproxy** (pronounced "two-em-proxy"), aka **nutcracker** is a fast and lightweight proxy for [memcached](http://www.memcached.org/) and [redis](http://redis.io/) protocol. It was built primarily to reduce the number of connections to the caching servers on the backend. This, together with protocol pipelining and sharding enables you to horizontally scale your distributed caching architecture. ## Build To build twemproxy from [distribution tarball](https://drive.google.com/open?id=0B6pVMMV5F5dfMUdJV25abllhUWM&authuser=0): $ ./configure $ make $ sudo make install To build twemproxy from [distribution tarball](https://drive.google.com/open?id=0B6pVMMV5F5dfMUdJV25abllhUWM&authuser=0) in _debug mode_: $ CFLAGS="-ggdb3 -O0" ./configure --enable-debug=full $ make $ sudo make install To build twemproxy from source with _debug logs enabled_ and _assertions enabled_: $ git clone git@github.com:twitter/twemproxy.git $ cd twemproxy $ autoreconf -fvi $ ./configure --enable-debug=full $ make $ src/nutcracker -h A quick checklist: + Use newer version of gcc (older version of gcc has problems) + Use CFLAGS="-O1" ./configure && make + Use CFLAGS="-O3 -fno-strict-aliasing" ./configure && make + `autoreconf -fvi && ./configure` needs `automake` and `libtool` to be installed ## Features + Fast. + Lightweight. + Maintains persistent server connections. + Keeps connection count on the backend caching servers low. + Enables pipelining of requests and responses. + Supports proxying to multiple servers. + Supports multiple server pools simultaneously. + Shard data automatically across multiple servers. + Implements the complete [memcached ascii](notes/memcache.md) and [redis](notes/redis.md) protocol. + Easy configuration of server pools through a YAML file. + Supports multiple hashing modes including consistent hashing and distribution. + Can be configured to disable nodes on failures. + Observability via stats exposed on the stats monitoring port. + Works with Linux, *BSD, OS X and SmartOS (Solaris) ## Help Usage: nutcracker [-?hVdDt] [-v verbosity level] [-o output file] [-c conf file] [-s stats port] [-a stats addr] [-i stats interval] [-p pid file] [-m mbuf size] Options: -h, --help : this help -V, --version : show version and exit -t, --test-conf : test configuration for syntax errors and exit -d, --daemonize : run as a daemon -D, --describe-stats : print stats description and exit -v, --verbose=N : set logging level (default: 5, min: 0, max: 11) -o, --output=S : set logging file (default: stderr) -c, --conf-file=S : set configuration file (default: conf/nutcracker.yml) -s, --stats-port=N : set stats monitoring port (default: 22222) -a, --stats-addr=S : set stats monitoring ip (default: 0.0.0.0) -i, --stats-interval=N : set stats aggregation interval in msec (default: 30000 msec) -p, --pid-file=S : set pid file (default: off) -m, --mbuf-size=N : set size of mbuf chunk in bytes (default: 16384 bytes) ## Zero Copy In twemproxy, all the memory for incoming requests and outgoing responses is allocated in mbuf. Mbuf enables zero-copy because the same buffer on which a request was received from the client is used for forwarding it to the server. Similarly the same mbuf on which a response was received from the server is used for forwarding it to the client. Furthermore, memory for mbufs is managed using a reuse pool. This means that once mbuf is allocated, it is not deallocated, but just put back into the reuse pool. By default each mbuf chunk is set to 16K bytes in size. There is a trade-off between the mbuf size and number of concurrent connections twemproxy can support. A large mbuf size reduces the number of read syscalls made by twemproxy when reading requests or responses. However, with a large mbuf size, every active connection would use up 16K bytes of buffer which might be an issue when twemproxy is handling large number of concurrent connections from clients. When twemproxy is meant to handle a large number of concurrent client connections, you should set chunk size to a small value like 512 bytes using the -m or --mbuf-size=N argument. ## Configuration Twemproxy can be configured through a YAML file specified by the -c or --conf-file command-line argument on process start. The configuration file is used to specify the server pools and the servers within each pool that twemproxy manages. The configuration files parses and understands the following keys: + **listen**: The listening address and port (name:port or ip:port) or an absolute path to sock file (e.g. /var/run/nutcracker.sock) for this server pool. + **hash**: The name of the hash function. Possible values are: + one_at_a_time + md5 + crc16 + crc32 (crc32 implementation compatible with [libmemcached](http://libmemcached.org/)) + crc32a (correct crc32 implementation as per the spec) + fnv1_64 + fnv1a_64 + fnv1_32 + fnv1a_32 + hsieh + murmur + jenkins + **hash_tag**: A two character string that specifies the part of the key used for hashing. Eg "{}" or "$$". [Hash tag](notes/recommendation.md#hash-tags) enable mapping different keys to the same server as long as the part of the key within the tag is the same. + **distribution**: The key distribution mode. Possible values are: + ketama + modula + random + **timeout**: The timeout value in msec that we wait for to establish a connection to the server or receive a response from a server. By default, we wait indefinitely. + **backlog**: The TCP backlog argument. Defaults to 512. + **preconnect**: A boolean value that controls if twemproxy should preconnect to all the servers in this pool on process start. Defaults to false. + **redis**: A boolean value that controls if a server pool speaks redis or memcached protocol. Defaults to false. + **redis_auth**: Authenticate to the Redis server on connect. + **redis_db**: The DB number to use on the pool servers. Defaults to 0. Note: Twemproxy will always present itself to clients as DB 0. + **server_connections**: The maximum number of connections that can be opened to each server. By default, we open at most 1 server connection. + **auto_eject_hosts**: A boolean value that controls if server should be ejected temporarily when it fails consecutively server_failure_limit times. See [liveness recommendations](notes/recommendation.md#liveness) for information. Defaults to false. + **server_retry_timeout**: The timeout value in msec to wait for before retrying on a temporarily ejected server, when auto_eject_host is set to true. Defaults to 30000 msec. + **server_failure_limit**: The number of consecutive failures on a server that would lead to it being temporarily ejected when auto_eject_host is set to true. Defaults to 2. + **servers**: A list of server address, port and weight (name:port:weight or ip:port:weight) for this server pool. For example, the configuration file in [conf/nutcracker.yml](conf/nutcracker.yml), also shown below, configures 5 server pools with names - _alpha_, _beta_, _gamma_, _delta_ and omega. Clients that intend to send requests to one of the 10 servers in pool delta connect to port 22124 on 127.0.0.1. Clients that intend to send request to one of 2 servers in pool omega connect to unix path /tmp/gamma. Requests sent to pool alpha and omega have no timeout and might require timeout functionality to be implemented on the client side. On the other hand, requests sent to pool beta, gamma and delta timeout after 400 msec, 400 msec and 100 msec respectively when no response is received from the server. Of the 5 server pools, only pools alpha, gamma and delta are configured to use server ejection and hence are resilient to server failures. All the 5 server pools use ketama consistent hashing for key distribution with the key hasher for pools alpha, beta, gamma and delta set to fnv1a_64 while that for pool omega set to hsieh. Also only pool beta uses [nodes names](notes/recommendation.md#node-names-for-consistent-hashing) for consistent hashing, while pool alpha, gamma, delta and omega use 'host:port:weight' for consistent hashing. Finally, only pool alpha and beta can speak the redis protocol, while pool gamma, deta and omega speak memcached protocol. alpha: listen: 127.0.0.1:22121 hash: fnv1a_64 distribution: ketama auto_eject_hosts: true redis: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:6379:1 beta: listen: 127.0.0.1:22122 hash: fnv1a_64 hash_tag: "{}" distribution: ketama auto_eject_hosts: false timeout: 400 redis: true servers: - 127.0.0.1:6380:1 server1 - 127.0.0.1:6381:1 server2 - 127.0.0.1:6382:1 server3 - 127.0.0.1:6383:1 server4 gamma: listen: 127.0.0.1:22123 hash: fnv1a_64 distribution: ketama timeout: 400 backlog: 1024 preconnect: true auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 3 servers: - 127.0.0.1:11212:1 - 127.0.0.1:11213:1 delta: listen: 127.0.0.1:22124 hash: fnv1a_64 distribution: ketama timeout: 100 auto_eject_hosts: true server_retry_timeout: 2000 server_failure_limit: 1 servers: - 127.0.0.1:11214:1 - 127.0.0.1:11215:1 - 127.0.0.1:11216:1 - 127.0.0.1:11217:1 - 127.0.0.1:11218:1 - 127.0.0.1:11219:1 - 127.0.0.1:11220:1 - 127.0.0.1:11221:1 - 127.0.0.1:11222:1 - 127.0.0.1:11223:1 omega: listen: /tmp/gamma hash: hsieh distribution: ketama auto_eject_hosts: false servers: - 127.0.0.1:11214:100000 - 127.0.0.1:11215:1 Finally, to make writing a syntactically correct configuration file easier, twemproxy provides a command-line argument -t or --test-conf that can be used to test the YAML configuration file for any syntax error. ## Observability Observability in twemproxy is through logs and stats. Twemproxy exposes stats at the granularity of server pool and servers per pool through the stats monitoring port. The stats are essentially JSON formatted key-value pairs, with the keys corresponding to counter names. By default stats are exposed on port 22222 and aggregated every 30 seconds. Both these values can be configured on program start using the -c or --conf-file and -i or --stats-interval command-line arguments respectively. You can print the description of all stats exported by using the -D or --describe-stats command-line argument. $ nutcracker --describe-stats pool stats: client_eof "# eof on client connections" client_err "# errors on client connections" client_connections "# active client connections" server_ejects "# times backend server was ejected" forward_error "# times we encountered a forwarding error" fragments "# fragments created from a multi-vector request" server stats: server_eof "# eof on server connections" server_err "# errors on server connections" server_timedout "# timeouts on server connections" server_connections "# active server connections" requests "# requests" request_bytes "total request bytes" responses "# responses" response_bytes "total response bytes" in_queue "# requests in incoming queue" in_queue_bytes "current request bytes in incoming queue" out_queue "# requests in outgoing queue" out_queue_bytes "current request bytes in outgoing queue" Logging in twemproxy is only available when twemproxy is built with logging enabled. By default logs are written to stderr. Twemproxy can also be configured to write logs to a specific file through the -o or --output command-line argument. On a running twemproxy, we can turn log levels up and down by sending it SIGTTIN and SIGTTOU signals respectively and reopen log files by sending it SIGHUP signal. ## Pipelining Twemproxy enables proxying multiple client connections onto one or few server connections. This architectural setup makes it ideal for pipelining requests and responses and hence saving on the round trip time. For example, if twemproxy is proxying three client connections onto a single server and we get requests - 'get key\r\n', 'set key 0 0 3\r\nval\r\n' and 'delete key\r\n' on these three connections respectively, twemproxy would try to batch these requests and send them as a single message onto the server connection as 'get key\r\nset key 0 0 3\r\nval\r\ndelete key\r\n'. Pipelining is the reason why twemproxy ends up doing better in terms of throughput even though it introduces an extra hop between the client and server. ## Deployment If you are deploying twemproxy in production, you might consider reading through the [recommendation document](notes/recommendation.md) to understand the parameters you could tune in twemproxy to run it efficiently in the production environment. ## Packages ### Ubuntu #### PPA Stable https://launchpad.net/~twemproxy/+archive/ubuntu/stable #### PPA Daily https://launchpad.net/~twemproxy/+archive/ubuntu/daily ## Utils + [nagios checks](https://github.com/wanelo/nagios-checks/blob/master/check_twemproxy) + [circunous](https://github.com/wanelo-chef/nad-checks/blob/master/recipes/twemproxy.rb) + [puppet module](https://github.com/wuakitv/puppet-twemproxy) + [nutcracker-web](https://github.com/kontera-technologies/nutcracker-web) + [munin-plugin](https://github.com/eveiga/contrib/tree/nutcracker/plugins/nutcracker) + [collectd-plugin](https://github.com/bewie/collectd-twemproxy) + [redis-twemproxy agent](https://github.com/Stono/redis-twemproxy-agent) + [sensu-metrics](https://github.com/sensu/sensu-community-plugins/blob/master/plugins/twemproxy/twemproxy-metrics.rb) + [redis-mgr](https://github.com/idning/redis-mgr) + [smitty for twemproxy failover](https://github.com/areina/smitty) + [Beholder, a Python agent for twemproxy failover](https://github.com/Serekh/beholder) + [chef cookbook](https://supermarket.getchef.com/cookbooks/twemproxy) + [twemsentinel] (https://github.com/yak0/twemsentinel) ## Companies using Twemproxy in Production + [Pinterest](http://pinterest.com/) + [Tumblr](https://www.tumblr.com/) + [Twitter](https://twitter.com/) + [Vine](http://vine.co/) + [Kiip](http://www.kiip.me/) + [Wuaki.tv](https://wuaki.tv/) + [Wanelo](http://wanelo.com/) + [Kontera](http://kontera.com/) + [Wikimedia](http://www.wikimedia.org/) + [Bright](http://www.bright.com/) + [56.com](http://www.56.com/) + [Snapchat](http://www.snapchat.com/) + [Digg](http://digg.com/) + [Gawkermedia](http://advertising.gawker.com/) + [3scale.net](http://3scale.net) + [Ooyala](http://www.ooyala.com) + [Twitch](http://twitch.tv) + [Socrata](http://www.socrata.com/) + [Hootsuite](http://hootsuite.com/) + [Trivago](http://www.trivago.com/) + [Machinezone](http://www.machinezone.com) + [Flickr](https://www.flickr.com) + [Yahoo!](https://www.yahoo.com) + [Path](https://path.com) + [AOL](http://engineering.aol.com/) + [Soysuper](https://soysuper.com/) + [Vinted](http://vinted.com/) + [Poshmark](https://poshmark.com/) + [FanDuel](https://www.fanduel.com/) ## Issues and Support Have a bug or a question? Please create an issue here on GitHub! https://github.com/twitter/twemproxy/issues ## Committers * Manju Rajashekhar ([@manju](https://twitter.com/manju)) * Lin Yang ([@idning](https://github.com/idning)) Thank you to all of our [contributors](https://github.com/twitter/twemproxy/graphs/contributors)! ## License Copyright 2012 Twitter, Inc. Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 nutcracker-0.4.1+dfsg/scripts/0000775000000000000000000000000012542173515014770 5ustar rootrootnutcracker-0.4.1+dfsg/scripts/redis-check.sh0000775000000000000000000014726212542173515017524 0ustar rootroot#!/bin/sh port=6379 port=22121 debug="-v -d" debug="-d" timeout="-t 1" timeout="" # keys printf '\ndel\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\ndel\r\n$3\r\nfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nbar\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$3\r\ndel\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$6\r\nfoobar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ndump\n' printf '*2\r\n$4\r\ndump\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\ndump\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nexists\n' printf '*2\r\n$6\r\nexists\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$6\r\nexists\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nexpire\n' printf '*3\r\n$6\r\nexpire\r\n$3\r\nfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nexpire\r\n$3\r\nfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npersist\n' printf '*2\r\n$7\r\npersist\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nexpire\r\n$3\r\nfoo\r\n$2\r\n10\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npersist\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npersist\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nexpireat\n' printf '*3\r\n$8\r\nexpireat\r\n$3\r\nfoo\r\n$10\r\n1282463464\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$8\r\nexpireat\r\n$3\r\nfoo\r\n$10\r\n1282463464\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nexpire\n' printf '*3\r\n$7\r\npexpire\r\n$3\r\nfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$7\r\npexpire\r\n$3\r\nfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrestore\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$7\r\nrestore\r\n$3\r\nfoo\r\n$1\r\n0\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npttl\n' printf '*2\r\n$4\r\npttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$7\r\npexpire\r\n$3\r\nfoo\r\n$7\r\n1000000\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\npttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nttl\n' printf '*2\r\n$4\r\npttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nexpire\r\n$3\r\nfoo\r\n$2\r\n10\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nttl\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ntype\n' printf '*2\r\n$4\r\ntype\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\ntype\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # strings printf '\nappend\n' printf '*3\r\n$6\r\nappend\r\n$3\r\nfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nappend\r\n$3\r\n999\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nbitcount\n' printf '*2\r\n$8\r\nbitcount\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\nbitcount\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ndecr\n' printf '*2\r\n$4\r\ndecr\r\n$7\r\ncounter\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ndecrby\n' printf '*3\r\n$6\r\ndecrby\r\n$7\r\ncounter\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nget\n' printf '*2\r\n$3\r\nget\r\n$16\r\nnon-existent-key\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ngetbit\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\ngetbit\r\n$3\r\nfoo\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\ngetbit\r\n$3\r\nfoo\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ngetrange\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\ngetrange\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\ngetrange\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\ngetrange\r\n$3\r\nfoo\r\n$1\r\n1\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\ngetset\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\ngetset\r\n$3\r\nfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nincr\n' printf '*2\r\n$3\r\ndel\r\n$7\r\ncounter\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nincr\r\n$7\r\ncounter\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nincrby\n' printf '*3\r\n$6\r\nincrby\r\n$7\r\ncounter\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nincrbyfloat\n' printf '*3\r\n$11\r\nincrbyfloat\r\n$7\r\ncounter\r\n$5\r\n10.10\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nmget\n' printf '*2\r\n$4\r\nmget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nmget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nmget\r\n$3\r\nfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*13\r\n$4\r\nmget\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npsetex\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\npsetex\r\n$3\r\nfoo\r\n$4\r\n1000\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nset\n' printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsetbit\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nsetbit\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\n000\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nsetbit\r\n$3\r\nfoo\r\n$1\r\n1\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npsetex\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nsetex\r\n$3\r\nfoo\r\n$4\r\n1000\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsetnx\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nsetnx\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$3\r\nset\r\n$3\r\nfoo\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nsetnx\r\n$3\r\nfoo\r\n$3\r\nooo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsetrange\n' printf '*2\r\n$3\r\ndel\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\nsetrange\r\n$3\r\nfoo\r\n$1\r\n1\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$8\r\nsetrange\r\n$3\r\nfoo\r\n$1\r\n4\r\n$3\r\noof\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\nget\r\n$3\r\nfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # hashes printf '\nhdel\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhdel\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhdel\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhexists\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhdel\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$7\r\nhexists\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$7\r\nhexists\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhget\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhdel\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nhget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nhget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhgetall\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\n1dleif\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhincrby\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$7\r\nhincrby\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhincrbyfloat\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$12\r\nhincrbyfloat\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$6\r\n100.12\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhkeys\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhkeys\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\n1dleif\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhkeys\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhkeys\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhlen\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nhlen\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\n1dleif\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nhlen\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhmget\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nhmget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nhmget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$6\r\n1dleif\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\n1dleif\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nhmget\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$6\r\n1dleif\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhmset\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$5\r\nhmset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n$6\r\nfield2\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhset\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nhset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhsetnx\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nhsetnx\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nhsetnx\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\nhgetall\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nhvals\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhvals\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$5\r\nhmset\r\n$4\r\nhfoo\r\n$6\r\nfield1\r\n$3\r\nbar\r\n$6\r\nfield2\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nhvals\r\n$4\r\nhfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # lists printf '\nlindex\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlindex\r\n$4\r\nlfoo\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlindex\r\n$4\r\nlfoo\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlinsert\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$7\r\nlinsert\r\n$4\r\nlfoo\r\n$6\r\nBEFORE\r\n$3\r\nbar\r\n$3\r\nbaq\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlindex\r\n$4\r\nlfoo\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nllen\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nllen\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nllen\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlpop\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nlpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbaq\r\n$3\r\nbap\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nlpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nlpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nlpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlpush\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbaq\r\n$3\r\nbap\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlpushx\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlpushx\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlpushx\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nlpushx\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlrange\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n$3\r\nbat\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlrem\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlrem\r\n$4\r\nlfoo\r\n$1\r\n2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbar\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlrem\r\n$4\r\nlfoo\r\n$1\r\n2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlrem\r\n$4\r\nlfoo\r\n$1\r\n2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nlset\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlset\r\n$4\r\nlfoo\r\n$1\r\n1\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlset\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$3\r\nbaq\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nlset\r\n$4\r\nlfoo\r\n$1\r\n1\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nltrim\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nltrim\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbaq\r\n$3\r\nbap\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrpop\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nrpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nlpush\r\n$4\r\nlfoo\r\n$3\r\nbaq\r\n$3\r\nbap\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nrpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nrpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nrpop\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrpoplpush\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{lfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{lfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$5\r\nlpush\r\n$6\r\n{lfoo}\r\n$3\r\nbar\r\n$3\r\nbaq\r\n$3\r\nbap\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nrpoplpush\r\n$6\r\n{lfoo}\r\n$7\r\n{lfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nrpoplpush\r\n$6\r\n{lfoo}\r\n$7\r\n{lfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$6\r\n{lfoo}\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$7\r\n{lfoo}2\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrpush\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nrpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nrpush\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nrpush\r\n$4\r\nlfoo\r\n$3\r\nbat\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nrpushx\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nlfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nrpushx\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nrpush\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nrpushx\r\n$4\r\nlfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nrpushx\r\n$4\r\nlfoo\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nlrange\r\n$4\r\nlfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # sets printf '\nsadd\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nscard\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nscard\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nscard\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsdiff\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nsdiff\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsdiffstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$10\r\nsdiffstore\r\n$7\r\n{sfoo}3\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsinter\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nsinter\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsinterstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nsinterstore\r\n$7\r\n{sfoo}3\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsismember\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nsismember\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nsismember\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$9\r\nsismember\r\n$4\r\nsfoo\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsmembers\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsmove\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\nsmove\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nspop\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nspop\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$4\r\nspop\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsrandmember\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$11\r\nsrandmember\r\n$4\r\nsfoo\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsrem\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nsfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsrem\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$4\r\nsadd\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n$3\r\nbat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsrem\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$4\r\nsrem\r\n$4\r\nsfoo\r\n$3\r\nbas\r\n$3\r\nbat\r\n$3\r\nrab\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsunion\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nsunion\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nsunionstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{sfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nsadd\r\n$6\r\n{sfoo}\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nsadd\r\n$7\r\n{sfoo}2\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nsunionstore\r\n$7\r\n{sfoo}3\r\n$6\r\n{sfoo}\r\n$7\r\n{sfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$8\r\nsmembers\r\n$7\r\n{sfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # sorted sets printf '\nzadd\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzcard\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nzcard\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$5\r\nzcard\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzcount\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzcount\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzcount\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzcount\r\n$4\r\nzfoo\r\n$4\r\n-inf\r\n$4\r\n+inf\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzincrby\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$7\r\nzincrby\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$7\r\nzincrby\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzinterstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{zfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{zfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{zfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$6\r\n{zfoo}\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$4\r\nzadd\r\n$7\r\n{zfoo}2\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$11\r\nzinterstore\r\n$7\r\n{zfoo}3\r\n$1\r\n2\r\n$6\r\n{zfoo}\r\n$7\r\n{zfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$6\r\nzrange\r\n$7\r\n{zfoo}3\r\n$1\r\n0\r\n$1\r\n3\r\n$10\r\nWITHSCORES\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrange\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$6\r\nzrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$6\r\nzrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n3\r\n$10\r\nWITHSCORES\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrangebyscore\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$13\r\nzrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$13\r\nzrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrank\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nzrank\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\nzrank\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrem\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\nzrem\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzrem\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzremrangebyrank\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$15\r\nzremrangebyrank\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$15\r\nzremrangebyrank\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzremrangebyscore\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$16\r\nzremrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$16\r\nzremrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\n101\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrevrange\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzrevrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzrevrange\r\n$4\r\nzfoo\r\n$1\r\n0\r\n$1\r\n2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrevrangebyscore\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$16\r\nzrevrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n101\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$16\r\nzrevrangebyscore\r\n$4\r\nzfoo\r\n$3\r\n101\r\n$3\r\n100\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrevrank\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$8\r\nzrevrank\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$8\r\nzrevrank\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzscore\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nzscore\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$6\r\nzscore\r\n$4\r\nzfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzunionstore\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{zfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{zfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{zfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$6\r\n{zfoo}\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*6\r\n$4\r\nzadd\r\n$7\r\n{zfoo}2\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$11\r\nzunionstore\r\n$7\r\n{zfoo}3\r\n$1\r\n2\r\n$6\r\n{zfoo}\r\n$7\r\n{zfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$6\r\nzrange\r\n$7\r\n{zfoo}3\r\n$1\r\n0\r\n$1\r\n3\r\n$10\r\nWITHSCORES\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzlexcount\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzlexcount\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$2\r\n(z\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzlexcount\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$4\r\n[bat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$9\r\nzlexcount\r\n$4\r\nzfoo\r\n$1\r\n-\r\n$1\r\n+\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzrangebylex\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nzrangebylex\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$2\r\n(z\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nzrangebylex\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$4\r\n[bat\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$11\r\nzrangebylex\r\n$4\r\nzfoo\r\n$1\r\n-\r\n$1\r\n+\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\nzremrangebylex\n' printf '*2\r\n$3\r\ndel\r\n$4\r\nzfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$14\r\nzremrangebylex\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$2\r\n(z\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*8\r\n$4\r\nzadd\r\n$4\r\nzfoo\r\n$3\r\n100\r\n$3\r\nbar\r\n$3\r\n101\r\n$3\r\nbat\r\n$3\r\n102\r\n$3\r\nbau\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$14\r\nzremrangebylex\r\n$4\r\nzfoo\r\n$2\r\n(a\r\n$2\r\n(z\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$14\r\nzremrangebylex\r\n$4\r\nzfoo\r\n$1\r\n-\r\n$1\r\n+\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # hyperloglog printf '\npfadd\n' printf '*2\r\n$3\r\ndel\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\npfadd\r\n$4\r\npfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npfcount\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npfcount\n' printf '*2\r\n$3\r\ndel\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npfcount\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\npfadd\r\n$4\r\npfoo\r\n$3\r\nbar\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npfcount\r\n$4\r\npfoo\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '\npfmerge\n' printf '*2\r\n$3\r\ndel\r\n$6\r\n{pfoo}\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{pfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$3\r\ndel\r\n$7\r\n{pfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$5\r\npfadd\r\n$6\r\n{pfoo}\r\n$4\r\nsfoo\r\n$3\r\nbar\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$5\r\npfadd\r\n$7\r\n{pfoo}2\r\n$3\r\nbas\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*5\r\n$7\r\npfmerge\r\n$7\r\n{pfoo}3\r\n$1\r\n2\r\n$6\r\n{pfoo}\r\n$7\r\n{pfoo}2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*2\r\n$7\r\npfcount\r\n$7\r\n{pfoo}3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # scripting printf '\neval\n' printf '*2\r\n$4\r\neval\r\n$10\r\nreturn 123\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\neval\r\n$10\r\nreturn 123\r\n$1\r\n0\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*3\r\n$4\r\neval\r\n$10\r\nreturn 123\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\neval\r\n$10\r\nreturn 123\r\n$1\r\n1\r\n$1\r\n1\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*7\r\n$4\r\neval\r\n$40\r\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}\r\n$1\r\n2\r\n$9\r\nkey1{tag}\r\n$4\r\narg1\r\n$9\r\nkey2{tag}\r\n$4\r\narg2\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*9\r\n$4\r\neval\r\n$56\r\nreturn {KEYS[1],KEYS[2],KEYS[3],ARGV[1],ARGV[2],ARGV[3]}\r\n$1\r\n3\r\n$9\r\nkey1{tag}\r\n$4\r\narg1\r\n$9\r\nkey2{tag}\r\n$4\r\narg2\r\n$9\r\nkey3{tag}\r\n$4\r\narg3\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close printf '*4\r\n$4\r\neval\r\n$11\r\nreturn {10}\r\n$1\r\n1\r\n$4\r\nTEMP\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close # quit printf '\nquit\n' printf '*1\r\n$4\r\nquit\r\n' | socat ${debug} ${timeout} - TCP:localhost:${port},shut-close nutcracker-0.4.1+dfsg/scripts/nutcracker.init.debian0000664000000000000000000000344412542173515021244 0ustar rootroot#!/bin/sh ### BEGIN INIT INFO # Provides: nutcracker # Required-Start: $network $remote_fs $local_fs # Required-Stop: $network $remote_fs $local_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Stop/start nutcracker ### END INIT INFO PATH=/sbin:/usr/sbin:/bin:/usr/bin DESC=nutcracker NAME=nutcracker USER=nobody CONFFILE=/opt/nutcracker/etc/$NAME.yml LOGFILE=/opt/nutcracker/log/nutcracker.log DAEMON=/opt/nutcracker/sbin/nutcracker PIDFILE=/var/run/nutcracker/$NAME.pid STATSPORT=22222 DAEMON_ARGS="-c $CONFFILE -o $LOGFILE -p $PIDFILE -s $STATSPORT -v 11 -m 2048 -d" #DAEMON_ARGS="-c $CONFFILE -p $PIDFILE -s $STATSPORT -d" SCRIPTNAME=/etc/init.d/$NAME ulimit -Hn 100000 ulimit -Sn 100000 [ -x $DAEMON ] || exit 0 [ -r /etc/default/$NAME ] && . /etc/default/$NAME . /lib/init/vars.sh . /lib/lsb/init-functions do_start() { mkdir -p /var/run/nutcracker touch $PIDFILE chown $USER:$USER -R /var/run/nutcracker chmod 755 /var/run/nutcracker echo -n "Starting ${NAME}: " start-stop-daemon --start --quiet -m --pidfile $PIDFILE --chuid $USER:$USER --exec $DAEMON -- \ $DAEMON_ARGS case "$?" in 0|1) echo "STARTED." ;; 2) echo "FAILED." ;; esac } do_stop() { echo -n "Stopping ${NAME}: " start-stop-daemon --stop --quiet --pidfile $PIDFILE --exec $DAEMON || true case "$?" in 0|1) echo "STOPPED.";; 2) echo "FAILED." ;; esac } case "$1" in start) do_start ;; stop) do_stop ;; status) status_of_proc -p $PIDFILE "$DAEMON" nutcracker && exit 0 || exit $? ;; restart) do_stop do_start ;; *) echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2 exit 3 ;; esac exit $RETVAL nutcracker-0.4.1+dfsg/scripts/pipelined_write.sh0000775000000000000000000000117712542173515020520 0ustar rootroot#!/bin/sh socatopt="-t 1 -T 1 -b 16384" val=`echo 6^6^6 | bc` val=`printf "%s" "${val}"` vallen=`printf "%s" "${val}" | wc -c` set_command="" set_commands="" # build for i in `seq 1 64`; do if [ `expr $i % 2` -eq "0" ]; then key="foo" else key="bar" fi key=`printf "%s%d" "${key}" "${i}"` set_command="set ${key} 0 0 ${vallen}\r\n${val}\r\n" set_commands=`printf "%s%s" "${set_commands}" "${set_command}"` done printf "%b" "$set_commands" > /tmp/socat.input # write for i in `seq 1 16`; do cat /tmp/socat.input | socat ${socatopt} - TCP:localhost:22123,nodelay,shut-down,nonblock=1 & done nutcracker-0.4.1+dfsg/scripts/pipelined_read.sh0000775000000000000000000000076012542173515020276 0ustar rootroot#!/bin/sh socatopt="-t 4 -T 4 -b 8193 -d -d " get_commands="" # build for i in `seq 1 128`; do if [ `expr $i % 2` -eq "0" ]; then key="foo" else key="bar" fi key=`printf "%s%d" "${key}" "${i}"` get_command="get ${key}\r\n" get_commands=`printf "%s%s" "${get_commands}" "${get_command}"` done # read for i in `seq 1 64`; do printf "%b" "$get_commands" | socat ${socatopt} - TCP:localhost:22123,nodelay,shut-none,nonblock=1 1> /dev/null 2>&1 & done nutcracker-0.4.1+dfsg/scripts/nutcracker.spec0000664000000000000000000001240412542173515020006 0ustar rootrootSummary: Twitter's nutcracker redis and memcached proxy Name: nutcracker Version: 0.4.1 Release: 1 URL: https://github.com/twitter/twemproxy/ Source0: %{name}-%{version}.tar.gz License: Apache License 2.0 Group: System Environment/Libraries Packager: Tom Parrott BuildRoot: %{_tmppath}/%{name}-root BuildRequires: autoconf BuildRequires: automake BuildRequires: libtool %description twemproxy (pronounced "two-em-proxy"), aka nutcracker is a fast and lightweight proxy for memcached and redis protocol. It was primarily built to reduce the connection count on the backend caching servers. This, together with protocol pipelining and sharding enables you to horizontally scale your distributed caching architecture. %prep %setup -q %if 0%{?rhel} == 6 sed -i 's/2.64/2.63/g' configure.ac %endif autoreconf -fvi %build %configure %__make %install [ %{buildroot} != "/" ] && rm -rf %{buildroot} %makeinstall PREFIX=%{buildroot} #Install init script %{__install} -p -D -m 0755 scripts/%{name}.init %{buildroot}%{_initrddir}/%{name} #Install example config file %{__install} -p -D -m 0644 conf/%{name}.yml %{buildroot}%{_sysconfdir}/%{name}/%{name}.yml %post /sbin/chkconfig --add %{name} %preun if [ $1 = 0 ]; then /sbin/service %{name} stop > /dev/null 2>&1 /sbin/chkconfig --del %{name} fi %clean [ %{buildroot} != "/" ] && rm -rf %{buildroot} %files %defattr(-,root,root,-) %if 0%{?rhel} == 6 /usr/sbin/nutcracker %else /usr/bin/nutcracker %endif %{_initrddir}/%{name} %{_mandir}/man8/nutcracker.8.gz %config(noreplace)%{_sysconfdir}/%{name}/%{name}.yml %changelog * Mon Jun 22 2015 Manju Rajashekhar - twemproxy: version 0.4.1 release - redis_auth is only valid for a redis pool - getaddrinfo returns non-zero +ve value on error - fix-hang-when-command-only (charsyam) - fix bug crash when get command without key and whitespace (charsyam) - mark server as failed on protocol level transiet failures like -OOM, -LOADING, etc - implemented support for parsing fine grained redis error response - remove redundant conditional judgement in rbtree deletion (leo ma) - fix bug mset has invalid pair (charsyam) - fix bug mset has invalid pair (charsyam) - temp fix a core on kqueue (idning) - support "touch" command for memcached (panmiaocai) - fix redis parse rsp bug (charsyam) - SORT command can take multiple arguments. So it should be part of redis_argn() and not redis_arg0() - remove incorrect assert because client could send data after sending a quit request which must be discarded - allow file permissions to be set for UNIX domain listening socket (ori liveneh) - return error if formatted is greater than mbuf size by using nc_vsnprintf() in msg_prepend_format() - fix req_make_reply on msg_get, mark it as response (idning) - redis database select upon connect (arne claus) - redis_auth (charsyam) - allow null key(empty key) (idning) - fix core on invalid mset like "mset a a a" (idning) * Tue Oct 18 2014 idning - twemproxy: version 0.4.0 release - mget improve (idning) - many new commands supported: LEX, PFADD, PFMERGE, SORT, PING, QUIT, SCAN... (mattrobenolt, areina, idning) - handle max open file limit(allenlz) - add notice-log and use ms time in log(idning) - fix bug in string_compare (andyqzb) - fix deadlock in sighandler (idning) * Fri Dec 20 2013 Manju Rajashekhar - twemproxy: version 0.3.0 release - SRANDMEMBER support for the optional count argument (mkhq) - Handle case where server responds while the request is still being sent (jdi-tagged) - event ports (solaris/smartos) support - add timestamp when the server was ejected - support for set ex/px/nx/xx for redis 2.6.12 and up (ypocat) - kqueue (bsd) support (ferenyx) - fix parsing redis response to accept integer reply (charsyam) * Tue Jul 30 2013 Tait Clarridge - Rebuild SPEC to work with CentOS - Added buildrequires if building with mock/koji * Tue Apr 23 2013 Manju Rajashekhar - twemproxy: version 0.2.4 release - redis keys must be less than mbuf_data_size() in length (fifsky) - Adds support for DUMP/RESTORE commands in Redis (remotezygote) - Use of the weight value in the modula distribution (mezzatto) - Add support to unix socket connections to servers (mezzatto) - only check for duplicate server name and not 'host:port:weight' when 'name' is configured - crc16 hash support added (mezzatto) * Thu Jan 31 2013 Manju Rajashekhar - twemproxy: version 0.2.3 release - RPOPLPUSH, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION, SUNIONSTORE, ZINTERSTORE, and ZUNIONSTORE support (dcartoon) - EVAL and EVALSHA support (ferenyx) - exit 1 if configuration file is invalid (cofyc) - return non-zero exit status when nutcracker cannot start for some reason - use server names in stats (charsyam) - Fix failure to resolve long FQDN name resolve (conmame) - add support for hash tags * Thu Oct 18 2012 Manju Rajashekhar - twemproxy: version 0.2.2 release - fix the off-by-one error when calculating redis key length * Fri Oct 12 2012 Manju Rajashekhar - twemproxy: version 0.2.1 release - don't use buf in conf_add_server - allow an optional instance name for consistent hashing (charsyam) - add --stats-addr=S option - add stats-bind-any -a option (charsyam) nutcracker-0.4.1+dfsg/scripts/nutcracker.init0000664000000000000000000000267312542173515020026 0ustar rootroot#! /bin/sh # # chkconfig: - 55 45 # description: Twitter's twemproxy nutcracker # processname: nutcracker # config: /etc/sysconfig/nutcracker # Source function library. . /etc/rc.d/init.d/functions USER="nobody" OPTIONS="-d -c /etc/nutcracker/nutcracker.yml" if [ -f /etc/sysconfig/nutcracker ];then . /etc/sysconfig/nutcracker fi # Check that networking is up. if [ "$NETWORKING" = "no" ] then exit 0 fi RETVAL=0 prog="nutcracker" start () { echo -n $"Starting $prog: " #Test the config before start. daemon --user ${USER} ${prog} $OPTIONS -t >/dev/null 2>&1 RETVAL=$? if [ $RETVAL -ne 0 ] ; then echo "Config check fail! Please use 'nutcracker -c /etc/nutcracker/nutcracker.yml' for detail." echo_failure; echo; exit 1 fi daemon --user ${USER} ${prog} $OPTIONS RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/${prog} } stop () { echo -n $"Stopping $prog: " killproc ${prog} RETVAL=$? echo if [ $RETVAL -eq 0 ] ; then rm -f /var/lock/subsys/${prog} fi } restart () { stop start } # See how we were called. case "$1" in start) start ;; stop) stop ;; status) status ${prog} ;; restart|reload) restart ;; condrestart) [ -f /var/lock/subsys/nutcracker ] && restart || : ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}" exit 1 esac exit $? nutcracker-0.4.1+dfsg/scripts/populate_memcached.sh0000775000000000000000000000075712542173515021157 0ustar rootroot#!/bin/sh port=22123 socatopt="-t 1 -T 1 -b 65537" val=`echo 6^6^6 | bc` val=`printf "%s\r\n" "${val}"` vallen=`printf "%s" "${val}" | wc -c` set_command="" # build for i in `seq 1 512`; do if [ `expr $i % 2` -eq "0" ]; then key="foo" else key="bar" fi key=`printf "%s%d" "${key}" "${i}"` set_command="set ${key} 0 0 ${vallen}\r\n${val}\r\n" printf "%b" "$set_command" | socat ${socatopt} - TCP:localhost:${port},nodelay,shut-down,nonblock=1 & done nutcracker-0.4.1+dfsg/scripts/multi_get.sh0000775000000000000000000000101612542173515017316 0ustar rootroot#!/bin/sh port=22123 socatopt="-t 20 -T 20 -b 8193 -d -d " key="" keys="" get_command="" # build for i in `seq 1 512`; do if [ `expr $i % 2` -eq "0" ]; then key="foo" else key="bar" fi key=`printf "%s%d" "${key}" "${i}"` keys=`printf "%s %s" "${keys}" "${key}"` done get_command="get ${keys}\r\n" printf "%b" "$get_command" # read for i in `seq 1 16`; do printf "%b" "${get_command}" | socat ${socatopt} - TCP:localhost:${port},nodelay,shut-none,nonblock=1 1> /dev/null 2>&1 & done nutcracker-0.4.1+dfsg/scripts/redis-check.py0000664000000000000000000000123112542173515017520 0ustar rootrootimport redis range=100 factor=32 port=22121 r = redis.StrictRedis(host='localhost', port=port, db=0) # lrange print [r.lrange('lfoo', 0, x) for x in xrange(1, range)] print [r.lpush('lfoo', str(x)*factor) for x in xrange(1, range)] print [r.lrange('lfoo', 0, x) for x in xrange(1, range)] print r.delete('lfoo') # del print [r.set('foo' + str(x), str(x)*factor) for x in xrange(1, range)] keys = ['foo' + str(x) for x in xrange(1, range)] print [r.delete(keys) for x in xrange(1, range)] # mget print [r.set('foo' + str(x), str(x)*100) for x in xrange(1, range)] keys = ['foo' + str(x) for x in xrange(1, range)] print [r.mget(keys) for x in xrange(1, range)] nutcracker-0.4.1+dfsg/scripts/benchmark-mget.py0000664000000000000000000000216612542173515020233 0ustar rootroot#!/usr/bin/env python #coding: utf-8 #file : test_mget.py #author : ning #date : 2014-04-01 13:15:48 import os import re import commands ports = [ 4001, # before improve 4000, # after improve 2000 # redis ] def system(cmd): return commands.getoutput(cmd) def extra(regex, text): match = re.search(regex, text, re.DOTALL) if match: return match.group(1) def testit(): for mget_size in [10, 100, 1000, 10000]: for port in ports: cnt = 100*1000 / mget_size clients = 50 if mget_size == 10000: clients = 2 cmd = 'cd /home/ning/xredis/deploy-srcs/redis-2.8.3/src && ./redis-benchmark.%d -n %d -p %d -t mget -r 1000000000 -c %d' % (mget_size, cnt, port, clients) #print cmd rst = system(cmd) #100.00% <= 2 milliseconds #28089.89 requests per second rtime = extra('100.00% <= (\d+) milliseconds', rst) qps = extra('([\.\d]+) requests per second', rst) print 'mget_size=%d on %d: pqs: %s, rtime: %s' % (mget_size, port, qps, rtime) testit() nutcracker-0.4.1+dfsg/man/0000775000000000000000000000000012542173515014054 5ustar rootrootnutcracker-0.4.1+dfsg/man/nutcracker.80000664000000000000000000000411312542173515016305 0ustar rootroot.TH NUTCRACKER 8 "June 13, 2013" .SH NAME nutcracker \- Fast, light-weight proxy for memcached and Redis .SH SYNOPSIS .B nutcracker .RI [ options ] .SH DESCRIPTION \fBnutcracker\fP, also known as \fBtwemproxy\fP (pronounced "two-em-proxy"), is a fast and lightweight proxy for the memcached and Redis protocols. .PP It was primarily built to reduce the connection count on backend caching servers, but it has a number of features, such as: .IP \[bu] Maintains persistent server connections to backend servers. .IP \[bu] Enables pipelining of requests and responses. .IP \[bu] Supports multiple server pools simultaneously. .IP \[bu] Shard data automatically across multiple servers. .IP \[bu] Supports multiple hashing modes including consistent hashing and distribution. .IP \[bu] High-availability by disabling nodes on failures. .IP \[bu] Observability through stats exposed on stats monitoring port. .SH OPTIONS .TP .BR \-h ", " \-\-help Show usage information and exit. .TP .BR \-V ", " \-\-version Show version and exit. .TP .BR \-t ", " \-\-test-conf Test configuration for syntax errors and exit. .TP .BR \-D ", " \-\-describe-stats Print stats description and exit. .TP .BR \-v ", " \-\-verbose=\fIN\fP Set logging level to \fIN\fP. (default: 5, min: 0, max: 11) .TP .BR \-o ", " \-\-output=\fIfilename\fP Set logging file to \fIfilename\fP. .TP .BR \-c ", " \-\-conf-file=\fIfilename\fP Set configuration file to \fIfilename\fP. .TP .BR \-s ", " \-\-stats-port=\fIport\fP Set stats monitoring port to \fIport\fP. (default: 22222) .TP .BR \-a ", " \-\-stats-addr=\fIaddress\fP Set stats monitoring IP to \fIaddress\fP. (default: 0.0.0.0) .TP .BR \-i ", " \-\-stats-interval=\fIinterval\fP Set stats aggregation interval in msec to \fIinterval\fP. (default: 30000 msec) .TP .BR \-m ", " \-\-mbuf-size=\fIsize\fP Set size of mbuf chunk in bytes to \fIsize\fP. (default: 16384 bytes) .TP .BR \-d ", " \-\-daemonize Run as a daemon. .TP .BR \-p ", " \-\-pid-file=\fIfilename\fP Set pid file to \fIfilename\fP. .SH SEE ALSO .BR memcached (8), .BR redis-server (1) .br .SH AUTHOR nutcracker was written by Twitter, Inc. nutcracker-0.4.1+dfsg/notes/0000775000000000000000000000000012542173515014431 5ustar rootrootnutcracker-0.4.1+dfsg/notes/redis.md0000664000000000000000000016237712542173515016101 0ustar rootroot## Redis Command Support ### Keys Command +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DEL | Yes | DEL key [key …] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DUMP | Yes | DUMP key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EXISTS | Yes | EXISTS key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EXPIRE | Yes | EXPIRE key seconds | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EXPIREAT | Yes | EXPIREAT key timestamp | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | KEYS | No | KEYS pattern | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MIGRATE | No | MIGRATE host port key destination-db timeout | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MOVE | No | MOVE key db | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | OBJECT | No | OBJECT subcommand [arguments [arguments …]] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PERSIST | Yes | PERSIST key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PEXPIRE | Yes | PEXPIRE key milliseconds | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PEXPIREAT | Yes | PEXPIREAT key milliseconds-timestamp | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PTTL | Yes | PTTL key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RANDOMKEY | No | RANDOMKEY | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RENAME | No | RENAME key newkey | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RENAMENX | No | RENAMENX key newkey | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RESTORE | Yes | RESTORE key ttl serialized-value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SORT | Yes | SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | TTL | Yes | TTL key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | TYPE | Yes | TYPE key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCAN | No | SCAN cursor [MATCH pattern] [COUNT count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Strings Command +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | APPEND | Yes | APPEND key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BITCOUNT | Yes | BITCOUNT key [start] [end] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BITOP | No | BITOP operation destkey key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DECR | Yes | DECR key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DECRBY | Yes | DECRBY key decrement | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | GET | Yes | GET key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | GETBIT | Yes | GETBIT key offset | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | GETRANGE | Yes | GETRANGE key start end | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | GETSET | Yes | GETSET key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | INCR | Yes | INCR key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | INCRBY | Yes | INCRBY key increment | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | INCRBYFLOAT | Yes | INCRBYFLOAT key increment | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MGET | Yes | MGET key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MSET | Yes* | MSET key value [key value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MSETNX | No | MSETNX key value [key value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PSETEX | Yes | PSETEX key milliseconds value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SET | Yes | SET key value [EX seconds] [PX milliseconds] [NX|XX] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SETBIT | Yes | SETBIT key offset value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SETEX | Yes | SETEX key seconds value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SETNX | Yes | SETNX key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SETRANGE | Yes | SETRANGE key offset value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | STRLEN | Yes | STRLEN key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * MSET support is not Atomic ### Hashes +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HDEL | Yes | HDEL key field [field ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HEXISTS | Yes | HEXISTS key field | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HGET | Yes | HGET key field | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HGETALL | Yes | HGETALL key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HINCRBY | Yes | HINCRBY key field increment | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HINCRBYFLOAT | Yes | HINCRBYFLOAT key field increment | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HKEYS | Yes | HKEYS key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HLEN | Yes | HLEN key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HMGET | Yes | HMGET key field [field ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HMSET | Yes | HMSET key field value [field value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HSET | Yes | HSET key field value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HSETNX | Yes | HSETNX key field value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HVALS | Yes | HVALS key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | HSCAN | Yes | HSCAN key cursor [MATCH pattern] [COUNT count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Lists +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BLPOP | No | BLPOP key [key ...] timeout | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BRPOP | No | BRPOP key [key ...] timeout | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BRPOPLPUSH | No | BRPOPLPUSH source destination timeout | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LINDEX | Yes | LINDEX key index | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LINSERT | Yes | LINSERT key BEFORE|AFTER pivot value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LLEN | Yes | LLEN key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LPOP | Yes | LPOP key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LPUSH | Yes | LPUSH key value [value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LPUSHX | Yes | LPUSHX key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LRANGE | Yes | LRANGE key start stop | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LREM | Yes | LREM key count value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LSET | Yes | LSET key index value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LTRIM | Yes | LTRIM key start stop | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RPOP | Yes | RPOP key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RPOPLPUSH | Yes* | RPOPLPUSH source destination | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RPUSH | Yes | RPUSH key value [value ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | RPUSHX | Yes | RPUSHX key value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * RPOPLPUSH support requires that source and destination keys hash to the same server. You can ensure this by using the same [hashtag](recommendation.md#hash-tags) for source and destination key. Twemproxy does no checking on its end to verify that source and destination key hash to the same server, and the RPOPLPUSH command is forwarded to the server that the source key hashes to ### Sets +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SADD | Yes | SADD key member [member ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCARD | Yes | SCARD key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SDIFF | Yes* | SDIFF key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SDIFFSTORE | Yes* | SDIFFSTORE destination key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SINTER | Yes* | SINTER key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SINTERSTORE | Yes* | SINTERSTORE destination key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SISMEMBER | Yes | SISMEMBER key member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SMEMBERS | Yes | SMEMBERS key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SMOVE | Yes* | SMOVE source destination member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SPOP | Yes | SPOP key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SRANDMEMBER | Yes | SRANDMEMBER key [count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SREM | Yes | SREM key member [member ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SUNION | Yes* | SUNION key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SUNIONSTORE | Yes* | SUNIONSTORE destination key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SSCAN | Yes | SSCAN key cursor [MATCH pattern] [COUNT count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * SIDFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION and SUNIONSTORE support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](recommendation.md#hash-tags) for all keys in the command. Twemproxy does no checking on its end to verify that all the keys hash to the same server, and the given command is forwarded to the server that the first key hashes to. ### Sorted Sets +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZADD | Yes | ZADD key score member [score] [member] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZCARD | Yes | ZCARD key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZCOUNT | Yes | ZCOUNT key min max | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZINCRBY | Yes | ZINCRBY key increment member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZINTERSTORE | Yes* | ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] | +------------------------------------------------------------------------------------------------------------------------------------------------------+ | ZLEXCOUNT | Yes | ZLEXCOUNT key min max | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZRANGE | Yes | ZRANGE key start stop [WITHSCORES] | +------------------------------------------------------------------------------------------------------------------------------------------------------+ | ZRANGEBYLEX | Yes | ZRANGEBYLEX key min max [LIMIT offset count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZRANGEBYSCORE | Yes | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZRANK | Yes | ZRANK key member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREM | Yes | ZREM key member [member ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREMRANGEBYLEX | Yes | ZREMRANGEBYLEX key min max | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREMRANGEBYRANK | Yes | ZREMRANGEBYRANK key start stop | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREMRANGEBYSCORE | Yes | ZREMRANGEBYSCORE key min max | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREVRANGE | Yes | ZREVRANGE key start stop [WITHSCORES] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREVRANGEBYSCORE | Yes | ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZREVRANK | Yes | ZREVRANK key member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZSCORE | Yes | ZSCORE key member | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZUNIONSTORE | Yes* | ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ZSCAN | Yes | ZSCAN key cursor [MATCH pattern] [COUNT count] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * ZINTERSTORE and ZUNIONSTORE support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](recommendation.md#hash-tags) for all keys in the command. Twemproxy does no checking on its end to verify that all the keys hash to the same server, and the given command is forwarded to the server that the first key hashes to. ### HyperLogLog +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PFADD | Yes | PFADD key element [element ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PFCOUNT | Yes | PFCOUNT key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PFMERGE | Yes* | PFMERGE destkey sourcekey [sourcekey ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * PFMERGE support requires that the supplied keys hash to the same server. You can ensure this by using the same [hashtag](recommendation.md#hash-tags) for all keys in the command. Twemproxy does no checking on its end to verify that all the keys hash to the same server, and the given command is forwarded to the server that the first key hashes to. ### Pub/Sub +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PSUBSCRIBE | No | PSUBSCRIBE pattern [pattern ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PUBLISH | No | PUBLISH channel message | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PUNSUBSCRIBE | No | PUNSUBSCRIBE [pattern [pattern ...]] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SUBSCRIBE | No | SUBSCRIBE channel [channel ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | UNSUBSCRIBE | No | UNSUBSCRIBE [channel [channel ...]] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Transactions +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DISCARD | No | DISCARD | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EXEC | No | EXEC | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MULTI | No | MULTI | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | UNWATCH | No | UNWATCH | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | WATCH | No | WATCH key [key ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Scripting +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EVAL | Yes* | EVAL script numkeys key [key ...] arg [arg ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | EVALSHA | Yes* | EVALSHA sha1 numkeys key [key ...] arg [arg ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCRIPT EXISTS | No | SCRIPT EXISTS script [script ...] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCRIPT FLUSH | No | SCRIPT FLUSH | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCRIPT KILL | No | SCRIPT KILL | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SCRIPT LOAD | No | SCRIPT LOAD script | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ * EVAL and EVALSHA support is limited to scripts that take at least 1 key. If multiple keys are used, all keys must hash to the same server. You can ensure this by using the same [hashtag](recommendation.md#hash-tags) for all keys. If you use more than 1 key, the proxy does no checking to verify that all keys hash to the same server, and the entire command is forwarded to the server that the first key hashes to ### Connection +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | AUTH | No | AUTH password | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | ECHO | No | ECHO message | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | PING | No | PING | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | QUIT | No | QUIT | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SELECT | No | SELECT index | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ### Server +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BGREWRITEAOF | No | BGREWRITEAOF | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | BGSAVE | No | BGSAVE | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CLIENT KILL | No | CLIENT KILL ip:port | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CLIENT LIST | No | CLIENT LIST | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CONFIG GET | No | CONFIG GET parameter | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CONFIG SET | No | CONFIG SET parameter value | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | CONFIG RESETSTAT | No | CONFIG RESETSTAT | +-------------------+-------------+--------------------------------------------------------------------------------------------------------------------+ | DBSIZE | No | DBSIZE | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DEBUG OBJECT | No | DEBUG OBJECT key | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | DEBUG SEGFAULT | No | DEBUG SEGFAULT | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | FLUSHALL | No | FLUSHALL | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | FLUSHDB | No | FLUSHDB | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | INFO | No | INFO | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | LASTSAVE | No | LASTSAVE | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | MONITOR | No | MONITOR | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SAVE | No | SAVE | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SHUTDOWN | No | SHUTDOWN [NOSAVE] [SAVE] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SLAVEOF | No | SLAVEOF host port | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SLOWLOG | No | SLOWLOG subcommand [argument] | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | SYNC | No | SYNC | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ | TIME | No | TIME | +-------------------+------------+---------------------------------------------------------------------------------------------------------------------+ ## Note - redis commands are not case sensitive - only vectored commands 'MGET key [key ...]', 'MSET key value [key value ...]', 'DEL key [key ...]' needs to be fragmented ## Performance ### Setup + redis-server running on machine A. + nutcracker running on machine A as a local proxy to redis-server. + redis-benchmark running on machine B. + machine A != machine B. + nutcracker built with --enable-debug=no + nutcracker running with mbuf-size of 512 (-m 512) + redis-server built from redis 2.6 branch ### redis-benchmark against redis-server $ redis-benchmark -h -q -t set,get,incr,lpush,lpop,sadd,spop,lpush,lrange -c 100 -p 6379 SET: 89285.71 requests per second GET: 92592.59 requests per second INCR: 89285.71 requests per second LPUSH: 90090.09 requests per second LPOP: 90090.09 requests per second SADD: 90090.09 requests per second SPOP: 93457.95 requests per second LPUSH (needed to benchmark LRANGE): 89285.71 requests per second LRANGE_100 (first 100 elements): 36496.35 requests per second LRANGE_300 (first 300 elements): 15748.03 requests per second LRANGE_500 (first 450 elements): 11135.86 requests per second LRANGE_600 (first 600 elements): 8650.52 requests per second ### redis-benchmark against nutcracker proxing redis-server $ redis-benchmark -h -q -t set,get,incr,lpush,lpop,sadd,spop,lpush,lrange -c 100 -p 22121 SET: 85470.09 requests per second GET: 86956.52 requests per second INCR: 85470.09 requests per second LPUSH: 84745.77 requests per second LPOP: 86206.90 requests per second SADD: 84745.77 requests per second SPOP: 86956.52 requests per second LPUSH (needed to benchmark LRANGE): 84745.77 requests per second LRANGE_100 (first 100 elements): 29761.90 requests per second LRANGE_300 (first 300 elements): 12376.24 requests per second LRANGE_500 (first 450 elements): 8605.85 requests per second LRANGE_600 (first 600 elements): 6587.62 requests per second ## redis-auth feature + you can enable redis-auth for a pool with 'redis_auth': alpha: listen: 127.0.0.1:22121 hash: fnv1a_64 distribution: ketama redis: true redis_auth: testpass + notice: + *MUST* set all redis with a same passwd, and all twemproxy with the same passwd + Length of password should less than 256 nutcracker-0.4.1+dfsg/notes/kqueue.pdf0000664000000000000000000030013112542173515016421 0ustar rootroot%PDF-1.2 %Çì¢ 4 0 obj <> stream xœ­[YoäÆöq…^8€Dó>’—8H‚l8Ž­l?P3”†‡œå±²þ}ú¨êúÈæ¬6@° ´8Íî:¾úê`D7¡þGÿÎï?¾ÿx™5þïp¾ùãýû¯¿Ïo¢4HÒ<¾¹|oŸn¢$ Ò›<‹ƒ<¿¹?õ÷K³4¿ûíý/ï³"ˆ¢ªRÝ¿úF¯DIP¹•§¦oÆö ×ÓR?YÚõº?êµ8ŠŒŸuW?tÙ6Ôô³’›÷vå“^)‚°¨ªœžég³¡:bÁ¿ë‡¹}w¨çvèÍóqPe|€ÇúÐvíüªÿðõ÷åM©?fæÖwq\qQÝÜ¥aPÅúé¿ }=Ÿj³MšiÁÛü£9ÛÍõUPåqžê=²4(>Ç/~ê›æ?ü)FýƒÒYÉUâÒVE&nɈ73WdÁå–Z9l§šåºS=4dœu: „Ó"„—žDV"ÆÚ¼$*£I¶Æ0Ê•w\šQ»bÝÀÕزÉîÒÔm35^’:åªÇ¡áÅÔáºwZ<Ø>*wÜ—¦ëÌ£UxF£Åâ-¶@¶=»@¢7d»8ŒM=1ŠepÌ~9?XÕ%1ž»z–³k±YsI+ˆt÷N.œ­íg%\ºœØV}°‚‰p‡zdã—£ÕÝ4Ø»„¥Þ–ïÒµçv¶¦lY6n{(ŧÔÆfºPYý '.¶N©â: øÕšy$¿wö¥gJ— ‹åJ¬bë*<Ý“1àÔíSíd³adÝ|VÌ¡eÄIS°u#öfšIÛi ±ì ·d« ˜ÈCž{”àiÞ—k›¥÷Û§ÓL·ˆ§X¬Æ|n«Ùƒ[Ûß’ëÇ[GL@YîmŠŸ…ðãC·2qyzjŸdO·Ø%ླêˆ2ŸwQE4ËaÈAéð©¡JPG“Éæ‡z.qΗ®1ôÃ8WV®œË† †$ÖK}¡è‡þvQÒSV1 ­@Z¢èa¨Ã3k(®K²f­+j¶ƒõ–µ* ‰úŽMǦUä׃Ä+ýP‚á¹Ñòk§ó-ÏØðË©=œˆ>Áuºnà7 dN´$Н™ö\v ÂÊ€•3gÀä°Ú‰Édf"·Çq8{„¤¶á.ÎóÒÙp夣¶Ò'DøG Âÿ›^äÊÓ°Œ2Ne»F`»l¢…fÏH.Ñ5MFBˆ›I¶9a„éFü)+§Øqéd{z/>Y¬B œµf03 ® ÃæêåÞtôæÑþ>ѽ;´œŸYèÉý*ÀÁÀþ^#{âÂ"Ý[ötÖn”g$ÂXY¿_ˆ‘”ÄúÉÑú¯*­vÔ-tr‰7%#‘Iö&•«0a{\æÅ†9eô¢ÚÿÅ(Sô¡•Ze?Íq†e¦uñnƒ°L_JŸØ­yƒHw›¨ã°Úù¹ºˆð°ë\Ũu›m*Å™¬íNŸ2×OFdM²å‡~=…ÇåÀØf¥êb&Rß)¸–– 2 ë(Àg"Îɾ’²·•$>v[²ž*ö~Çi;EfT®óM¡ÍŽIˆ»³rk¯â‚FDÏ7ìRÛSÓŽ¤4`ÏâˆLäÇgz¸ELj43T¢n$ ¬i²êF0±“&SÜmÂÆpŽHÉá’q²zîž9wq1‡Œ ?˜‰,G†œ:81™2ņ±>³I€.b øfëÃm§åaj>.êò”gúœ=Èv 'cè±@¶Jr¬ª¶7s L\ Û* ‡¤é¤îi¼8I`ߟæ¼¾Qf9o3ÄDûø:CŒÀíëƒÜÈû.*à *‰ã¦ dE ‰—W`¥ÎÁS›Kcõ¶NâìQ°°y"ü‡*ÌËÆÉ1­VW̲¥bM*AÊ%«ÈÌ©Pè]c«Ôc3 ¹ŠA¨\–‘«XoeR"Òv1/ñ˜R³ë¡ œ8°ú +ßÙb–ú•DÍó!¸àeä…¬}R„o"ÕBQïÅÓÃ`¹b,°µªÙˆAJ½Ë#„/X]°Òä¸YÁõAˆp)DØbR†‡T‚ì”ë.e8kÊWõ8W4°ä>ßµ¤ 0°ä¦DbÅ¢rnɯ€ïº’YàØÿ±²µªh÷MËûºL÷è18¼ø¦GV*W—úPºo¤”PåX£êQ!æO{çØØÃ…Ò½»ˆ´AÃæ`%óV2:¥.A×óзêIJð1t°ñ"î~!má"’æÝ$Oд€ t5—EèO–ô­BÕ7ÐÚ'¿GG,U†n¡8U°ŽŸHg‘L¹vë/š§ölj0a z«Ú—–””PB²‡°h&ëæÔ&[š±©ˆ ËDާHRrÕ° ûqiÏ?°óŽî²ªŽïã5VY%Ä]×5£¦~æa˜ç®é›Ãó-Z /]Sy—t…²Î¡æúbî~i9sÉÞ‘ =Ñ¿Ÿ-ôf"ÿc/$ülo›èÏ·ý1 9ì,ül±/¡f¯K‡Š_å[X<ŽéçéUŠÂµyÄQ¬3ÇØ¨ñ ØçëÈÑq²¢˜G†‹ËãÀY\„Í ßé(Ä ¼d*ñ†k~ÓÔ^%¨©W‘zKEùêAÆ•ó¸Å+Nj¢¶çžëxe E!Çõ)3“'SÓ+·=BS=r8p‡~°D?Ūû‡¯ÿéÉò§¯êv –ÃÛFÔL\”}PÕ–jké*Ë©åT Òb£sÛ4Å$áý-óÅÂCçtÓ—éwôv9ÒÍAº\U¦EÍ2)F'û`²]Ÿ÷äà*ˆ¤r/7yàc%’ÿƒÒ<•,WìEŠ|æ ¨n'­ÚŒÐÂÜ‘côòðÔȸeØ |sUÃi-¡[µ’Ah«úBb%É ôŽG]ººµÓ*Ú†^4JÝ`¨B•‚™•@bŸž[¥8{wÝW.q*¬{÷kº”TÛÃeÎ3<ã’É"BºÂªV˜æ"‚£‹ à®Åœ»P‘ytçØÌuk‹Í¦tcC×ú§~æ1ÃC&Œaz¯Š—,¼ý=E; “¢&´#,]3KP/‰ðÇOc}æ:ú]T˜àÄ‚Ø1ÅZ§n R˜ÑË©íxº-ò(Åó†Œ}ÓÑiHê×è"ƒãÜpñb uЛ;¶Óa™&¨— š®+ËíÑX0_Ì’¨„½ï6e~äMg•z)ˆ=³ä!À¬4½D0ÔÀóÆÔâDžyE ,ò> }½Ä’ruñdÿÚâŽ7ÄM®=Xê; •¯…OîT4ÊÍÂMD¹<#—ökÊÃh›4+Ò!ó*þPX¶;0›A¶nÚDaAŒÓ} m+¶¦Å†Ëù\Û*Ü ›!lº Á†jÄÌèSOƒ)4(]]%…ôyTìˆÎðÐÙê©7[FŸ½Üé FŒ¤£BÀ¸VU‘F»£T×k*oÌý¯d:-«!×ÇfÄÈàfj“è‘.²½“òA±*èüí`_jŸ¥ 3(²¸l£Ü$TŸ˜:/‚¬'.*`ï–L‚_íªoPêgªÓ)K„Ö•=-mŒÜ~ä·awˆª é΄©kÓßÊCxÈÄ+n_§ã2óMÏèVn@¦ ý~(Ĉ…Ë×Aèòtï › íùÜ[%æ)hè gúô\šà w>ñÁY„Ã^QsoÔTÚëZtˆÉðÞd¼2´B›ssÆM-âÂ_n §«ã0ñ¨Ãª¯N“*^2~ýLG #¯ÀAï’Dvë)}lv?C:. ZÿdøY@}Vo› \Ä8eâ®ô¥¢åÇ( ²^Ú£…ñ+*µ=¬?WÂÙOë†Æm¿`mû@õ]oÆ•ðWQûú·6\ÑšOd-GÎ8Úm°}ReCð4P° uV -Åj'3\— àrÈWÄKìô ‹4X+©•èIîVŠˆw²ªí(تvÑê*€íHò—½Œ§[öåÆ ´"(Üp“G×pê³æ”*¼Üüzч¢ú_‰°±W°!~õÃÇÆ[âB°Ð‚“r*ÊÂU–Û©ìܽǰNµ H„vê‚öÍ)6¤ìÏ%R4|žò¹±ä œ}g®à\?KOØMöORÎ>¼Fb6ë° ?¾Xz•Ç7ÓD0”â7%4ð§À çß—[ñ"ó±™—Ñ6Ö³µ_:-Í¡R‚¿8¥?^wÏÞ¶{¯ZED±oùóýÍ¿Þÿëý®~›endstream endobj 5 0 obj 4481 endobj 3 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /Text] /Font << /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 4 0 R >> endobj 12 0 obj <> stream xœ]Û’ãFrõŽ|‹ùŠ~³1Mw ô$ÇÊö¼H^í8ì½ It7¶I€Hõôþ†öƒ]—ÌÊS¨»gc´Ã!ª¼œÌ<™U³Ý$7[ý?úïîøþ×÷¿Þ$æ3þÏîxóïŸÞûs}Ólš2-ó›O÷ïí÷“›*½©’l“¨_ŸÇo>ýå}²Ý4©úËOû¯ß {óI½©è“ócg>)6yÕ4ö³}7ï¦þt§Yÿ]^nš¤©ùû홑$üyHVðgOæ§ú—Me?馡;èOSµ>÷½c;™¯¦ÛMáØÙe¦°¨Ö¬%Ià[­þ¨Ù¤YÓäö£ßúáAx›n+½˜›Û$5¿Þ{óû|“”üÕó‹]µýóFÿ¡2 ¦OþÜ;³±´ØE¸Ù¼þ²­> ­!¥_>“~òŒ¿óüØïÍ/«M•¬i¤Úl3ÖÈs7u´åbSVzËɦ®×v®õƒ•¢~›3ŠÞJ»Ú¤NS7_góyZƒEômBDÔZQf"Êýåtèwí¹Ú@é60Þ“fERÏö[õrZ÷8=}g¾Ö˜O—ª[lOæM·F$I“n±~}b^Ïx™E§Ê´Ùì®=ì.‡ölÞË®ý·æ›Ì}¿îÇé(ûk6jAK—©`‡±‡Ì/ó¹;šµ4›ºŠ£F_¸M“RkÍÙøaêÚ½±é,1mäFÕšxÛ‰—#‚ï7ôÙ–¼ë£ÙGm¾³ú¨ñrØ“C‹ñ(­tíD[ÛºW8€ÊÜjï:²Àq´«SÎ$?íî훵ÕÑ›ßíún8“óÙ—(iUZ)àˆUàd¹æÍ¥1b]u éÒÿSýNÖo<^HòÛS;ÏdÚ‚wí2ñ5itÎk9ôÖxÕbÉ¡Ð#—Rh·XاPgO=i» ? #½­fô™`Ü»,v4ú ËDî{(ÚìË–ÿ×®¡/µ‡'yCY$IÌpÒÁÀ[Þ@ï¥mþôË×?þòyE¿ŒïbF>XQ*Å[×dc`/Á´÷c7“³Tî‘ÃÈ«9Ì :²(ˆÝá@¦S.#¥z¢3±í–µgö“‡ÎB¹Šã‚zë¢^@z°J¹MòªÑ KTÅ‹!ˆáâ&ùº| !Ë¢ÀØŸ/[³4l•Y‰flóH;K³@¤ô³}×6ôîÚ}ëϧvDZ4Ñ¿æÝºÂ'¤JÁBîNjͰ”’üÊ‘Ï<Ž!«HZ!«¯5¢Ka©Ú´¿£mâû§ÆãJ¥¥§Þg2Þm©…ï¢&gu xõÐly`˜Õݽ,bˆ²ãù¯ŠRŽGõù¸ûåë_¾1‹ÈñÉ­K[óE Vbv¬%™FB{æ…ê•pp¾LlÅÛ¥’3ÞýÔÙüTÙŽä®»ç0…à>uú±jÃÆÚT*-»úþþÜMôœ&°·³ÝxòýèEL¾\UÆÉ>Z0í–p)Ñ6ǪÕIK·7Ï Xµ£Cþµ— ƒ¼ » ýøÜ{k*Y¦!®ö¾¨M¢^Ó IG_Å|û©ÖätX/ì;¸‚KÁÆ¥`ØxÊç%Ž>?vJ6æ!YâÖ†ðÌ¡ ”S7ìÙüñ×c [#«fV‘ˆoÞÕ€©n!ʘÂÇ{»VH.bt½Ö¿y¤(„Š’¸&8GL£ÎƳúmM*Ö«›%b)T·’X»Z¦Y†1…©v9×î2M”l*•«8=| iER©çžBw užzC8ãå´§2D!K,`0(ÊÎþ-ˆ³MkßÜtÕbƒ{SÑæ(ïf·2!&­ø®,”e,*ŸÂdý,NÆ%)Ù.3a'V[ÎWÅ~~éçÈD¥e%éx¤˜y롺ˉVõ ‚B¿W¢]hô§iÜu6Iϲ…¥TД_¾ Ó†0ÝšÅ7 Rô§éL¢Ø›]wÒaÔ¬ÖØ,§êýé ‰»ñõgÂD¥‘RLž`K¹tQúéF±’$/™–À€`,óA”^ öòŒyàŠUL‚YÕ(8`Ï•uMKßELL[•5`e Í¢ÈËéC`¬Lw‰ÀÆÆ0‡ëu¤¦!y?´=!N nyG.“ÔSÀ[Îŧˆx›Ô‰V”x„ã/À#„-#z´®\ým m#T¹Õ?¬xŸ~”½3âºvÏbç C¸®º©öj äŽÁbbI=? Lml‘½g:¨—J;%HI ‚Ûã®;ò)/;¡,Má4R¤©ÀŒF. ¿T@èvgˆ-f&5/dJ²?ƒ4ßÑ÷Å‹G&M0;W]žÃS`S+áØW8Nû"„¢ý…«Z: â&pté9eÎLóˆÌ¥²óºZ6Yf`bKIxZ-@X6àP¤`~DsLv£]J¶ÅÀÜ;ÊGÊP—² é+ä—H’Ð%–H#yÇóãhM;­@·×ÂJÍÑ%˜'itÏõŒõÉÈA‹ÕU.éÕÀʓ&NrÌE–iµ0Y$@Õ&&«èm­UäÍä„Wv¢Ë^ã¡O¦Ù·#ÀŒ±-~Àí&pwqds0ÿÑ »î¨þƸávÑ`O™y3ÀÒ/è脹 >8 ¸;pêŸ,)$ù—”­3hšMÜfD”ph²õÝÅ£pwgjez±X¦ØÖ9xxèEú¬ŠpÂj]ç+Ò*_Á+²H.Ž¡bh— ãŒÄ´¦ˆu¼ô mÈ|¹›;îQK¾"3.‡ät{×íTw“ó ³²½“ÒÖ §¤1¬ùÜr^-?Öëe×)xTžOKçÆ°ùiþw‡þØzWÆý¾ý¹¼QßEªÇžn+ƒHåÍ­ÎùKýƒ,ÈZþèp8«  úϱµ±f1Ju[Zcµz¦B!Ûíd^¶ÜˆLÕ3©Ì­#¿¸›œºÓAåîGB˜"aÈÄœŽûU6dË8Dïíšz•ûÚb=‡!”µYBæ ×Úp‰’1¿%Œ@»©ã¸ÞO;‡‰ /Ê##ç³›W‡H ïã®%CÃk3 ±üTB…n©“7oKíOn†Öog3g°¨, Qèxéµ’ô¸˜‘? —ãk zO{Ë OðݶØò9ú4ŒÑ%„ä?šÀÃüƒ˜…›"¬OÄL-w.€ày/³’²msæÀä¢eÕØ0tÙiŽ0Û©'ûk0’:[¨ŸHë «Ô*+(Öã´ŠXnjúª³Ñ¾}làd#ÿóãÇÿ£rîZf(°Ûulw*Ç™ JÙcŽaa· RQ}ßëxICpµù[;pS&e(Ðä¾tny ³æÌ#x•*OÉ <‚:Vk ÂÕjí7Ý&•ê—5P$’¨\º·“´­È=k4³N…gûŽ&ņ\7qk KU‰Æ½M<ÂÛMø$a©»ÉÈ u”£Èíõ#V=¤+ô?›SxDRlí§þÄû÷Â`Íw”"TÍràõâØ$䟹\¦ä284mbes,FËŽŠŠÊäíù±Яu­¼óñô×Gê8'·h0x=ÙÈŒ© ògª³œ!Ï«ôôMhdz…„=“Ø„f{Ç à¡ýhQÉnç]7´“m—{¡î—¨ÃÔ”;È\yg0b¾›¢ãâ´¿‡b;UÑtju4Z wJõÙ›¤pÇ…„)WÎ&…äz 3µ"Oÿá·À§ÙL¼ª×I¨ñÂxt p¯*ð@1îöá{ „Ø)Š—ß`æógÍ”ýè´¦÷õݵÂZ2B÷?ÐŒZ±uÌ\wé•+JÄÝpê¶^n<˜ì{-Ôêß°µŒ—iÇÓxEgèx¯Áqzt¸Ë邨éxhFNýÃ~ïßlú\@ÔwD*·÷Ç“s Uß/ÙxÈò{·ènåéöÒ{“§Ñ‚pÔ<‡Ú52$ð²HañgËO—ó™ãýÇ£ùJÁ2¸6³ŽìI‘Ö]ÏàNòµÜöÄCݾÞ˜þ“Qoƒ5•Yȱ¹É´Ó8 µÇ ãÑÓþ‘¨L]St1©d¯¯ä"øÔíúwô–Ú#¨ïcö³…S‡ú¾a*ã°e>a'+¹–ï „ ù¥=7‡¬·ç ñ’Šøü$ï+¼‹€ÎÝŽåˆí$We‘)<õú%JÆÉDK0Øiñ’—,p{roµÇŽcÏwÞás.K=•€äƒ@¬œª%ÔŒw®ZÒ¡Aý~†V[x):8Øe˜èà¾õXˆ÷$‘e²³>?çÂ@ 9+º›þÆc¼Ëmðxb¹!ö‘Y„öOðÏ $îg ?ñðÏ~c˜Øx'N*¦S»ç‡O7zÿ§÷ÿ—À’˜endstream endobj 13 0 obj 6483 endobj 11 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /A 15 0 R /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 12 0 R >> endobj 26 0 obj <> stream xœµ\[“ãÆuN­^’±+G‰ÄI˜‹•j†‹0z’”Uy+NÊ’7VöCbf% .@îhü7’œ¾œÓçkt“;G¥RÉÆ€îÓç|ç;—îdžÎýýwµ½xwñn–šgüŸÕvöÅë‹ßÔ³å|Yfe1{}saßOgU6«Ò|žª‡ÛçwÍn½iÇO^ÿ×EžÍëj¹Tï¼^??ܵúYZÌ«?Wí®ºÞ¼¼˜—9ÿaÝŽ«¡»n×ú/E2/Ý0ÍuOÃöÁ{óÊ|™.—•}Ò^êGËy*Ԝ̯Jó=Ûlú`F÷fâj1‹E8q™_³¿ÒÏ®²4›gÕrv•fúñ~Ó­šC×ïÌ‚ªyž.kÄ~*™'n%ûv¸é‡­yU­FÆÖOÊy­Ð÷Ípèš~žUú4èÐ6vYK˜®ý¸šW¾8=ⵚš u¼Ñêyª~UÚG7í@“¦w.í¯ôfÐ+íd³prÝÆÌ5UXòÓë–f¶Y ,×µþî?tÏìFçù¼rÃôfBê5:/M)œÝi;¡T–цâÂ[º˜/Üw­ÕýIé»­_”æÐmÍ{Yjÿw°b«E&+¥X£}†—ON>ÿÕ«9½X»E¾¾ëF+ª¤šg¹Ó­U? í¸ïwkóçE‚¶ÅÚ%Z$Z+¯‘¸rÎwÚ­Ý­™Ä ³Ý6»C·2Ÿ*HÚû¡'ãÈ<Þwk»wY‹½~àr/îûÍæÍó7ŸÐÒ2-S^ZÌ:ÇvÓ®úFDWêoF3Eq>·û¥?±°¿y¶##IŒ;(énâd5˜‰‚—C;tÆ4òDBt¯ŸTÆNiC›Ñ7ƒÃ]sô;†jͳD3ÞõÇÍšžä¾y¼øféák ›§öŸ¤ŸÐ<‚²”,ÊÈÒ¼”%£×”íØõdl™è¥ˆBÖÜíÈúĮݲq9§-Mtw £ÄEJô9@׿ìAZÉh÷â>:´û~8f”ˆÝÕ mM•¿/™q„ZÖfKÁÃf`x2.Ë)æÞ úõ¦5JXT°_ö»Q)¿•·€Úÿ⾸jÆ–4Söüþ®µŸÍŒ¡#XWÆ’ü¯Þ’à&,ð.U¸E ÉJDÄ‘¶Vfb}†çÄgšŒödccï&“Ád.dp$¦x8»À_ݶ»vh… ÄG‡÷°l»œ,X\íÔmN‚Ûü…Å-úÑ}K“`Z¦ÝW» ÉÂh@Ϊ©¸UõK½&Vœ˜ƒ!ebz ”Iá-+.CŒ(¹ï—b$ì«’õ~Ú¹owk©"VbÄ`¯‹3ó’¶XÌÐ::¡.´3²øÊ¹ÙhdšeñºötÅljNÃ{×mB6B±Eî¢fˆm<7Á²ˆ ) sŒ‰ :.ÀŸ­´|¤yæ’* ªa$^ ø}×-kW.’Y­žD²™><ÌÕžuµó'_&(‰&šížë¾ì)¼I>CÇsGõR ù¿5ëa ¼#«ÄâX#ëàäøÐ¯TüuIX$áU *¼kÛ<y­`û#•ˆ»ÆÊ;“!#ÑÂ)dÌa"OdwëtNÆWóc¶£6ÕSš·Çm¡·Fëd°A¿ÚLs ¦`¬þ'a!‰‡§ûnÅ~87«d[#FŽŽÓd»Œ)ìfsZžìë^Ýw»5· ¹·›?@.™ 5r;ŒµÂ8ŒÛÅ&Ø‹z_?ñJìj“§»i{ë’e²•ñª†TÐè"„MßïçD±rpMm`ÀgÃþ<1¥âjþ”ø0¬€åaA×#zXÿX\ŸJŠsº„… ¥Uq¡fæHdí€AQ ­UyL0ᇑ!”(¨Dè+éAQéƒóú|¤Ã‚‹¡úã@R1Q«·"Àòzí#Ê_u×­îB„ Sß'Ë‚)ýÖ”Y.}SôÆ9í:Úa!2}‚å°k7—¤A@ÈŽ#áD²@¹Lƒþ†8®¢ ÀAû`¨N§×o5 â†íqsèöŽFç.÷´Dn†ðç%3=G«à\ cu±'ëc&[¹¾¹e‰å,ÕÀ¼È ¯œ]iì.õk™®vÿúîØí"2àãÄï&„óª¤îˆ+Å"s/º«ëÆôC8cí´eè×Ç¥–Œx˜(ÒˆöÖ +iJ:c ¸ÊßÙΪô ‘ ¨ÞñM˜*ÒJalKyHä÷Uw{´e:'{/¦è,ñlÃÐGh`–VCAíP¾·ª…mع$’)â°ÂdW …„k)Ø?¸·hbÞ×eâd$eî%ɼ”Üb)æ2R ñ»ð¼à*­)1ÔS.T# _åÓSÙäy`³¸(™J rå:%eÖ¨ö–z™Ë˵cùWý|ɼ+­^Ö–0(yr1ÓO5»m7j³le$ÇRóŽK“Âfwö6¤Uª˜Ù›ZZ$y°ˆòQ‹(~ÇEöAƒ(I·D›9·D2ÊrNçÁ2HJ›¥ünÛ«K½ã¾]™"'ÿTÿE$¤Öu©ÉP jýP Pöç4Ýj¬:‘+r]ÌJei©.8ü0fÑ\œÈ ƒü¥ŽœJýöQmÃÞŒýòõìë‹w6¥ FV5ËKê×sƒü«íů.^¼ú·Ùa8¶/¾¥/~¡þýâW_ÎÒ‹Wÿ2û½‹—¯.¾>yDéÄ”J ™2#V2n¦7]ŸØJ»Ã‹†CYãn¹ÐõÛZB±e+êÓ“èÛexÆÛ{˜2Þ¹ú4äÏõ~¶!oë%>cyreò;l™=¿ªòÉÊ¥XAnó¡˜S£Â§T£(­j$:Sñ}©F¡ »e¢Ì%ÓªqFÌô£æv…Yžk³béµêŸÍ³}” ½}g嘧6!°ô!9.–E­Uþ{”£úÕ$Ùeb¯ó͇¥¶8§êç´5ÂÅBUsá›Öµ²`#ÊÉ!ì-ˆÆä¤&`äT/òïŠ2…¤‹Z} H:yuWÉc+.!דRM»—O‰¨ Ô(|]Q-ó »>=~x¦´™ý¾ywäYÇÒ¸×Ñ4³E¥lB‹êÍpÖíäùl¡ïŒú½üÍIc( .Šªø¡Ôµc™Î¯_¾~óüçÆ/{‡!&á2ˆŒ8Õ/Á½ÙýK¡tھºÀ<ÎÁcéýã!$Ø4ÛjÈ4œ&KK…”"—äå>Å¥ÿL¤‹f%ç<÷@ZT¢´ßÐøÌz„si)/Qcá(½W^DŽyÐOwÇíu¤|êÎ×È!ŒÇÕ Ì¼²]»ôŽ€zä0‰\œÎ‹¯¹ä)¾çKËÈÍóL:9äkÞÙ:8’éº28¡sî|ä ¸÷ ̽–°’ï¿J—%Vñ¥P–»HÑÒ÷…†“)š s¨®y3ó&2=&‰¥Ô•"-<â>’Y4QDÃÓÒu|„}ý±2(Fó|(Úš0tL2Aá7’»Ê’$sO ½I*צʦ‹ÏNœ/eõcléxäbn—š”~dY?ò`!K}Ná ™%X¾â”G×u¤·û‘ªUÐV5À‰3+Såàü§E½ˆ W„å¤Ï [î pìgCÎåÀ\oB*h=|Ýë¸ `5‹BòØz1úÛ7Óƒl®ÕlÛƒ‹!YÛÞ…Ýð¶.$,ÊÉf&Ýx]×|€)ÃâšEâºdï¡øžiälýg‘Ò`MO~Ûý•Y‡WÕ׌“-1Çzã¨h—Òg‹h¶;Éõñ¹›$°ºØÚvr5º0iÊტ~Ó¶0¤ÇäOi[d•ØQˆ·šŒ›¶Ý«¯P¦¨6h{ïº Ëi·×]òïÿñË_>èN-2$_VÂÜ6ÜŸ,ò¸ÞPÓ½÷ᣢÛR 1UP îŽzk¡o !>wDÊŽ¢x’œK2¬b[~:¯4=±Ò³Nb¢Ys—^壯×cý9×· z¦y4Ö¸ºë{Û¤¦02åÌ•¸A¦Ý7õÈ(Z”‘2LNää® kå~:§Ž‡€|«8SƒpCrûPƒ!±dÄt)í'÷Î"Ñ¢büî|I•Ä9>Y­ÆÒi¨÷C ¬Qÿv+ñÙÅ9þó➌ËE4O$°"m"X«”sè÷C§‰Ž"æ ·AQ¡äå{ÞÎî I¯ L\€ìäQ rÎ`œ”Uý!ï’Ä-¢R‘_;€°¦xål{ȼΊ÷öÒ–ðÃd¯¼Ë¬!¯!¢~Л8á‰)K9/ñ^$Ëj\u˜Õ…\|„²«ôØô<î:ƒm¸#`šUaÖ$P66:çí1÷Òm]GY§¦NDÂŽÅ•¸±|N¡ÜQ1ËÕ²^oŸ?û£?ü“?þÓýÙÇ?þó¿øÉ_ýå_ÿôo~ôƒ¿}üã¿ûûŸþÃO~öÿôó˜Øg6¿²H Þ¨¡”Øj“F;îéžs¿Ôî_…ˆµtZveÑÑëWW¦°¥TIå]ÇànÙwºm›waýÔéñbè!<‹Á½%zî°š•¡úi!(ž6Ó¹ëY¶4¥šgožüæ%⟠"þýs"Η©ÎdË„$¼o:ëÞë)ç:×ý”è³ØI¡«!t7ÀÜk¦˜â3žiqùÑ)#÷ÑÛÑóW°É-%ó.¥¾±q9h‰97®"x>n©Õ#+ ³ïÚ;ÊÇ]bêl`¤Û¡Ì˜9l œŠôÿ)=_«>i!¿»:Rg“׆u×òÑ*ï 2…\îðϺl‹2PV/Öº_ÀôÔOŽ‹qg–w,Ëj ûIÎ]CŸÅ¬údú%@ð°Ò—ù I¨íµmîD^qÆzÖZ·®-ã*­JŒÆ•8]Ó¶„H·ØS@/;æ z\ÞP}².rJ0ÖKrºm3âåAÁ®§[È%Ÿ"£šVʽš›xQéux·PÄÒšaþ 2ë¸ó÷¸äed”À|:Ãvƒš[ncÔÑÖù}šOHýÂì…,’ËAÓL¾Ü Ä`c¯ôÎA®Û=Ý9áE>}˜­ÅZù¾a¯i1¾nù. ¼I0Ú忇þ5¹äL¿SŠ0òÅSAØã/f{áÔÞœÀJ(2X‡4Æ'Ì<ÒêTRÌÅHk hj@'ÕB:–*°àA| w;·ä%Àñ ï´ØÔû‘{WyÔŽ`20®c»’ãÝibn_x’‡G3â:Ø!™ÒzˆÎ¥8úuŠç]â^“qŽ¢{~[»e…ÿƒÞ€ –~贷׆ݼ £Ð)ní5º}¡7»æ[Eð0¨ËNð>Û­„ X Ô¿ãc…EÈu錰†N^=lÄ^Ž'\©‡Ô\0AËJ´€bk“_âõba‘N” †ô-âLÎÁ‘ºˆëG>¼êŸL´½¡tŽø”sÂ\…ão ‹^0­øž*¯VX! ó× Õ`+ÍíK¹ü,v\ö86·-ù_‘†®s¿Á‘þlâZ¦\Gç%W× -ïxÖ“¬´;áA@`³‡•wk ·ªG\¹rs3|o³‰2'—¾òýSÐÃfÅYIVõd!)Ž}øžéFÌœmfÕéšâ‘ ôúF¾ØÆ»P æÃ—•w+¸y]ÀrÝûæ@'ÝÒ¥w=é ,©Ûµ|̬‰cñ,æ¸Ç}g«EŠ·ì¬C ò}aÅAîƒósuìbï Dnúš¾¾ø_àD·Uendstream endobj 27 0 obj 6223 endobj 25 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R31 31 0 R /A 15 0 R /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 26 0 R >> endobj 66 0 obj <> stream xœµ\KsÜÆN)7ý •*oßh‹JXQD‡fÅ]@,ÈE„ÖV ÿ}æÑ=ÝÌR¤%V*¥2ˆfúùõ×=ƒè$TÿƒëÝë?äÿ„H3ýßgIPœäQò‹ ,Ô=?_¾~{ù¯“y<4o?‰ÞþCþÿç_9‰^_¾;ùËë‹Ë×ÿ–OQ¿Õ‰“2ãÏy žþœ“H¯ÿ©w'?ß¼~{]œ”A™‰,9¹¹{mVä¹~C$‚òäf÷æ²ßæ¿Ýü÷µˆƒ4*KyÏÍæÍ_«ûé'uõíuÆr&o*åMgr¥#uïÅÔ7'jCqHB‰"©weeœÉwï&óí&ŠÒ@È7ÈÝj;çïÞ™e;{YŸ·óÍfZmxÞ6êZ”°õ•$÷”…¹òÅ\)å¥îégø{Ö /…ú—ËÇç)^ûüÇ¡94>!GqäZÊ©¼ú4çy¢þ}AKà …’ñÅG½)>»—óŸ?\xä.½!²÷üÚŒ»VK++2bXËôirÿôæÓß´S¶=WãØÌ‡±×ï•b]j\ʸTW¥°µ ¿CïíºTè+.ÍæRÛ_¶ÚøÊ ¢»æ±½¿oÆfø¬!‘Ë_?ÇT4yYKä´Ç½»üíˆö“B Dø®ªÛN‹6Î\§È™S—½pŸ3 OÃJßmURÚq beBýékÛuú-ðã~0ëHÕM+K“GäÖ.f­Wi_ä 7°wxÚ«nnFýÃ(ˆSúáÔtÚ¨„û´ õbq|5›v:ƒdA™r Wšð›|¿´±ç˜ZXÏIyÂÔÂ2ˆmj.n¼––IŽj½nvÄw²+®'É ²dåÉ)i­ïnv`ùòé,àJZqΞqñŒ&Lì‹&xÙÂ×m[oõ«rÛª±Í±kó\ÕÛfc<Âae¯:ZjDKÝ4S=¶ûyõB’LÉ©X¼þó0»jnëªëÔÒ\= nÞ4]3ÃR"&ì¯Û¦‡‡à|ÒÈ”#IÑ´"½ Á‡ÅYXTÝ Ó»/¤+ z†å§i^ÙKZ¾|C*ÃÿåÃÅùµ/¿J´£PÏï0†$zÓ®MóÈ𜗲g¡Hs© yc«Ÿ'B£©åóàF£m‰:i·€#Âü>r˜Ì΢,ˆÁíNµQi›¥h ¯ Os5›ÕEî”d¸…©1Z" ZÊÍÖÜ+÷—K ¨Ÿƒ[ʵÝ:pAzó1Ii­”^u3 Y@–š›`ƒÜз3–ÙÆf?Œ3,·ôì4äPt¬ú©Û¡7™“m?ÍMµ`DŽ3ÜÁ"5ÀC÷ ø}}G°’8djÕ+ Vøã€šP±Úê»Ât²t¾kàç<$¡Œ¤ySðÚU`0‰ÚÏq«¤‹X¼Tù ÄΠq©’«¬„`Ì z¹–Ÿ+£vnÿä .Q¦­‚Ë«¦¤”áìE1\RJýh wõñâ·\­Bè'äHù’yýRIëŸÀROµC·>9&Lж’"·t0™ÇÐwdÆ·³åáïÕ8™WÌ†ÚØsÝ@j#‡W;”è![#¸Ò.ë¶iû{ðUV J¼¶0[½Z*d‰D±„"{ÊR?®!c×0Pb¸{zxÆØ¹FC™ ¥ßÈ:†1wI ‹"õ‡G*oƒ‰NA¤¤o”S©ƒ.³ !r„…úëÕaÆCÚlÄýÜÏx`OOø¶ëFEúè›Ç\WÚn˜ébüê½ÇKCyßÚØéjˆŽÁb\L µ<Ɇ1.–Ê~]w,À·ýFFà²zBŽËÈÍ€Mû¦n_ÕRi­°M¹VºVòá:·y£n©íäY..Ãôe!|Ci½]_ƒ[RмòaA&œ”²…5ùµ¬.x&å—ýâ™P¿aH‹–šdiðB»á…h¼X&¶·YÈ<áÐÎD §å7¬v6BóÝaz>½±m+='xžhIšÄq~ô¾tÉŒ4½ÉN#”x'_Á“s¶|íHþÛÕ‘À—2Ú%ÅIR¤ñKÑ]ÒMU½ž‰9£õá ƒ»þ}%‡su%ã‡JnÈ— ̶ãgS ð±tbÖ#!‰·aœ?E‡º˜/#Fm ¡Ò`ÚæÍ+¢’ék/*rPÖ)=zÛ ×@)b}çºi!xQ4ñ @#c–“my†Uwƒõ™Ä‰—dƤ‚NKµ5›PdlêI™ÇΜøÎ³‘O26Œ÷¸<ØäöanðŒ™…*Ÿ@9Ì&æÔ­q—E×#Öb(/|»ÑEN˜;>W 'i!”ëœ/R2;1Ñ€¹ú=÷a·­g­p0?Â;ì1ÄžR%yªO,Ë‘ºÂ꘳M74¯—ëïÇÞNøK»±³AŒÄ,0ˆä_ „9A‰ Ó~±ÜV„ì¦v ¿ÃÅ™;$®x†Àš†§ß¯Û¶Cÿ"cpÇÕ’”5ñ¼|âHJdGG'D¥™«ýÈsð„‘¸ˆ7èñÿSigZ.ÂË)­Sn4«Wx¬(õf\üw°RŸá 4»SP Ãbž÷­±™cä.Ž" Î0—3÷M³Y7š=£à»êóÊÍ“c}–ñ)37jþ‰`\›:-;^͵.ѧ%Ü3Îë.ªYj DYПë‡nÚ©6ˆ\³\VÐa±ØY³HªIÍv-kàhùŒšb_o« Ôf4íLC=çü+¯gÁŠã¤G²¾Ã=×±ƒÙ&FJ¤ÕôЙ³H¸‡~8˜’¨`–yùö ‚måÓ›ª@wœVÔYƤFÌuÛµóà 4Ms„€tE*«¬>Dô½]Q¯Í„èŒc3Ç¡ðäð4,[Qf ¶B®ÿ>y¡^§R˜/…OÕº…zE+|ºpÆý¢Á’/=²òBb£ð…W^H3§…ëÖˆ]yÉ"=¢2^õrÅEn ‰Šz:%A sz]m¿Áæ_˜ð'¯dÖ1)!ÁðcjKrËá‡É·N!?s^ÒÎ|AØ<À8`±ÔÆ=~븮ÒH)Uê* _LW©êt§¥z‡Ñ–k§óÙe±8Õ<6{Uõm@‚ ;îEd9ˆ±Þ60¦˜9cXœ.ñêRšw­:š¥xZŠ`@zGÃgD§@üŽ…*-ÃOZÓù©ƒ=’Æû[Lµ,aUm"râ(+^j(QçPÔ‚D˜¢ªÖhó´9ê¡S=lòÙU£ŸÙùMªƒÈmÊÿêðâÑAQÏ´¨ÏÁ&¥æÜfDPL@9Í^ö%,ì컪níã½-Ë…fîŽ:ÄRDXª…lº}7l<«JoÀÌ£x c]7¬6÷“´ðBã7ÚQÞÒ‡ßtã¼&¼ö¾è³ï8! yíz£ªˆ¹{Y7ušvv”ïe„>Úƒ>P¼`^åLê3®P¶ù#¥øÜîÖRß €ÐЀL`íQÒ4ƒÈ+‡œí³Q‡I[á$üçMººüŒ³ŽE¿¬O·fV8Úr§Ê ¦5Kx‹ž/)mèä³ ^~+^Z”˜(ž»8i½Ôdj\È7%ò²îÈVÉÎipzÚÛe3ã‡ZÕÄw<þ9ÉK)#É¹ÝØb…)ެ†WЄyÔª0d±cð+™åù¶ì ¸$ÉB}@]$é‹©*É4‹"ß‘€®Žƒ*õž®B!@Cð_½@£ÅÀämû%ÎAè~ó©ýç0À2¥†=N-%«‘¹”b] zÞ¢ù%Êž¥’âøGœp÷+©ÐœõA6sÆ}1ö³#‹¢Ò{Bk-ÎÇDž.EþȨS.R¥g¨ÁÍ~i@å¢ñ¥´t]ÜÆ[öe <…;#”í±ƒØœ²`±¿ <à)mjÖ÷­'/œþ°sä-rL ?ËA‘èxkØH†΄§LÔ!BøŒøØ”1cþ“ÿ|2ª»ãË@è`Ü£CÁΫ+E¡MíÆ˜ çžqÙ^1¹Ýø¶÷À‡9ó£[,’Ì|ØUõ¶í›ñ^MÊ8%H­ð!S øûÒnˆò,â@¬OpúCx|Üží‚1µy;lÀ™É­<ÃjloœÄ‘P¸]%5Ÿy"v¸›-A¿îÅñºÔ‘|áÃvnç>r‚ªý䘧¾z¤0†CŸÎ9YK;ãøì#HþñZÒ³—·³ž¦‘§ÿè«„ŽŽ:%™Ôœ:¢gÉK:%™žc—ïëNœOr98Ôù#+Ö¾ Ù…MÜå‰e?+äÌÖÀ™ Þ$Åg1–©kªcuÊŽGÓ—±Ø‡>èËX,ªbîPòiÔû Yp¾üÊ„þŽòtyî]ö[6¡gÒæ©ó@¼‰|†s=$ðϼ~ó88ÿ:-ÿʬ`E¢)#¬à5¶=?æoá>µœµqó8 ‚#RƒìQÇÄ06ÂàkçY§àýßǘL¼Ê¦Ü#“->þc¹§tðtÔµ¿³JLvÛS¶4®ú䈡Ƈ’·ß£h´•Õ åÇZÊ4Gœ{Y'~ü«‘¸ã§Ã¼H°jÂ'Ÿ•:¯Õ}é’@Ió¸tDFvtNºÀéÁã"_'Dœ§-r΀ Sêë8äb,.bÇa ­¦#ï¿_ÿä> >> /Contents 66 0 R >> endobj 69 0 obj <> stream xœµ\KsÜÆN)7ý *oû$Kt™eYRXLœƒ/à.–‹hXXÉ̯Ï<º§{0³”(‹åJ©ˆ`™~|ýõcGÉY¬þƒWûçÈÿ„È ý÷EUgeRG•üG¤"Š+uÏWÏ_\ýz6ÇöÅogÉ‹Ÿåÿ~|ÿê,y~õúìoÏ/¯žÿS>EýV?D>-æÏe$¾ü9g‰^!þ³ÚŸýxóüÅuuVGu!Šììfóܬ>9+Ký†DDõÙÍþ»«þpœ_¼;ÎòŸÜü÷yžÉ»nÖßý´kî¦ïÕ•×Î…¼ãB$Q•©ÛÞ¾S·$YT¤um~ys©.]Þœ© ¦1 )‰‹TI§¨“üì®í.‰ë(êQ\¨ý]þçêÆì‘†¨#QâŠßê&u[ššK­ú#‹Ò¤®KsåÏnn×QH uÅi]H©ÄQüõR)åýÕÓJ¥”RJ*?½»þ% •4¤ž–ʪÙíÚµº" &¿Í0~øý»ßÿN"²(“# »_-›\ZLñ´²ÉéàÚ`._…D#Ø‚¹häF¬ Ðj˜Œù³&#jWÇÙ0•xbEݨ+UTæ5^éõ£¤‘¦ö·Ÿ´Ü&÷ƒ³ŒÄþøc§x‘T‰Zœ~¢,+lÖ5~ÔoJ56© ¿'Œr“J/È\F})ŽJûâ©Ûw»f„ýÑ­Ên‚®“Ä"Ù:’º|jë L•uÜ\¿ÔæG©Ýõ«+),°[øI#«þ ÈtØí½Sy5ÇÛ>ÁÞ«Ò1 Õiák¼d‚oVã—û9wÌ‹¤Œ•T¥°êdÒJIó(±k¸ÙjQo—hƶ×Ñ!•4¿‚iûS·ÛÁ3MÆv>޽gùŸºy «+Ô*puÖâìsV ²Bi?¯óoaÁ˜™Èp,ý|…cÒ(òܵ‚DÞh77µ3좴·u=¸1Ý6‘KS ‘ÿ]F_-Cå ûgín}®¯e\ØÛng4–k'v@€•¼OÎCªli¨JéøI@•2  *ßør=Š ôX¥Õ7ˆƒA=ЬŒ2éòQ¦᫟¯Þ¼6!Ží'Å'”êÐ(Aw1ƒÚþm”ˆ…Úë²M¯ÅÌ]QãæÈɤ±eÄM®^ƒèZ`ëfn"³æL¨ëŒ£¹s\O3<°'º:ô»û´OY]¥Ÿ+ÉÎWbu.ªø‰YN.åW¸Ê]Æò’™þͶ›ÀR+sInMT®Æj¦Er%®0¢é=ÃÆsrŒÒµŠhd`Kj±ÀpåÔÁ÷ î–Ûnµg¥»?ÃäÂÁ:Õ>ÿb´Îê"yâhIœ.OGëËëë€Qd‰ ØÕÂ(x—Î Vanr`Ø]®@ïÚz—üŸ‹ûé"a©â&Ãì¹Ýíh®PêhŒ@3Y àpì›[¤çϘ°R[3Ï ˜IÉ¢vÓƒá‘k…LˆË|ô)¤2ÇØ/E1ŒTX§’rH•Á¢_€}ŸÛ¥XwœŽÒ˜ïž0H<âÓY\<äÛi8ޫ֣1;Ifçfî†ÞÐ$°éeÊ.j•U=>gW²¢x:#í-“¯ÈªHæð* ì!•Š‹°ñô{؇cÅR€ŒñÇËÿtõìˆ,îæTÐR†…ZS?¦pò¨ /7'}„¶ýþ,±Ïy÷Ê\!S0qv!­H»Ý´»Ãlö(é%g«a”Vsúu×kH(â…yÕK,B ‡¶÷س%À”@¸I¯»±]ÉåÜÀE‹q];.$>æ´hh2©­E9“yÖriðf£ú¥xù–7EÞCTwi¤ÌW[¼¯öáˆá¡]u›{â£Õ2˜U 3š•öR@€šq@/ÊÐC\{Ýà ; põgl!Øj‡]·Òh¡Ñ°2dÉ•¹X×Ï­4!¨HÆVR´Àôk÷Q!d…«mAeæ®›æQ¿þH…†HÜ ’Ð2JÀBއ¤cB˜I2jæÔ$ö’W-˜Øc¦Ï-X £ „Ïf‡Õê(ýÊÔØ2]ç¨kvª‡Â¥ˆK¥KfÓSAϱFwµ$oI»5Þ]HC“;6×å·ŸÏA“JWUÓ¬¬Ä>Mªi“|E”V Æ^_¾¹4+ºÊÒæ…¯[{*ž ‚qíÒ; ÿv}õ-—ž|JùàÒ ”—ÿ¹¹|ûÚ[z]ÄÉW.>ŽÕ3øŠÅ‹Z®TË]ÓáBûp뻹¾úÑØnòƒ¿¢ QeékBÈÜ]w.Þ\½ý%`B_«QTߢ+ñàÒ%Á0]‰ëË·^’÷òWãym Ïâ„¿ K<„ÕDB4^ŒÒš€ZkŸ»1‹S5ßšÙgÎö”IÎ"³ÈNdKsÕC;í!Öp­ª‚¹CA†q?Ap~Ì«Å2êÒF‘41ÎU<ÛÙ1—+¼¥ÔEå~˜ÝÇݶ§Ò^A?ìšó¾Š%Ó[â#ˆQº¸`Sü BåÛ[¬ù‘Ü÷Í=¬“È,+±yè»ë‹µÇÎçᮕBCþ’ç.èµjÒÓæ±Èg)9þæŸÏ ÏfaAðì8¦NÇþ¸›»$®¹‰Ê‹%cáŒ#{qü—qpžzÝEß… nÌ2â,h€¼Â2snãáOc7Ï-’µ:âi3þ­I¤Ú9;ë›=¬ö½›L lœ¹gßìÀžèÅwmߎæºÌiˆ´Ž£¤T˜“=ÛÍÆ„DÊ @i¯>Æ«œ'ò,§”iï\C>˜X8¥»×í,#´ Ö¶à L¶¥Õ¶éïZè}ÔL¿Ð¦Yæ_¼RçUþ¶ëÀT8"ZädN¨[CådG4€Ää36ÝŠÙ: ¥×¢§Ë¥¤#Cž‘3…7¨BŒå*7Î}ò¨^h÷ À%g«ˆ<aµ´€Ðœ&ÑÊdË"SÃ6Ûe…@0;p`‘Ê䯢 ºøÈ»D‡P•=ÊïÇ)[7Tqc¿é?a‘Ï”­¤XkÏö¼ »4Ž.,a‡ý|qÂÎUmc<ßòjè)§NN5Ö›úîÀ÷À5KÝŒ>PÇy×Q€¡ÅÙþ½êT©ÊªUQÈ…)TK«åKfXh&›Ã^Iüÿšš» !|½1"—6{ŒB"½(um¡”o‘6Õ_þ8¶¦îètäÑ7™ŽÖíÔÝm(üB)Y±ŒQGS¡”;ªÅ ‚Á‹{H8nÛf cNÝ›p1€°ìö¼27b…‚ì°ÛasYF, Ë*ôÂDhuó9<œ,Ã<ÜqÚvc>'ð|¶ê¤A•·`!YS·¹‡:œŒáy¾”NáÖŸF ŸV}JMHe˜)žÆ/1_Ï :OwãxÕ·íˆ/¢Ú¬`j8Àuxy·µÙMìÄA ?þØaRÔ Æ øKÐj†wAtÍxLêA§ÊV­v0|;˜Æ¢ó †-d 9ñ9©9 ò&Ö¨&]Þ‰ÀmèæŸÍÖQhH‘OÓÁÑYF2%Úá”>S SÌ© k û˜ÑÇÑiÀ::=˜½Puh’K2¸õ8¯è=ìƵ%…,btc‚lµj~a´ÛF¦>35›~õí³ AÆföžè±ô*“Éfí¤Ð€ß4“¯0O£Ò®^Jó‚4æ‘<¶(Çö" í ò¸’i  ¯ˆc!¤ @o3[µÄ¹ùƃNK4§õ²Ô6R‹iúB¤r-zxœáØw1dñ#ÙÁÆS^³ÂHfɲÅÒóÄ+C={Mâ0|ÝO5i„0õ”—ž"æ±YwJÁ†JúÀ¨æ¢²_²|ôÖ þ:v;ô~ŠüÍ8û5)Ï☬aó_]àâcªé‰›ªJ(ÓØi;Ò¼!”ð:þ4ÇÕ ôÊT0*Kç¦ë1ºržÑÀR¹ÐUzîøÉŲî…Ô4[ QÎj¦›ôIZŠlhµÁ*é?46™êØbA0Dž•±ow€tØ:ù"`Útn¼Ûc’âpdâ$¬´v7´¼ã‚'Í[iiw[ËÌ{AfÓð• ñ8¾єĠƒwÃpˆ@4d›/BJ…ñÚ‹Æ£n´nô1„³µPáÅîœÒɼƒ^Ä ™`Ô)ÂAßQÜzPKŸ·Á€°’Ê$ ÄÒ”(Ã=ExP)OhqåŒV‹c~ Î+ÎwÝ4óÛ•¸åß/÷Ö¤~È[Y¦ÓËk’ gX4°å©-©ðG$ÅÝ(–<ÂÖcÁK«jß‘§}V‚`Ù.ðû‚ÑKË¿×CÍD*ž´VVEÎb#ä2£˜š²"JQ>„3'1E‚¹ Í·xg“ê´¹¶±˜Y,êíÇÃZÒ-‘Ø(°Äƒôc †SÖ–÷Ò32”¾Î=}ÙÒB6å²&Â’Ûø‹ÉNš0ã9¼ãõ™ÌKº³Ø¸â¾›1»bÅjg`…7?áœ2ÛB -#¸\ ©Û6a!>éü-Çðl±?gPW uI ©né#"i`–L…Ï hÌ"#ïƒr¹SÿÅq‚CÝ®™‘³qiµnµj(î:Dç 9'xXZóÑhf$$šM;b͘ÄzÛγŦÉU²Ý¯ZS?ÎùŒ¿_°ífóº”hJE˜3¥Ë¶´ƒþ¸¿…*.?8°zêôyG;[ àöÍZ_U¦nAìû·`9#Ô€:ŒˆÆ„A»áˆUYF:Þ_†ðîÒgÁˆ–¼¬Ã‹˜ñê½ «÷Öo‘L¤‚.–-‘BúK‹µùä°‡®–“`ê#8…þ(Ñä>5ïÓ?(UlzK’¥ŸZH7߃©R$PZÇÑ?Ú¦àº3Q”Œ…¢3‰kÁò£L2’ÀšÊe¾Gý$(÷¦lÝÈùÓfaÁ,þqà Gƒ:çÞh|ŠÊ"ú½X.wNÖlSJWÅMBªÆíS€.³’‰D–»±ÙÃv­¥Þèº7åPİêeB/gÔ½©ŸH× »¾¾|ùúà$E apqLìá–<–K˜ÈjIÑ4œ›©…U'm¢í~H¦NYjÅÆLYQ>ã”ÁŸ·ô3 =~ÅÅò ƒ@Æ-â±SÎ=4ä “yÇQOáa"Zñmªåq>ްp µª¸-”êýXi@– £yºSç‘™.7ß[ 97ݶ™:Ðò ¤šÙ8”ñÎ6ÅbU9å8ùNloöêQ'Fß øh!#Å„&éð.Þþ[VÆ«!Ö“ Iâ†ÿÕ°ög 7cs·Ç:ÐÚÕLhÔ0X'³ÒÀ’ïƒ6fè6Ëî›-|UØEµƒDϸjaÂO…ÀEbc 8^3ÈŒ| ²kò”+YÞd*¿óí›qŽ? &²ŽÑóƒqÄWœž™â+ç …˜ƒ+ÄÃÌçLNbEöÀEå ¸Ó«¤wäœY<2€Yñ!†¸pÓ®o/nï±ü¡“'Ïïq6– 8»>”§ð#wð õ|1x•Û#mS³ÇØ\¤¾`üÑîØ4wîÍÆù#/€[¦¸ °Íz¯JâkƒTYÎf­Ù„Ñ L)u0Š”¸)iºgv"›âG U ƒE¡ø‘f/)#ft¥ShΜϟÚUÂòüáü‰" ’?cM‚öDä< ÄiÖÐy(s8WŽÝÝmgI¢õû3}$ÉVïL²%8]ÚÁmÊÝöM×[æ; zše6‚S„Gqí†Õˆdi]Î9&#–}Yì²®À=ø¿”@²ˆ›N6šLÜ5°ÞØáñ"XˆØ{Ê]8´;Ϻ›ž$å h7R)(0›¥Rj@YÔÉS Æf©>‘J±z0vcŽ8Ss|‘ öÐër:Ì“g ¬ÃJÇnÂv"Y¢@ÛŒ#Ø´‹ÖHÆz‚,Ì#“Aj㟶 |Aá1•ÁPóœ¡Á®²B`¯ß°« !ˆ õ¨ÕIcs“û½N8Ù|ZåÁC,)¿¹kvÝÿuS–!x¡#”Zê!èžm&(â$5ŸðFÉaìPZž‰tVÇÙt¨c6툀^ðc$&p³á»SŸðpÒú¯Ö÷9@1L1È™„‹¼âÓb8@±‹•ØS/yH4í¥C\vÝMœÌKsæB¶^û!á¡a›g‚OÛx:Œ&î:uw;(ÂÊU[l ØGì½Ò;äâuNTi ÕN”U/¦—$xá~.ƒ bš[Ùü3¦úÖšñd—3êΧž°ÂÇC\N.·QÀ|ŸÖåÖ·}ÌNmyé·¥s¦bgŽ«!—JÑÈîpNù+HéºÝaw¢|õ¶M"{TÎéyS_Ö˰Ôÿÿv˜ý\*[I©òâ{:J66-NÃå ^óšæF2¨i ÉWÁ‡µ@4Yòö_oހ岸ÑÝaUKÂ{Í&è‰ ]Æ×õÒÐz9lWÞ[ÔMýž&ïÊ&¬mÎæôïBZ·H±Øöã=byväGR¥VZåóeÚÉ­–½+pÔ3tî½÷]Ì+KÔ¡ð?³ã«_ÝõƒE•3Üë?…,›pï×(!”$´oVÎô›ËaóN0zÇ@ɇv‚Ù‹Óòe={6vŠåÿÌÉÞíg+EÛ@7%Ðî *guÂc(¬€ùHTqºyÞ€Vè«/L¹Iœá7 Es‚ÁBåñ§–3PaY9•³Jd-›A’±ÁôªkòOx 즉!<ø+[žú†YñÓóYÅÆ[¬»,>‚Ui<q{ç¾lŽÇz8½K 9n°‹*ìsØ6ÎáræÜúP:ôW[ÜÃ)‹c%JŸ ärTI—!ßö.ÿ­þ¦kRñ’“(Å=]º¦‡%Ô;¢Lçk¯Þ\¾¼>÷l&”‘Îø±¯º¹„Ø/iB€¸ð³8œSí†'út3GiwÂÞvûÁƒdõY+˜rv>;b#Uå~¶ëü¤ªù IxÖÊã7K™øgˆãÎç3ˆœ¸·ƒ°ÅrYªÞS¹Û6øS¦‘^±[,฽kÃýœÞÉGˆîNµ•l(ßæ÷§‰ñ£JìH W`¥1"ÅUMåÕx¬ê‘J!Z”<’OÒÆNÿ¼vÛBK‰×f´¾œÖndVÎ T8ŽJ{J&œ-’Løé>¬XÅŽ¾0:£—èoýØsËf´¹’³ÚÓ3kyÎ;Uáƒb˜aP•ÏøÓ(Î'½ù\:©ñšÚŽ8:ÈM׋~>YNé(Ž÷s2 ?Ä¿éé2§óÕÍöØ$?°jûµêÓjÓÈùi3r¿l©uwÚÂ?#Ì>è”?œåK‡±ÅÕÌÇ©±Ã¨-6çÎÁ~ð¸õô飆4ñ “X™oWÇFÜSU{š“5ó2k6™¢€Ž”ÿ|þ| endstream endobj 70 0 obj 5728 endobj 68 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 69 0 R >> endobj 72 0 obj <> stream xœÕZëoÛF?ä¾ù¯Ð}IœF¢ù~8 ‰­ FÒ:QÔÇM­lÖ©”sE¯ÿûícfwÈ¥Úº×pH‹6+rw¿ùÍì ]Ç›¸âü·Ø}:ú4ñäþ§ØN^-NI8Iœ,q=o²\©¼IæN/uÜx²ÜßæÕjÃ>²{VuíÕñÕÓ§ËÜÉ,sÂÔuƒÉruü³Xò2ÇMår’er¹¬:øÁ˲Œï,Ö¦r)tü—*\ ôS]¹eõ^¾¤âÑT­¿+ê„X­,/¾ž_~»|.~˜e‘Y8™‰“Ê­Õó‘Þø…YÔÊ®Þl®Žwë•”"Lœ$ÂÓªõª•«~êÄZíêéàLn0.”Pqmés¥$é­}ù²„«Œ;ó'M²)U¹©»ZÊ"ƒXÓÔÍGþ¥D ÇÐ××ucùâ긴D° ä>·D¯Œà¡¼°Òï™ÍËgÏ”†~¨$pfžë:aõ ¥hÞ–^*F!QüÇN!^ŒˆöLb'Š„2'ˆ]׿uÕ•Õž)“Ùà­f³ç6Ò.Öc¹ì;^”E=—\¿»|ûv¾XHàIŸÄþm9A<ùÍw/ß^=•"_@2é wò…e  Â_œ¡’?CA%ld„½ømêkÞ°|•_sRY¯`sþ?OG<‘þ™"qš°eÒxøÜ”;$” ¡¾P¿là’ã8 Шø°¦ˆýn•wê$ Éžü@Fb#;#dš]Y+úœ¥±pfØ·ÜÈ;ÀãOµ^Èãd.ìs˜Õ”6MÛÔöòü|,ô•'úRsÛ¯WÜø2,|a–ô7­WÏ1¢h@˜j‚@ûp«ˆÚ8ÝUΰ|HS Û´L­IýA fž>2 0õy'‹t’qÌøq(òsIžÊn¯Ë›}à º9‚`á©´>×Áå˦¼)«|#=%VF³z†hWÔ+¦ÐÌm›ŠC]Ž ºÜÆ ‘É“Ë6G$D.AB‘«Ä—8®>óZnìùä´úºËËŠIƒñÜÍuÁd{ÛÔû›[¹?ÙµOH‡dË[µuê$ZõŽ5[•TÝX`G¡'›È œDúO>Ü2öà›ÈkÁžÞ´Ý±¢|T€X&HUŠæ˜ yÝ0öêùÜ6È„“q±"uÍ $~ÌKeÚ˜xS8B!üómYH“ø‰À–³ÞpRiƒ"Ç󄲾܄5ÂÀòŒ¸ÿ†[w×Âr¤Õ«× 1xa×Ôk[µwèÛ¬›ZZ×÷ˆf,ñ\apܳ»e ¬,cÆ0.q ÷ä²ÁÙ¶-«›  ̾UÝqÿPLí°âpÈ,‘ƒþƒw"8¤ùyÐEÆÖkÄ`¯SØ›ùؼ@CË¿¾\¼±¤û{~QaSºeÂkHI-ë ÒÈɸÆÄœÌéÖ˜<&÷‰”6ò¿ïE Å‹µ‘Æ·ŽÁÚ{ñ‚ÅÈwölkämyr*Ë´ePaRÌs…§ðºÙ‘Ÿ[Y¸Aa¢Z‡&ôyvÚs¶ú ÂÔ„oÁ ˆNk¡¶¿é€_|@¾a"BÞ”-?¬påêÉÀX¬‚üà»~Œ$‚œ Þï8`!Ìiôa¤Ç¤êGË‘}5,Œ’ݾ©ÀÓDîãH¢È]˜«·v¸! ªRFTʤÙýø8„còH (å?ï%©IGâCSGñEÙÝÜ“‘ØCÄœ}uñöâÎh~ 8à¥h©eB€z/¢|d‰µÞW²Ë7e§ôDð¤ÄŒ‰ÌP nÞBö5™#_­"úd[Áƒáðfa‘СvM^ºòQTXÇ-I—'`4Âí&C˜ò•À(àä_j%3+¬Ø {8P•’L¬X.05$‚ÛçÛÝF…¥ l¿ÛÕªúë­w·yg ªâ1/ –¸xþÝë‹·òRªøû|9yô‰SÓDü™…< {nÌÓn:áÙ%•y±=zuqtrñõ¤köìäû‰wòÿ÷Õ»³‰wtq>ùÛÑüâèýÁ¶O¯ªÄ®çÊë‰H`^(:?ï˜nAºË3ÐÎxÚ0/-Ä0V|ŸGàû˜`‡ÇE oc<^Éœ©Œ ˜ yŒf›) ,¥(›ÿ0?›š„‡ç?\ÀPZ–VvF ƒ„¯9Ó îX;ë$À^N!Ç–š×Ó>áVšSC"L0®ó‰¼7Šøÿ45ÄX[ª·åfÕ0u… rc–|™¦²Wj_Ø÷1X1„¿Ÿ9€“²Þõ,cØëÌÚlÄ0Cb—y 5ÜØBvÈFm”õŒWº6UUBj{3$ÄxŽýJã êO“ ^|`¾Ã»‡ÿë4†TT६×Ð× êfo_°¨Ba"®ÐM“o§àÒ`¤ÌH{ë4]SÐ8¢MÙµx;3ì¨jJN„’”‡!$Çä»Ý¦4Õ{HËÁƒ͵S’K´)ðÞJËþ;;{¨ÏT÷×T¾0{X/˜J6@·P²ÛÕeÕá]Ž$ÚN©”˜®QVêýf5(0,AÕ ò†&k†}-ý߯bk= ˤ²ã—/Ùl8Y$ÌB_X#áR&‚Ø…–+¾í)”/“@pòZäƒS@ÉÜp‰šE²8&]’»¾ú=»êJ-uIêZå]~ 7t(SÏÌ‹uOýãϦVEÔ“ÁÔ…Rœ1˜€D…yí>X2 ïÃÂÿ·ÂéìêÇ„Evh›GÇE>Sé!3/–œ2íÓ˜–À…¦V4—A½[MLL¦ säæH“iÐJ°xÍÊL‚mX»««¶¼V-‡ÖyX „$í5l¦tíÏÙBG¡›LbÑ”Ññ×Ù ‘ˆ7ûÝé–—EÙ"ì͹DŸK**§‰ùáàpÎÌÆ¢ÓÞéòëËo—?ް{K~ú†ÿfýô LDm%oÏ¡Tn¸¿ÝÞ½“¦½:¾ûdYÜN-VwÉ Só¿Ñ!»·›ô•ràÔØ lóx05L=~F/ÿçsC°øÈܰ´òä—ÖkÕÈkzl(u“þLà˜‘ï贈ݗÎzw 5:CÇX"Í¿û8_,.8hK„§âžQþð M€Ô¼é°œ%^©øÑ¡Žº‘™Š¼@.?.æ/a¶2Ã)ÒÁa›8@æ…ŽÛþ`ß/.–s[²#7K´ÿÕÐ ôø+‡nÊtºq,~˜/¯Ž·=BR¨†‚œZŒŒB’ÁåüñÁµû?Ç‚œ©ès婿ü…óùÛùr>ÕQÖŸ·¹vÓK½ ?pW¢¨žŠ&{ölˆõƒ6^¯°™+H„6°ÚyÙ0•@}I8ûª‘¼­FzÝhZð/¦L›Uß_¼ßwU‰u¼ëÚ·^ŠËj’«'f^ò~ã%¼„/ŽÉ·ü†ë)ëšuŸ™ºžÉhgǬíò†©HP€´Øy „vñ5:À Ã,Ý X©ûåÉ"žx¢¿ùÂ{®t](k‚X“¤w!šy[.”¾XZ5(<>ó}qêÕ ¤Z®‹=¶L YãHÈ´Ù¬ï¡MÛÛR·ïã´oQpì›O{¶g®d„øSÛ19 \:»å:„‚›Ã¸¤ÆŽ®Ù!W!ë»ÞÄÀ¢p·ØPн1­¸\C 9&Ñ)·«¯¢™s¬d\Áü&À jT¾l$1·©ÀQ]/kn…-ú‡„yx:³o‘˜tžZˆ“ÞÅçZyÞÌ„‡é|H€‡´Ñ‚`LHHê ÑÌw}(‹•%J†Ú@ºa¤×e$ëÂh Р؆ ®P ƒìm]•]ÝÀ@%ŒHCלi£Ûɲeæõ0¹obt¬læV¿ÕÝÀ®ÅfŸÃ’^_í鿨¨c@2íí<6/»3ëõ:½0(Hœ¿¢ ©¼©â ‹Œ„ á)Øtæ{2þÑ÷›²‚f¨Ghcƒ„£Êû„£%¦`|ç‘k6Á߯¾¿ÅO |‚&sg¢‰T^0×KiOmtÂNLì –]Ë6ë©ÅÓ”j îZÖÜC´˜<ϰ±è÷èL€:cMöu=ÒKØí11:µSgmíÀSŸ<7И¾/Wã²AK‚¦Î¤Û§ÍÑãRñ¹#w „hQOˇ³ÑvvBB9o°sbˆ[\.dHøý"ƒÒp׊m0š»ÂØmà½(¦Éy¹g·Œ#G·€ mªë½K.ŠvÉ1ÒGÉ l‡!jâ¶É‹;«VÔÏÑ>`öˆ$„ž©ÛÔÜ·î`@hr.xŽëm ˆËÒßîe:ìÿfAÒí‡}z*ámŽéÇBt´!MGk¼Ì   ñ!€Á¬!½ñÇÛ*VÓ{*UºÍ7ý­kí0¸©ó ÔnCP›o ò¢¨y­X¶ÐRŒ’µ0‡kìذطHUKêïªè c@–†iÛý5/°ÓñhI¦æt²úÔ_1 ?õF§U‡ óÖ©òK,?˜ÌÔ–ÿê9–g^BD™R†Û¯%ȽTLI^Ò·óœ!±ä§Ç$ÂäF+v8ôÅÜËñæ™ú °Š‰ÉG($4ð+Q•zlmÎx_ÖûêV$Æk( yZ6hÛæðUýFM¹ŒÞÇïvèB7„` ]æ9J;ÿË(…´[’õšø<’oAjB9ôÀ{iN—h½G‘mÁb›ºÆÏXŒ0ûä¼a2˜ðé¯ý3`LE+¼pÒPzÄ6X59W5lLKI~R^MûL'‡#7trÛñ0éµESî:ú #úJwÈ=ÞýªÛendstream endobj 73 0 obj 3692 endobj 71 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R74 74 0 R /R31 31 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 72 0 R >> endobj 76 0 obj <> stream xœÕ[ýsGѦÌoâ7^ pläDZí÷!Tɱªrl¢\Â/ªr­÷ö|‹înO»w&ð¿3ÝÓ=;{²Jxß·Ø›íéyúé§{F¾L|ùü»Z\\Oõ ÿU­&§'Y<ɼ"óƒ`2è‚IáO² ô’x2]6ëí£éß‚Â󳢿Og‡Í‘z{QÖËGÇQäÅY‘MŽ ùï"—?õÛnW©Iô¯¹~cÛ¬ê~SWò‡8’³³Ú~!÷‚Ÿ¢¾P$^‘úBîû‘üåùKá%ÆšéçÏ¿š¾øòìSeh"Ö’£ØOÏÄoðSšáOÿÖk„™EPàÔnÚf& #/7n¸<üÐñüZ_>ºdkN2/2¿ªoêµëŠ/Á§Ã 䧃Ü+R?Pž¥ÅEÌh›÷òðêúÈYUµ8r6p]-Êõ«ºW¿g—%ElmZ}ƒo´Çê+ú¥8ô ³¬‡°e¸Œ<ð¨Hå„Qêëe4sÇW—zmâYh,ûã'Î0ÿò‘š5´-|Õn[µ%±Vw]Û½ÿc´)ˆ,—yÛ9¹tÌTNUfæRˆ×5GÊè´ƒ]àßÁëGiønVnK½Ibrõ¾µI"Š&6ÄvÙCþ-ŸŸ\ä“BK˜Æ’ÃŽc/V.:ŽOYôYój×Õ°ù›6!ýƒ²Ó—¡¾ê›õ+50e.Pvr !|b¹z4kºZÇ{˜°•ÏwëjÛ´Ê%‘ÚU3¾ß”Ûj¡W`ÓðqúrŽÉ±˜ë.ÝÃ&õvXšÊM-—½+¯wËå¶wÆ» ô8ßM¾óR®¯Ú4Æ{°_žMŽ"ýòüÏùê¯G#BcØÙן?Ìþçg§OÝR6ÒÅܧOž8Ëú×hÔ<;}üôÌM$cëydà+üšPVÉÇi›E0,QàÜ„Ìg_=Õk´‚sĈ‡z)˜à„·óX{»o^­Ëå%w¬N ©qø áTœ³B”V?þx.æ7àr–ãöQÆ]¾8bÿn§rÒ ÜqrBã­$Ò9™…ÑÉŒžýÉÁ;æ2í$›:7ÐZóËÃûz/œ-x0s0«—ÍMÝÕ³ûnÄËßï;n™Dÿú¾Žíų"ÚÄ\ŠP¼f&œ¯¦Õx’«üă½‹£ƒÀócEÐ"É :1œš!CgÅÝ }¥ìRD€¨õIÄ`é;„m,Ãùz„Úi7¬J×ÃØÌŒe_I0¾ÞÃñ‰tðwÉñßVoÿ¯pü~>s(°û×Ïž?Ñ„,eŒøRbAîÝÉpYcÙ"ÄØOŸž^Ð})%PàªÏ>{>={q!&üü ÍÃÈÑ»8Ÿž9‚ý_úbåiTP,«7žœ==›¾õü§ÓéÅùce2jµ7ç)1RâÙ|÷ÿ@’ÒpùùåÿOrQ©ø»H-Ö(í ,-˜L3]Ônà5ËÚYÆmÙß©îb»6 Ù¾ˆÔ0çµ áû!™3°êбʓ1“úêêu¹ÍÓHÏ‘‹ïÎB®ïnàm×l·õú¿` f‡w·Pä²z;îÂ(ýîÌÓt4bza¿…ÕbÕÎNªE{»Þ³ÕöÉÛ²<`#FkÆ@©ŽâÍ’$ÿïKÝ䟌B€“†Ša",$-f$)㛪óžŽì ‘\ gU'HM*ÆÆ~6I#ßSÛyjx¨vH?k()/›u]êAÂüRv]ùx„©Õ@ Ø#é¿¥© AÓ^±ôõzñq½/v‡r=ÓK͘’û‡ëpœüñò5u„Êõ CÑÇQÌŸºv-Ë6à|Þ2[ªaHM—;ß ­:üDîE¨q#Í#û¿SöÈ"ìJÀà´®ë Ìgþ.«ªífÀg1…”¤C<‘>Xj|æOÁ*[g?hK)ª1Âä²ÃŒƒÌ 2½VÀo]¯=à šP²¼ºÞÕ;õ0 YûmµÓ¦ ‡RLtµ\½z\°ÇbÕ¤#·«u»Õœ)†’¶‹r«WâPë„„:zYd?Ñ8úe 1Á·]C/SR€ôJP l@P1ÕÖlsF[mŒHÀÙEx?0ž¨‘ÔiK œ/gb—;hÄKµ ¾vÖ+@³×$ÒtÍíÖ˜°ÝE„% ÅW×° ¤Á›ÞÁ\µlû÷œ¤Î_ûF¹Ù,­‚Û¸„8§p='ÁÐÙ¬‘ã Ì53äRmW1<ÇÊÝÈ&ÄC3L9©ü.Ïgp6NŒ,0 BgyÊÕÍ™ãzÈ4s©÷L|-a©oSWÍ=ÕHˆ¢aXSf¶—^fÖ™F‘ƒ`‘)ÈDAÐ µ!Žïè‹ÃÐäpªÄ¼¥Oj—‡º’ÒgäÃø`²ˆW„¥ˆ€<¶®Þ¾…ÌœA3&dU.4.Uµ¨<4þcS¡Ïëó·@[[pXºœ~u}ùÈ!AïN Ê~:ÁÓ,b9$Z”™Ëˆmš©ÆO†ä0y?…Çx -²A6¾årîÚÌvF!üfÆdYßý5~E*¦Wõn¹ûú3eh‡R“b jIƒˆ—@[Ü®˜MÀ;Ø4_Ør×J¦à… e—å„©B<Ü”UÍPL5öE“zÅvÇ’†ìñ)'|>«4¦CÄ…UBÌxSd0€T£¹£ù§ŽX•™¦´w@b¼ž2¥ ›ûP%Šýå"äYÌÔ¨b‘¾â}|“øœC#²“H(f¦ïms„{U®l³^>ÒÒŒ‘Ǧ챓biX@ Ï£¢dÓ6ë­V "ÿ9ý“äR®UDö”µ1Ó|(è­S‘¤vK·xzY»†J/×3µ11ï™L±ÐuBÂxuŸ;GºF¡ñç> ‚‘„2+œ…3ýÔápŸé¢McŠFJgÂí-X|.t‚–wëz 8Ë$¡ŽºAÌ»Ø ™‚,Æz#QŠ&QˆÔeå†n»®·Q—0&ÁiÊ ¿¥Äx6|qp-<5‘ÿË”ø~.ûq(Ò»øwµ:x|~prþùDPf}ò·IpòñßÇýtœ?™üààìüà‹½w÷ì#U=B|C©eù /’×÷ö–‹(„†I¥ŒD "mòr—²BÈË/ÜølT’÷®®=‡u±TçïÞé½8NÙÃð{ó^çJæÄ²Ã8ô^4æ=ÍàØÁmrÀJ+ïÔ‰!sÖÉéD ã•Ícù?"¡"ÃIìûÚ’{ýäg?=zïÁÏùÁ¯~ü£_O~rùè7Çüö½¿óŽHT³"A0r8ͦl:GJÃ:–m{µÛ8z1!Á±’g¬€|VÀ«Ê V‚rÄ@.·[‰@$)k9P£i$_ÁM©\˜zr>×ËÃÖØ¨ óMa»!½óî¼Ý­gî‰,?"ãšÎ.VXo&c-¥• ƒ0Ê›Á'ú4F?É&ƒ°£Ìyöõ¾Pòeñ“O¢,Íå¦|/¡$JIñ /%òàHÞ’?,_¡~ íí`H_o=EÌK­ÞÀ¥~qƈ·»l8ƒ,Ã…˜UFÏ»v 8•\²7ÇíMßc‡>™¥ãQBg ƒVbG º %8Û² s •¹Á÷–è[×טX©¢V}™÷‡ºv·m4­ª÷RÔ2Û²Y‚'3–LÆ«w8¼Ž‰Œ„¦T¡°þ”Ë4d­Ü^§vØÇ¹aÈ‹ÄFáwÙaðzßîºJ«õ(bø= Çßï°„n¦eÕųe_ù.V@/ò¥ƒN£pK.aÅâ®c¸€_"c¥<‚¿FvN@Un fÈ´x¦@˜`õû¶9Î LZöà´–´NÆ>Nò„Æ3ŠÅÎ*ÓDßËÑÈ¡ Ò–åUµëð|€¡|w+¸ïáL óh2â+÷ÀVªvÝHùŸNŒŒÿV5š vŠaŠ¡œ9ª*±#-rʘᔀ¹«†jÛ:Q¶ v$á:bzÈtZbÁȉ(wõ^§P¡…•R˜±U¹½ ®޽¢a¤X¨ë¦:g>k >_/Ýp)ç@Üvs‚æNÍJšÎ…¡] yÌËë‘4 ¸Å®…~8kk«W9òÞÝ2?L#)óÃ\^;øž´‰àW¹ì0ŽÊ¥8é«r’^ì.aÎJ8ÔT0­,NVt^‘ÝÕÝÚiü"LxË “˜-Å.¡ +¹dzªÝ…ìŽi,B{»©;êR ’æœCϤ_ß)„£&ü©ðŽ(S1c:³ƒÆo¶=Á+º¼ºãŒ×à”Þ}]—8‘b ~€>HzÖ,E,öúý7œ¡P‰ðî-1˜9Yn:<ÁŽÃM33­GŸw†Õ}b?ÇWÐeÆ{“U»îEìcâç–°µ¡•G»ºvŒ–0vÎ#+uoöêîÔx­‹Ñ“@ YÄâÉÿý~öÁoýÿ ¢0þqòÓ÷ß{ þßûÁ/ÂÔ®|²S-¯›¹A!Ö­‚Üsä$݈1É“lÌXeÏË«‚½²IŸö ëÒüØ%sý]ì:ÔŽT±ñXÏd£|’òJôn)‡Ù€•ŒHM Ø;Rÿ²ÿ‘¼‚<ÐÕ«F%wqF…uœðÖ s霪„^éâóïr÷n>ƒ¥ë{JÉHÏ9ñÌ™™¹¥0ß½¬•è„”0RܲÍ]¶ÕFïw5U¾íëõN(£Â$ÎØi5ÝÖ")§4ØðS¦ “apg+KGý ô¶uCg€œq€«™ïÙ‘yÝÁ´Ö™Ô÷Œ6Æï>Ì×GM©:—JÕQ“¾'<&x&£·‰s)€k2 Æ¥™¹ô³S$Yqâíõ!M[e°¹ ¢þ>Y=z­Óf@ŒY €îJ1ßn±wgݘþ¿QF§ ôá·”s€Z™îA b7t—9k®\Œ&$% ɾ“W5x¹Ep¾?¸"œÅ½–eRÕ˽H)~´ÏŽE4õ÷jö·Hv2‹(äÝ{ü¯;L™ÊUŸ{iÏðäŸÙØÕ,l/ö­C‚‹uñømŽæ05îïSaÏ—MÜÕ›¶Û~û¹#êvÑŒ";%p›K9]d_ü?þl“endstream endobj 77 0 obj 4441 endobj 75 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R74 74 0 R /A 15 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 76 0 R >> endobj 92 0 obj <> stream xœµ]Û’ÜFrupßæ+úQr-Ô€÷IZɱŒXk½Ò„õ`íØá´§oê ¹üûƒ]U™•••À )7O7ÐȺd<™U›¥Z4ñüsµ»ùíæoáß_n”îí¯;À­6z±½±vN÷loîoö7¿-TzZþcµ[|w{óÍOÝ¢_ÜÞÝ€ µ°Æ/m·h›n©ìâv÷ÕÃo‹ï¾¾ýŸ›n wmnìÒ-Þߨ¶é—]0Ъ¥]X~Û5‹Óxóó2e—šÐŒÎÁ¥Ol„jûežÐilÄù°zxs½‹íh¯lšp»æßÂwZ— 㥴ÚûØ VéзÔÎ>7¯4frÅôñŠïícW:÷Øß>vÅ6]1ê±+zÚj¼Òú镆õÇøjV° ýcs¼ò©Óeú‰Ï|K£¯]cצQ‰î@múÄçkïâó‹;<ì—qb øÛç°jég Ó>Ó€±þ øxýYÂÎÀ!z†¢bàÝþ°.>——´ ¾æ¬uáÛpE»ÖÜöÛ®—¸K¸7·€[‰}zf£$v€ÄІ¦—Ø$¬ŒÄp+±JX+‰ÀN`ßî%N}tÆHÜn%†>Z%1ôÑ:‰¡¶—úèŒÄÐGç%†>:+1ôÑ5;è£õC­–ûØI }4Vbè£i$†>j/1ôQk‰qN;‰¡ÊJ }TÀúØx‰¡–}¸“ض;Œü%hkåRÐóh`GÌ Ÿ· >__D+aEšej«Áç;—>ãóézˆïÕçtÿöæî_sCŸù ûgth¨ S•)©Ãé‰4TáÎ ÕØ ÕX ÕX Õ¸!ªpÛ Õ¸#ªqK4TcO4TcG4TcK4TcC4TcM4TcE4Tã†h¨ÂH‘†jÜ Õ¸%ª±'ª±#ª±%ª±!ª±&ª±"ªqC4Ta¤¿HC5jÜ ÕØ ÕØ ÕØ ÕØ ÕX ÕX Õ¸y”’º¤e %Áç/GIí*&Éϧë­?§ûg(ésôJ2¦!•”q|¦18ÑÑ}/q—îiŒÄ-àVbŸ°R;ÀNbhƒê%6 k#±ÜJ¬6Jâ°8ÑAÀ½ÄÐGk$†>ÚVbè£SC“úèz‰¡ÞH }ô^b裷C}#°ƒ>:/1ôÑi‰±ÄÐGk%†>ÚFbè£ñC–ç´“ú¨­ÄÐGÝl¡ÊK }TZbôáNbècc%žQI½¯TRNÿ¾”J +²7a +qC×QÜÔ÷3Jz¥’|0®>¼ËÄj•þ1 `/°ö€Ä°Xá3İ–m)´ÕH ¶‚’âØõ=ØŠnTc°鲯`+FÎ w`+*¸£-+p‹¶ŒÄhKKŒ¶”À t| 5îwƒ‚óQ™Ö¸ìÅê{'±l%F[Fàmi‰Ñ–ªp˜#´ÕH ¶º^b°Õu+°Õµƒ­Î ¬ÁVç$F[Vb´e6hKWç(’Å/úEŠÿYóÚ«í f˜Ó¸0Ÿåó6v½ûBÊw*>¿ bvWžîqí´ùÚ¶º“k§Ï|Àó Ê¥_ ¶†Šc“II`•I©Æà ‘”6™”j ÎII`—II`•I©Âœ?‘R ‘R…aq%Rª±#Rª±"RªpÛ)ÕØ)UØ÷DJ5vDJ5VDJ5¶DJ5* )f"¥{"¥ CJH©Æ–H©Æ‘R…{MUcŸI©Â}2) l3) ÜeRª±Ò™”ö™”j¬›LJÛLJwDJ;"‡Ç*¯p(ù—ïܪ3K8h“=Ý«Â'éÚ¶ºsJH¿ûÏNâ¼W”ÄL‰[-%nSâ&0%nSâ&0%nSâ&0%nSâ&0%nSâVcC‰›À”¸ L‰›À”¸ L‰›À”¸ L‰›À”¸ L‰›À”¸ÕXSâ&0%nSâ&0%nSâ&0%nSâ&0%nSâ&0%n5V”¸ L‰›À”¸ L‰›À&qq>x‡Ÿ¿XlW¹=Ÿ®Û¾þ¬æ“¸Ï~г(Ɇpå[K݈“n ^× Ü;À^bØ Üµ€­ÄøL#0Ôªb9S`´¥$F[ÀPÓq‘¶j ¶T'0ÔPb9\`°Ý´Æ`+Òe…-Ú²£-#°A[Zb´¥$F[Äi™Åò¿Àp'q¸è;–{ö€ÀmY‰Ñ–‘mi=ÚR£­F`¨ýÙklww€[S¢°—Øv÷hËJŒ¶ŒÄhK lr ücÉ]ß q¹tÐ?oés«¿‘Ù>e\]c˜µìcºÍ×¶ÕœÀ>óÏ".ŸH ˆË3² qG` «@—+ÀÀ@V>ê€Ã3]'0•w­Ä`+Æõƒ­¨?*ìÑ–•mÚÒ£-%1Új²Š2S`°uF…¬¢ÌlY/1زNâ°•Ø6w€µÀ@VQf ì7YE™)0Ø2Ä`Ë´YE™)0Ø2NàmY‰Ñ–‘mi;´¥$F[À@VQf ¶¢ž«1Ø‚ Ï0#®ÐÏ¿/ššµÂ˜¥#v-²V˜Ìòy›?9Ö z—‘N~::˜×ŒtØœµ>óÏ*Iõ^ÑÉÀÝ5që¥Âà™m$NíkŒ—XÖ§4º“ض;ÀÄÉ£å%nk‰“ç4M'1ô±±ÃWÓ4§Œ¼ï½Ä°–8åì}×Il[‰àFâT6éÛ^âîQw€Ä=à^`¨Ï÷éTH…±­Ä0§éTH…aNáTÇ0§M/1Ì)œ áç´•æN…p s §B8†9Õ½ÀÌi:RaôáVbèc:Raèc:Ra˜Ó´¦8vŸÄZM¬e›†}ÞæÏew¿ÔáÓ¤‚žØ%'[Úò|ºŽgêû9›=óAÏÖbXhGlIUkQ Lú«ÆŠô—À¤¿jÜþ˜ô—À¤¿* Åõ¤¿&ýUãŽô—À¤¿&ýUã–ô—À¤¿jìI LúK`Ô_VbÒY“ΪqK:K`ÒY5îHg L:K`ÒY5îIg L:«ÂP\O:K`ÒY“Ϊ±"%0é¬kÒY“ΘtV…?^h·Ö±sÝÀTÇ>oóg,Ä“ ›|þR…yÔJXW§§{¦ª°’ÎïœÊ²ßý€H`6LîükùÊô5kü#¯yä+Ó×<ò•ékùÊô5|eúšG¾2}Í#_™¾æauûØkÔ„ÉkùÊô5ü.¾¡Áßå‰îðÄË>Ì‚åï :pÊ.Ußø÷ÍÛë)½$ »¥Q}n¹]Õÿ[üJ5Áfú|?º¥n£¶K߼ߴ˦ª(}s€oâC|óðÛu¼Žç—ñ‚i—aåãã/÷ãæ”Œ6ËÀ3øíz<¯N›ãå.Y½Ô¦GûÃé4|€é~©èAÃ~êsC‡Õe“¾±ì®wéyË^E¥–¾I=öËpGnëvs¾œ—É€JßÃ×?â;áËV•æéÕ%Õ¦ÉM_é¥!»é ›å˨Ýçt—ãO{·u&žQ‘C‰ N/‹œ'ó”8 QKE­ÞìqújÞæ–C?üç U[¾‰Cõ^ér&t·[¼ ”}â~³…Ѳ¬40–óôX——éÈö‡ý3âX—oï“ …_$ƒÏéqØæ h‹/Πk½£ùyˆ}K÷…ÓpÞGV5«{ôàŽú•^wKßvëõ.–2l†²ã†Tšs®Ýùþ€¿óøÍûýË©ßqÏ(Kì°Ï£Þ±ÎV –wÖÓóvÃüøfÜöoqé•Õz9`kËÔ¬7wø]ý0rãiÜ_ÀWb 0ÑWÔ2(†BK|gÐh‹Øø;ôÕ®#ÂÀÙ+Ksuد7— t4DSÆ §|wñÂq¿:¬q©xöýûÍå–Kè]™‹b°Üúb{OèkÅ—_‚©âjyn©í×4=¯t~T®–ÒJUFp<Žû5™5iŠù$¾ß ¶Œr¨°Š£oΰâàáä_Ïãú%ºjqà7°ü£ëc믜Òâ!ûÃi7l·ÉM MÆ Û‡Í{]޼é¨Ë§ñ|<Àè„p_žˆ¾¤Øœãjób•n5lÖÏ—á‚!%t¼§ŽŸ¯8ªx|fÍÇgwØ]ª6ÆËõ´Ÿcô…½|s8lGèjp¦ÂBïŠç¹½ŽÈiå¶M`ÕpÙµµáf½Ä²807®`L•“#¨Î¼ô§ÃÏb½¥øK ÷ëvÌӒӽ녴·9L·O…é[ˆGʼn£{¦]2ëÒŽÇm‹Ã>ͨKÜýy}™k ÁTYYÃö|À1.Qî8~ +„ðºC7(c}>ì²”(Þõ¿çÍ¿Å^ß[¸ÂïÆÕåüÿeR‰± ô ìþp8C ÌP–×›ƒ-u~¿9^·ä9¯T«9­ãóS_‰oËÚ’š¢Ó8{ uLм…§T¾Mc[‡iˆ¤p¦*ñŒÊAØZɤÖÊy2,JrÒÍÎÄ;—ôÈo-k*ÃyÄY)â%ÏgQÇ;t“0ê½ãK&Í-6n¢X´BöoG”ÅïïN‡|1¿ «㘊sHÂÙº0CXîRaݽ!²0t+h˜Ð‰¤6È;6;^WçCÈÃeí”HÎ'ât¸ÇKw½T&’¥c9œqéübb6Æ|Ó°™¡È]á~(k=ôG•=ÙnÉö«ÃWÒh3K‘MYÞóËiàbj³l1ìëã(æG­ÂÕŒJ Êç„LU¢Îù8¬ÀkCk;Þìo·Û ý^îOã8\07gtÅ”êî¸/èjÖ3/ ²t8ž#@ß»™ÑµÌc±ÿ°@TßD+Å·P$vÌ;Oão×͉„/{zÖ\ʼnˆÚ --Wû3œÅ>–êØW „óáz‘®¢ö‡Éó9OJQ xÒ:'åÅgI~i9zeÞ ãÀõ^lŸ?œ/ãFñ,ÿXä&:³ðNSR<äĨå"ã” ±ð9F剺ϫñòG&¬Å928¥ƒÅ‰þ¸ÃΨÉ÷XÚ÷©ÏÓ=L’àôó8Ç„+ ámʯ:1ßG_îÑ|ÏØ|8?`Œ¨s*$©X›qÝn*IµfwÖÙ_îà ™„èÿÜHœ)ÒsÁ‹ŠúE=gظ?Uyá¡ø2#é'Vò¼‡6-qõã·Øòð–Zm"¡‹”ö*Bq-‹|ðH3Œð^ Aæ0™õ÷g3Ñ¡–iȲµ¥Q Y=¯’ç"G̤Í5¹&æŽÇÒ›ßáâŽ90DÃFüºF¡,Ò–Ì5ÖkÒ•aº2ô«˜Œ…CWÇ^(¨|ó“_ßèÓQõ½ n‘ÜÿUH6tr˜†&ôó-zf˜Ò²Ö3Ù„µÂäÍ~Â×?¼ÃîpŸºL"ÒÏa|Òã,´Õ8#U_© ¾úäå]¬{„û~¹snÈê=³ë [­Z戇Õêzš–Qb¯ ¦AäÂbá\XŒs˜eøé´Á~'“Ø.VoJ£ã7žW}_l«z† ͇õ檷0)à°Å?L¤ÔñtNxF¡8ñjvç?6—ó¯_çZªXe9£T\Hó|9]WÜD0-„?m¡ŒË‡ãfEµ£6zJWu0}ßñÌù®åR IgЖsÈ/lÐJÒ£ޜ†'E¡™*?,)ɈÝUËÁ¹ó}H©Ç=î|ôì»"1CêùPX5zá·A¤®'Þ[ÊÓÓú¥ê`Ný0é›X÷%zŠS“ŽyΞÃÃT 亵Ÿ‘1Ž7s‡1ÛÅ[™ûR½7뛩菱ûëW¿~í/¶¢?-1È&c‘%Þ×ý*G·œ=Ñ£0J¶j /ç@ÓàÆºe¿Ünöè],¡]O,äÒ{ª ±ZD¶\•÷ ±þúÕu¿Ù o7û¸Ï©IŒÊI4³’”§ñn<(:*™4ld¯,ÏØ|‘6`Æ*I7œÑ0¨ôª€x‚Çú"°ß^w!®€Ãz^Æ¢} 'JÒØ­ÙôáÝbØ–g"-É[$LLã‚ã5´'UfäœÏ%€æ M®š¶Ì¥Ã³õνØÅY-Õ‰,•ËôÍ$(Û‡ÌXvfŲRÛ r,K|@‰è¢o\†Óþ$é»+ýå;n¶ÞÑ0Â.œWÜŠ¶äÁ%.°¾ÍbtJWœkP¢”Ù\ö—a³Ï%ÂÒrÉéÙ›Ó" ªzÄ( «Õþd’²¢(ÞvÕ·žUjÙ¶4Û¸Àü³ iŽ,žåµ—K<äWU³r™º:oê‹m©Ù™BgÙÿùŠ.ŒÅ§ úг¬…÷¨†­½¡?첇(¶îçò&`Xü¦x •Šùõ¸Úd…î „üsa:×­Ü8(þ•èª Œ(?-w®6 ÊÐçT(3ûñpºàÖLåtek&ßKäÖ=]õˆ{…“;¿²hÎJ"–ã9³]ÃÎ-nUae¾—ט‡—=O^Ú/UL†ÌW4Ž‘b¿Â:Dê„ê=ïÄ̼¶7Íe¼~!btz<•M~Æò¤R¼ üú®Lו¯Ê«¼yÌI‚›ijy6KÜcK÷>Ñcã†_®LwlW¸ ²Ÿo–{À3á yc€Õ­â7UŠ&‡ÔKp¡3bW;.B‡|'lj©l·'õad¹ÂèQ3ž±Qí̈gCÿ›œ“†ÞʬvnY®Crw³Å^úó‰6(¨D5±ÅôY¿çQ—b’­Õo•ƒáª Ñ_çéÏ‘š—ÒOxˆ€Ê$¾]öeíB’¾d>"æÜœí…¦ŽøØ“<Ý9å½ÀວÍ8¡Æ™!Xâ/±k|ÕZÙ¡Qˆ´O1ô@á×òrèiÖ&¿/Gƒž‰§f—‡28AŦ6RÊÙÜþ€™ßr§ô–÷ë©tAñ¬_œ­ÈgVÌŒõ*."NÒòÚšÛ‰aŒŠÃÊkKò,^}¼ï´¦z£Ü¬‡=`•cT>ÆŽ7ä-Su¹vÇ(BŽE0Û±:{^ fª–Ø1ýxØ u@ ÷KÓmÛ~b/#ÇêùòecâlÆê¥Š/ÜÆú¥¸Ð÷xX"jñšÞ/¤Œ§`¤.7º”¤¶+7–:JúkÿØ|ÿyдM½&¾ÅO"­v7ß½¾ùæõ,Bì¿ùe¡¾ùsø÷»ÿüÓBݼþ~ñ/7?¼ŽgxÏ ^Óš¥óÑÄÒ¤S¼çÕ°Ï!'4µ8yUCdÀ¡m¬N‡)Bä+M,bÂô4D‰¨]ññœ&Î|ól.TÁ¡Ÿáô0™¢1ïD•¯0—Q3E-Ï<¿„®’6PèÒöñÎyâc¡‹ ÂoÝÌQ<¾ðß®yuµ™$WU?váüÛñÉ{‡eÆf_ŸrÙ‰ïS>¨æ’ˆŽÕsH 3y{ØÄàQ"E>nfz–ºÎ Ÿ˜æŠ +]“ºÓ|÷‡rÖÕ^e>nÌϳ±!1— ¡Ë1SºÆ§–åC(B[–@ÃÑ1Î]U.wœ;rW,®Ž6ÞJ©G®¥ZVw€ÓPëv,úïÍ«v~_r摹PT©–Ó¸;Ìsg½$r1³cv¶fîíœ\œ®8®(R1¥.s]ÑÌHò¹Ä¿tãÃÛ¬°Ë̾·ÓýÕš1ïGتʗ8<™,!uzÒŽ·ã‡ÿz$lؾécz¬m`ÑîŸ6lŸšl%¦cÜøë?üü翦9à[_·¸;.¬ÃN_xÍNÕ)ÁüúÌVeè¼¶5âûúpTvÖ±¡Ì¬B Ä8ÉdÉÛwŠ,[¤¹tÌiµáV½:H TEX|%ÄË'K1fñ—·`.WÆcfõ=µ×âít4XÍ{ÒäÕý«“ÍÓg¦V‡ý ؽ IOÜÅÞË|ensÿ÷‘®Síƒ/Ôîí45!Ò߀ԩ^n˜¦ð›ýùzB&ç{ó)Ñ\]öãê.®a…‰R¯g'[bΘ¥˜¥)£±¶Y±Ï½S2sΤe”Á¡·”ÕpΉh»²cÉb²º™§¶±'þ§˜‘ÒQWFZxä=—H5,¯/^é+] ×óÌ».³G½Xê<9ôV2úB#J€{$½|º<(* !©hxá·ˆ“-?M®”ØZ±Ë‘zX“ÜoÙzožŒ»#žÜ˜-¾SõÄ[TÕ¡µÇßa Ó×Ù ¹¾™WuÁoò²`b't5ï.ñÙÃÔŸâ“r1ï ³ª³s§¹˜rÓ—å;&¬LOìÔ±iå®ek•P„`gfvCÕôô†(Šã–6ð‘íÅøçÁt¦:ž‹²©½ŽÞe¤ ®7W®Ì%©I!­œÔå¥*ìk1š BU"·æ `¯.¹©{öµ{òýí%ÄEVMûS”}ùøwà €´—Ìýé )R —Òc%GŒ ƒÍ.³ø¤?“1>¾&'p§<, [Ì=«×Άs |wízæ¼YҞǔ8¹í’èîê&µeé^örÅ”C4£·DKäÊo­°JEyk…qtyk…‘\>[XJYs 7¸BõË@zU’UöJVX<"¿Â^¬Š¿æàÈ Ý½æ[Ÿ<÷œŠó@«bt=漬:ý(ÔE­ò½Û '8:n턼¤wQ{ýSò›¨l°°T1¹ùþõÏß~÷—À§SÒHÛJ•ó>•÷’˜„ac‰DÎygN3}¬îù±­’jÇŠjÕ{3³ó¸> as~Ý_`ǧrüÙ äxØ`À|¥L›_‚XâFão^P@Í”=J¶Ï4ïPD<¬ÊÛ«ÃqCߣ䃖¿ˆChÒß8ðqí vàuü{%øòÁ¿;ñÿ» ¿!endstream endobj 93 0 obj 6944 endobj 91 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 92 0 R >> endobj 95 0 obj <> stream xœµ\Û’Ü6’Ýоé+úÑŽP—‹wVøI–䙎±,[Öî<¬öÍbwsšE–H–ÚÚßXð‚@&ò€@µäu(ϰYȯWOº¹iðÔÔöûÖ¨Z¼UËU««¹™èu”|el Ø™·€/°/– Ù—ÒÉb½-‹Å%´ûsÛuÞxU=·ôë({DþË“BσfÆû¨æŸ1”8ÉãE‹ó¢(6ñ×1”8Ù-ú»Œ±‰óÅR^üôêù[m¨ &U¢ŠÌ#¥×fÓ”z-Ÿ MkI¢µµD»€Ò¥‹Š¯l%Eä±›"™»ª6º­ 6·Ûw]Õ÷úa¤'JŸÍÏwòsä¹j;Ït‡BKÁ9øeÒ…üQM¸Œ•½æÚõ‚èlg±æ S¤‡…óÀÚ‰¯Ò֨ň®ÍcÇ@™àïÕ¡íL“\ ‹Vw[‘SKa„S?ÅÑZ0É odÛ@~g)–‘­ÆÔUÿŒÌ<³ãNm_7žñ·³±¨ÝZ3yáz?ˆâ&, å¦òì@ÅeòìP÷Ì3 Ńܵõ~Q¹6o Ð…€ wÛåmІ¾šyã=UeÝZÄå;à7Çf¬߬76‚ëAíU2Hª¼_´ó7q (Ö´ ÌÓŽñÈ«ˆGoršØMf#ŽDãö͇SsjözïÓ&F„f» >²2¤0€û6  Iÿ0ØŸŠÖLǪfcze=B^‹„¾Œ½-¦ýŒÔLdV‰ ;+ž°Íí¡!ù† H¨ç—ê©‘X©µ#²ì±n¯»OÚð@w{b„òa9@oÈzÆáÄŠ^’.°¢+lÈ4d†´NZ¹¿{›+¦¨ÍâÅ_]ò‹—ÉVy±åí|“‘ÀD×_·SÝt]Õ7ÃI/µÐûF ÷ó@|bå /#ÅÕÿT´k“éoÿfÁ'}«zR®œÕtßöÕøÉÃÌ']Cª¹o¦zl³Ñm¥îò§±¹Qê­FœˆX)m)]±:ŒRïË3"ŠÉÊ¡„,õ6.Ö4q‡¿V÷´ub+‰¸c5΄GbPÖJd÷ÍŽ‹þd>Võ¢x™ÂÓÉföÃx¨:ÒL­³åùõ¶«õˆ9“ò)6P©Tú~ 7ƒMàTQž`De™ì€¢ã&;ÊÆs<ìÃw¨A&°²8­}~É6ü‰¸‹ìȤb‚z~ÿÍûoe»#AãÐuöÊ0ä‡u7Lü)ü pE$@‹Ïƒ™ýõدrXeÄ‹îÃT(ŸLNˆ™ç°l#m»ü-Û‚‘T–[eš‚1”ï ÙÄ•µ¤­¢üäû—ºššéÁ@–­` äú3ñFâ‘—RC~Ê:Ö1›,‹,FŠíJÀJ—׌°Iòm„*„X}G–îu{ 9Y‰çZXLiJX¸1òˆ•ìQ§‰Ü‚8/PŠ€fßðf¹6ù ý¤@§Îé=Àé»¶ÛGÁ®kÌT€'ž½bÖ‘#¹E< $øÌˆÀs¢6<!NwÕȘ´bfHÊ$*Zþ „*còrÝ1¤e0Öl”'Ì–Åf«¡½Uz&ùØVô*PREîßóöÇ_þíý·Z‡òd™tÉðc€Ô“<¹ãù¯›Õâb3¤•?¦;”a¢=WlkQŠäQ½~µo–Ç÷´d ª¿“*iL³(ªñŠõ阎Á“ )^÷‘¦¹nÞÖÓȇêž uÚäûõp¤]IX–ZÅ™‡sž&O-šèˆbcV¤k³ù†“Ò>Eõ6zðþ[PW£=#½:9ÚEÂì©ñÂ3 õîNËz{¿qˆ’ÕN„°YG,æõ  »1 ¸ÈH½º€`ªgõžC#…vàÄFë¼™jÍsUß‘ëÏ`ß T÷Õ\”þ& m½¨­v+*"Ó„ˆxè9öT„kçFHÊ}‘Úïà'bÿeéh#¶kû{Ft¡Ïœeg;¶‘¿² %3VÓLc§Å¥‡]~F*c¹cÙZÄçñ¡1Ð>'Z #Óœ´cy¨½Ž€f†ÜôØÔ* &‡lÙã‹ë'øÆØýØîI²˜Ý„’š—lÐŽ±t¢_5í¶¿þ‰-µ§žÇÁìs¨ÚIÉÇ4^>'³5¿!h—å?qƒ5KV‹¸‹€žŠ;ÇêÞ'GØpêSCêÎiF?ÐDÇóË[zs:o^Ð$äwB¾d›»úÛÏž!?ÿI¿—¹ÂÙ¿³éLÔ(v8qh§§ŠªOØÊ`ùñîD„:A·¡÷Á Õn D€Ã¶›འJò6z© .Љ)SaæéŸPh]ÓšRá§] Á—ª;q²hK5U5ba'v[Ï 8ù8˜/õ`“aI5p”vö6˜lòóîž­WNûNímoª“°eØÒ߃ةÛu•¢ŒÆŸfº¦^€?}Å,µùѲ@v–°Øž¸P–¸D·‡çt}hgj®RRöâ¶ng—‡Rÿ„B‚ßÅ¥âóÖ6q•èZ”‚q˘“¿Ä±o:ò˜âmt½ú™7­”‡K9RX¡ô$¬åçt²±Ä Âú›‰Ïƒá˜HÃÁ'Û§ƒ¡Â!8—š zòÎä ˜Ï­—™†“äl=28QÇš«–ùÆÓv/®a¥Úï™ñb2uííÝl ì"xÛ˜€øú%é.OgBå¡»¦2Aœ3yðƒÀێʲÉH“²×Ùê,yªÿ}bc°b™œ%K^<ãzüÛfþƒ—OÚ--¸ìoè=×U2¨ÏÚRéªË-Ôìä (|xÄ}ˆ˜ùú‹9Ö3íôl³‰Õ#’³š8D‚ûHáÃ8œú}°Ùr§½›ŠáÔ…Žá Ï™þâ9Óf¼!‰pµpià£FÊuËe²3ªªÔ|“蕾V~QÅñRòZY¬'¤i–¼ýx¦4½ÚŸ×mŽ€ô[Od]I Ä3) &/¤»M 'MòÐXÊœú|Ú£¤›è`ÏŒ{ƒVÍ—M×yLâÖ¾Äö Œ¯ö·Æþv íq²ÝT¦Y|8µÇ#El¨¼\nô­Ò¬Ä¼uÕÏ ÷‡ÃÆ« jO‡Ë««+ý]ídhùvûúî<üñË lζ’Eñë<yhTœô‰PLfAÙÛ8ÆÌÝxê{é*¸þQy‘~{Iò’8TQžË·/LÞ"OLòE6[8!ã6ÚÄu–­“JA[n§r:]<2DM¤= ë%ǽ²Z7M”`ý*×ÃDq>ÇR“Ä(‰Ä(ÓP·ÕlÏ|ˆÂÙj´›'`2Ç)Ñ`ˆ ¹§l5OMw³!-ÂûQ0vÙZ?€áQ€Î“õlehÆ®ÎõBoÇÊ”SsŒ=ÚCÛUÜúëÈt¹˜¯à¤·Á±$ÿ•›ñcÙÊÿ6O xv» É q1d$aB!-ïçá\Þþ@fdZRø{ýžâ &uÍä× ÎFC¶¿ôš¶æwßÕ§Ùfxâ5+EH¬D8%SÝáøŒXäÎÃ&-fµYưuZ ‰|·!#<Íé«™µMŠ’"d^ô‚AŽLÙíæàÀi]¡’žœø÷ͦ@Y/ØÄ^ÝZV -fÜi™¯°;*ŒwÔkMa¶6 _ÂÞpîA"‹CµoåcÂ¥)ìó«Ià<¡W®©X·³e>ØËq¢t’{òŒ;G§þñ³š3“•év›,GÓ]}­“•évAt9Ÿ¡ÏU**»´Š½ÿæÅOo^üãÜÄ \ùWžX¡ÙLíí+S[Š3<}õú•ér [ëI‚ñ ïŸ{¨¯–l,+„&%¥ú¨ŒoOã{¶?“çIÁ(ÔÒVͽÉB]5/L’Ø¢ÎS/ÒnFØêŒžÇ|}>ÂtÖë ŒMVºÇàl_£•_™”÷U¯‚±zè÷¦˜íàlÛ¦bf¶Üow›`SÑÇÑ63€7æCvœ& ; o—cMèñ€¡d÷‰‡¼eà¤bnKì@I0®«®£|ö6Gxµ Þ}N’´L–FÊ4ÍuCå×°Ú$‹–‚TE·Eéà éúÝä´$”CÏQ,½’75ƒsÎÀ†¬ùª½ÍilŽÃ(—°ÏbÓ ×S'B áq_š }ÂUËíêÐ$±_l ­öÿ:M^w…‰øšn¡ H›c´®›…ò—é¤ñLà\™ÆxT<£eK¦Nœ)  öçXæ´U\DAS޶qJßOAjÏCK¿/5 ÀÕé&¿Lü€4Ö“¬+ʪ»9‚¤ð‹u6µR$zÊávŠÕ†‘ùñº'§”½>Û=Ì:°$.Ä<öIcáPØ8ù½õÐqcQ}ÇØaå ‘AÁž È«7E7¥^Çž›ŠíËCÒÀu36;Y§ã]k²VðekùðC24ðAþ½ÙÇÕ&­¶r¢&p&/ܼ‚Ù¸}Ë•2Î0Ð1[­bÑ×ÍüÐð™sk¶!—6òàµüÐì[ójáT…X´—`€›ñËžï*¾3CvzèoœiV£W£ÆïáÜ'›ÛÊ^às¦®üs½¡Â¨M`Ú쑈¡k +•+Ი•^ŽŸ˜ãcvBçäî7í‰|µÃqš™#o1[Ä“Î8÷l¥œ¢mÔ§`©Îޱã™#ò¼¡¢ãÐ/Zú'¹0“þ×:fl?Ü­ ½UC×P‰<ñ°\®#Àæa‰É*•Ÿ~ÆBëÊ^Í™~M¬–>¿™žÁÆšÓlÜ.ø"-ÒÒÙ%µÿ:œÅ‹88L²ðh“Un›HV³âèôØô{iã²1ËúÖÿl½Ém?7#BNÕO¾³Ž%o[oÞ¡p‚ìî¯_¶è\îȇÉ~æ&‹P§ð®Üè$ôáŠûÛ@6m²à×§ÿrýˆ”endstream endobj 96 0 obj 6419 endobj 94 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R8 8 0 R /R6 6 0 R >> >> /Contents 95 0 R >> endobj 98 0 obj <> stream xœ­]ÛŽÇ‘]è‘_1ðËʧUYy©*ûI²µX-`ÃöëÉ0š3EN/{ºGÝ=¢éïðoDfܲªz¨i ‚@)•y22ny‹nVîªÁèÏÛ‡W?¾rW›W~Õ6M¼úøjpêsW±ëÃÊwW¯?¬ú.eûªEßž¶†UÛž~hW}› ÏÍ«¯\î›ÿ¸}¸úæÍ«¯þâÝUX…¾é®Þ¼{Up¹«¾Yy—:h ­†¯Þ<|y ýæÿ^}ûæêÏ‚sHí*‹“(§ðNáœÂóRœiÕ ÎCþ£Eœ3É…f’) Ry¤ò0Håy)H·ê›~€ÚUì;¹ Ëä]¿ê“…ISx¦ðLṦoVd 0ÃÌ0¤UoQ‚É‚‘9"s\Š0ô`0="L SW5¦ŒÂ# …GP Ï¥0SZ5>O¿³~Õ´&Q Lá˜Â#0…ç…0;èÂuØ€_õyº]3‡Ù5Þ­Zk=LQ˜ÊÃ0•‡a*Ï…0»Æ­RÌÖã̧kèÙº"¦œÂ#8…Gp Ï¥8¡¯]ƹ`?Ƶ¡oÂ*"„ùHˆàú¡¨¦´CŒhæÐ¬º6b4iáïBí.ÃÖƒA(ÈÅ(©í\ˆÒ5`Þ¾³(¤óØ~gq2EqÅàdŠâ”v.ÅÙöضiõÒET§…¢0‰b`2EaJ;—ÂDoéZ‹Ó[œ¨°±µ8™¢8‰bp2EqJ;—âLˆ3ZœÖ~*Œ'S'Q N¦(NiçRœ=âì-Îhp¶¨þ±ÊϘ"8™bS§¶s!Îͨu§–-ª²I(Š“('S§´s)N4£ÚŽ:‹õß[ýŠâ$ŠÁÉÅ)í\ŠìÈ •~Ú°Þ¢þ{«ŸBQœD18™¢8¥Kq&ÄYéç`qÎ×LQœó€ÉÅ)í\Š’÷!¦‰›?0Ý!–$.´óÛÃøs;Äή±×œÏµ!g¸4èñÍæa¼úáˇÍv»9Ž·ûÝÝñ‡_ÿòÔÇ +hô*¦¸êRNºþøôðv<\íß]ÝÇÛÃæñ´?Áu‚<²àhîÍÝ—¿úðãßãûÍñ4þ~Üß~OÇ_©‹\·¸ŽtÑ÷«Õ rДeÂ.•ÉD ü>/â⪷±<5±]µIœz^œÄUJÆÜ!¹…U«·Êâ}ucÚŽ8PÌe„³0´…Ë9зÁ  Iì\+äRT¡]¹®Â ËFÏ\ ª€±#W ‰`kFs#A 4 ¢s}0”6b›…«A‰Ë¨‘ ÚJF 7FCØz0”ÎC.H\ÐTô".ä¦#À#†ØWöÖÊÛÚò"gd‚¦Bª$ïäÉ9‹©¬V•‹yЀ̇±úáPÝŒ~¼TµA4Þa“dºlΕ¶¾ÛlG£«ßCúË$ü*4îêoÐÆQ[Ж¼†Uµ-”‰ÚƶRPÆYµm‡ˆ*cÔ6Fh'Vk,P…¡ ø{Â¡Š Êš*½}a¢½…f¹òg3µt•Vóà'L…6Ñ]7QJÀÛZ¥l{ðkCÍD4£¹øU¬u2‚‡‚ÕɦÄ5W¡Y®$ÐI'SïNœêdê@ôýP1É0ÑgµJzB¥* vý„«Ð,WùîåŠÛ‚bM{Ì>ùWûívî_¿'e)4`ñmºnÕT:K”JgC½l‚†XÖZu±ÃüÔ謄CµÅD0_묺ܰr—0ó™¸ÜÀËeq¹¡,…-Wù²2€ÜúĸG5éQGÚ4%OPJ ®bjEÖXB–«|i¹Jë5—öÈ\¦Gvü.t«`×=É¥WÍŠ”L“e*¦Òvͤý“v§Sî`ÊmLñ>]2–Iza,S4År•/-Wn¼f’þ˜Çô'ZySMtoŠ{…•‘“j#7Ê*Lå;ËTÚ®˜´;fÒî$ª5¯†Îƹ¦ïV.ÔîÂ'¹ 1NËU¾´\¥õšK{d.íñe®F³ïäѸžY°¤H;›¦›»ßI©ë)”ç]ôê»Êõ€©¹ÞºžÒXmIzP_JHæá²802‡}òŸ9OvWÿ³fJÁfàdŽÃ· Ï69ÍÒø¨I1À`η(n HãaFáÅzZÈe 8ÛäTœ ¦&I÷½ï0ÈŸoq2‹”‡-jÛæôùü¸Å~T›jÊ9a_r¼FÓµœš…N•B.€Ë—dIãmzX†St> `s!gJ¥ó×f'“(ªóJ‘ ¥è–Ÿ¯Y íïçüÑÞç˜HŸeb{ž©(Íó½‘ÀT€z,jÖ»³“ÛCÊ3¤6…jÃâ|H˜rÁ´þ׿ýÓaÄÉl{\áÀëU×üié˜ö)¸*û,Xƒg®nÕw̵Ç»ñÉS8ùøÝþYÌÓ6»Íi³ÞæÎ,Řþáǧñi¤&´ÿÛõv»ÊÌà„ùûSË-iùÓu^ˆ·ˆVí×`æStøWëlò*Ýá^5óï›÷›i„\8û<:òñ~óîTƱ­ít,Hi°ÀÞyИ§ ãi¯Œ©Þާq†L§®Æ‘D¦Ò5¢i ëa<>mOÇ,k7€íy5øðyÔ§ûõ‰¦Ê‹@×î Éߎ›Ý{‚Ò‰<ö0UûÃxWzY-:+B?Úq_¡Ð7k’¨7Íßî'bÓ‰]!c߬I¬!ÃéË_}ÜœîiÊƒŠº‚E‰4ü!š5¸+”±PP©±Ÿ”'ÏîD-éXQÍHAœ«5¢H†kŒ 0=¬bþÝãa;óàU¯÷﨑ÔéÄ!©ËÒ†²ì¦Y€hÒó)Y£ÿn,Ê„šLÀ> JË“hvl"ÁØH¶1 Ï’µVzüx~K£é¤{RäÞôÍfßG” dY}‘ɳ³Á3v(ª¢¯‹ÏA„Ä´ßÝŽ3'pº§ ꌸ ©‡>¾ÛïÆ×$Š$C>°‰³o½Z)ËÚÞoÁm¼Ý{ô ïN©÷SG€6Eò‚hÝzÖ!W¼k8¤´0?ê4(g¶¦ÛÍ»™ÿÚœ ©Uýà±ÕÁÍn}{Ú˜ÕÍ,LÙŠTš&õ Mh•jÐí~w:¬§b½™"VBAgD€WÀŒ ¡~MÔÐ?Þon‹âvÙfwûTv_+²z ÊqýÀÖàÜ‚_zÞ8ׇ»-ÛvªÌºe|#ªyâÁ°!¤z uZgS:ÖדãÔèyj  ÷ÜÔoaÌ“”P&ëó³í,_ç\,R ,ìöó‰jf~z|¤`³%S\Ù?íîh~¢˜÷~WDg¦gIÅO”hTZe  —Lm]æÑ8Ÿ/\ܯ‚ø<¼cÄSmxýŽ£zcÜæóóŸg«wŒb)HŸ$oäñ±°6jïû§íÁR½-½ øÚwD3|Q<ïÃ?‘Å©q7;vt÷ •£{¿e esšÃâX§;Ìm#ÕÛÊc9£^ß툤qÇãÇÖÇq&8u1Ž´ë°·e4Ë6ãéë¹™8Ä,ªûõ,ž±ú.'WÞ&œ0:Å|WH¬ø¼Ÿ|€Lùáé!÷à± VÈ0å陟&7V®óéÚ²h\bôö!QOq{?Þ~ àwgöÃ0I%Ö·œ¹è„©Öü'©†ŒcI": Ü_lÙ4]í5üª6þNÝÁz»¹Ûœ>QèP¥|]8ƒúÙuñt0ÊhÍ~GF—)‡ñôtØqª×›¼wÁ-.i†Úætöžupç­Wòa“úî(j¤×%Ô™Ei_cIòµvòt,ó[ 4à•:íÈÉÇš`bt^ùäY0Oì'êh-µØôÔW—„k<òIÓaö½Ö¶÷l<‰×(»™0Dfjëf¡m×ν¦¯MŠ£©yÿ¬ŸÁ ‰$¢3¿¤¹Oc‘7Þì0Â:…ýrƒºW)ÕÝÓ--s“¤ïyU¦áVs‘ÛýÃãú°9nö»’8Ä€3V¯¿cy7 — ñýaýxÿš2¿Fµßíf7röÙÍo¸ZF«è4ÖºçâçT%™éåØ#ÔMÌ[Ž¿]qo´Äá êInÛ›Vž5\Íä’q¤v)ºV9×$¾f±TÉ&Z‡‘žäm“pb÷ HÀŠùs9jX¶./ P8µ÷¦IÐM¨Ð€lÖHœ,ºÉ Ø±ìÆ5§ej ÿû×d#FݳÁ¾ß…ˆ*Ñ%?š¿³y­D:«ïlÒ–Øv#k‡Á.¢³`þõáGü»¼íú#¨îþs ™òUß9_ñ´¸Ç ß|÷ê«ïþpu:<_ýõÊ}õßðï7úÝ•{õÝï¯þãÕ·ß½úó˶ ‡›Ç£—7F‰þ•ß¡Áõj†+¥š¡$YêÒÖ,% ¦ 7O´#غ˜Il÷ë»EYJe´U»Ö¶Êâc˼í×é^Þq/+“ßÈÖA2™Èf÷v<}Ç]I 2ÎvŠ¢Ðÿ=J>‡Ø[7ª]ÄqÃËI¼»T©þ|q¬^±íÙ\O2òni丬^uý¦DCó(·°$%ܼ$®ÒåÓýšS›ÉF¤rjÑØ‹±í‡/ø5/=­¤sÉ!ûëã̯“«$T?mîÆE±*»"[lWi*6o?êìðú¹h¡]uapâäCŸ~»§öÂYñº4ÍGa’íÖ¬d76œ(³Šä¸.ÊÜè8üðͱª1ÉH§½Š™ªrûÙÐÎmìU;L’ºX=¸ÇÝ]Ù\ Fix›à|èßåËx$'ÄLNä~86“Y9÷¬zÒ¾«1Ê—mØÈî9Ü(ÌKÙÓzî(ÏïMð¢vwzM°mC¼ ¦!Çn[Vͪ4viM«¢jÃÜRc§Ý‘%I>BO«X7hh—¡dæ ŠõøÁ, ²ÏˆÖkö ¤'W’‡îÚ™YHñ4æÎDªÛ§‡§-ŸZĬEìt%1ëaüñiÃ;4ÉnRrFevp l³.2b§KXÍîLLÓTaª¡œEåÈ”!Dc'gŽ•–²h¸+›_q¶ÖáëÖÙǼ`¦¤ ±öv/ÛEUÄ:žÖ¤æXØ(¼Ùò†îÒ6¢É KçÓMæÒLÎäjº¶°'Q6<³ÝT|‡9³Qd*/Én{“¨/ÙïR"T>5›kY g ³ôæx;î`y¶?’ݨ/Wwd Kfõñ9ÃKYé‰Â‡Æ* ñ»ÎŒ•3© ¨.Ç8­Ï:B»Z¿/V–M=¨—{wØçí,¼Oñìn½ZªTú Ù¨<»”jMª·(ùç¶RÄÖ~ï§Íþ‰Ã”ú’’wðqo´þLN`œPæÏT{¼müœZÓq¦âÊí‰9 ýqsb;ÑïI]:U—ûÍ{N`{³ Ö|8==’Ï1‡»ê;Ý4A„°•·+/[vãûiÇYiµüýYGh Ç:unãü,ãs9°ªA˜9œŒ¼&3nld>ãŽîieZ­rfiÊvîÞ>Ò>[gV¹?/…£“Žê¼ØvZ /äý6èÎP¹$ïç½oñ§yÜâ=nýæq|ôaò&â¥Çê½Ç‹ãƒ} ù›OæÔ5Þ> ×—-‚o,åûI‚’(¥ðJá”Âób”àÂòu]¼­;ygÉ(“>@T”LQ”ÊÃ(•‡Q*Ïe(f]œ¾³˜~À~ “(¦ðLá˜Âs!L¼ŸæÝô™¥À„É*/O&Q Lá˜Â#0…çB˜±‡®Òô•¥Àìñ%J%M¢˜Â#0…G` Ï…0;pöÁMY2Ì̳k…É…©< Sy¦ò\sVͦo,¦¼£˜òð\` Àyéãt‚iÞ¦w 0­×$Š< \ QäI ¶sá“@Á©©_~”oT(™¢(‰bP2EQJ;—¢ x«5^yŸî;HPû°V(Š“('S§´s)Î-¥³Ôý€‹´JžLQœD18™¢8¥Kqö˜Uò´ž38ïóM]Á)ÁÉÅ)Á©í\ˆ3à¥^WÉÓºÎàa9^Á$‚¢,’Š‘Û¸"¾séü¹·é/O§ÁbdŠ‚$ŠAÉ…)í\Š36*œÖopùxëßàdŠâ$ŠÁÉÅ)í\гÃËUíë8Ãp—ØÀ$‚¢,’Š‘Û¸âëÀ¾¥}–]¾ek³]¦H¦˜Ä©í\ˆ3âþ_¨DiŸ¥/eåaò,})T†É³tmçRœíðì³ôi°lX ÿ¿ñ.+65]ûóߥÿrD‹pßHüœ‡éWÏàeìÃôå“ú"Ý÷=Þ7/ŽCãòU}q ÃÂÝ,–ب"üÊC:öâ©Å×aÆÆSÂÛ!•š8|ä]^ÂhÓPR^ËH¤=1á>_²ýã3¸l\…’¹ °(ÔÔH\xÅ räòÇ åÆ ÏZ¶R<|+\­/zó‘ wÿ•Á‘^HƦ„‡Ì…E˜DTÈó)ÞÉ›!é ©²2FWŠ>F÷CpF/üÜÊ¥Ë_¢{ðTmòÓ—èxäðÓ¸ôªwá%ºª+¿DWuå—èÏ©kç˪RæÓ…PÆ+ó€2¤z=ø5´ìlˆJf-µY¹Ö[UnæRå…ìýÊ9« Î'L…f˜ÊW•>º˜;1 é ÛÔM¸ Ír•ï¬F†­42ບ©,SùÌ(dêñHÉ…LÝàù '3É0ÑgFùºê£>C'~ƒ®,¾A÷)—˜=AŸ½]x€.ªš_´F«ªD™¨jôŸQU|U©ªÇU¸³ªŠÅ+ún¢ªÆÅ†8u±ä7*ýÔÇÆ©F—/'zâ\ï©G£÷Ü£ŒTuZ<¸(µj~‘‡Ñ|Ð/kC,l.æ¡ߢÝÔ6T¦ÄÚOReiùKË¥…A”K{«ÕeÊ ^)=î»WIza R4Å0•ï,S’É&펙´;Õ:ÈÝ 4î¨Ã_f¨P EPÅ Š ”¯.E‰Ûw]…RÍÂgKLE©FÉE©F©_]Š¿€K7(Õ~B V è J¡J¢”B”òÕ…(.ådPªõ `ùEƒR(‚’(¥P¥|u)Ê€W;‹2)Ê4äU»A)AIƒR(‚R¾ºeÂ"BÊÎ×aY™Á¢Š $ŠA)A)_]ŠÏìÒ`Qöõ‰]oQ*…Q2Åøu¡0JýêB”ßS·ÊÁœ×Í L” ч)‚R¾º¥ÇmtWyõùª‚a¶ ¬T¬© k`Xâþ¤Œû«Në,NÑlq¡Ã¯õÿ•œÜÝö‹'w¿{r):Œá„úä®ݹÂòåè.tˆÀ‚ÑÁŸJÊÑ^Á¬î\ß•ƒ»!lŽ-Ö9Î[tM›7˜bBó0P­Iƒy÷"™ˆZÀÙ;ãÝaämùIö¤‹ôÆd¼ø…¦¦ez<,uÆ!—J)=±ü%‰®ÊÖ°ˆ{Ûé‘`?€’LÎÈo›–í¥jh¸j”Rö|y¸˜ Uã²­‘½Å“1’ 2á±Òþ"’3Á;´³¡Íê®kpã…‰\  hÅ{cÜä2•†ºÛÒöø0àºG¦!ïãõ¸Û©sc¦õ4™@°pG{Ìm›ëcó"WÈ~uJo̤öTŸU(,¼iÏâÇ Ž<õÈÕåSU†£¸­äLP =þ\1õw Xgp ¬Áón£E7FâPŠÞ*|KG‚ÀZ{jrauR«7ªÍ@{ª“ \*ž 6åk4¤§È»lZ¢¹7FwñfiWyÑØóQrô°0LªáÈ…ûxe‚x Ñ—½d¢ÀxV ¼žÉû¡Ëê¥&ÿâ¬Ö•µ Ôñ†sv:´í<9réG³ÛÌ^¨éV}å„2ak6g`Þ›ÎÚ ¾ o†Ê “3xЭ¾rB5¶µN¯†D›Ê„„O‹lÚªÊ OxèÐÈ»jý>%­w±}lÊô±› ¹vqW9#¦Y®ò]ågò/k ÖÑ °&Ψ,Oùªò3X=¼¬×ÅÏôQ\;£¾Kúªò2äS{ÒM_D4ËU¾«¼L@ÏÓZ/“£è”«Ð,Wù®ò2Ñt%‚½Lþ½0á*4ËU¾«Ü ÞTÁº¼aßL¸ˆf¸è»ÊÍ$ÜQÖÍ$ТnÊUh–«|gÝLès®ÓÍnì«eOª\…Æ\½|gÝLèAßúdÜLv¥gÄ´Â%Õ>¦,QÙÁ8ÜŒ¨9 É:!ªÁû2©OßcxæÓ×.¨”0«Pq?ø íÌ ¹Ô–q‰"ʳ~!öCxƒ*_)?„w#’]zeÈqLÜKÎçÙ ¹®ÏB/ä Wȇäâ…°’¯vÑÝàçgiŒ]ý SŒ/$#/T(5×/áÑ$› %P*%F¹·!ùU‘³M°Xò–«|Y¥aƒ®ÉÕ¤Gæ2=Ê<âeÃÁfk8kt(’fÛxH™ËU¾´\¥õšK{d.íQµ Æ]<‘P@yz>›&oK:g¼­h¡å*_Z®ÒzÍ¥=2—ö¨:ç…6‰ÄÆICí¸‹â¿Í–`yÊw–)7]3IoÌcz«Â§×v¬JnóI Û3@¬Ñr•/-Wi½æÒ™Ëô(¶ú¼¢PXr_‡’bÿ&’°C°<ù3Ë“®y¸/áѾĵÖr}D(àHè$ ‘2I\’å*_Z®ÒzÍ¥=2—ö¨ï¾9“i|¶KÇÛÜÈ š(ŽÑr•/-Wi½æÒ)PÚÅÍö-_5#JÛx\BU’œ± §Æ= }i¹úVï¶q[Ú#š–éN’}‡?‰ÔÛôßáö/8ðr”ÓèÌQÎr•/-Wi½æÒ™K{|ÙÙ¾ÏùÞa)ïתã}}*êÚùñþדj ÑT­Z¬d³TY£zK«/ÀšÔäâ_ý%]å²3°˜€a\ç‰ÅöxÑïîËnåèEµ­$yÇõTõñòO›;ª>䃩£¸ëÓf¿;–Î*™]÷¹NdâZ«“Û¦È×i-õϵR4—¤]®Ïg«8kakáÄÂÝ'ø—ê•äWßÓr\ÞàéÈeŠm±W~clñê[«R)J+œóZ~E^‹$õFæÂP¬­þ:WS‡G Nj4Ž…âøT#'oÿÜ—ª1U¥†ñÄ¢!.s…)×&ÌŽ^œéB™ÉªÊ•:;u±Jû‚KÒ ïéêB ó«SZé‡/O§O´ßÎ¥%E4¸ÍÃfK•û:ã^©æH§ÖÿW©ckÈ»õÛíøÕÝæ¸¦’í1†Øêº“›Z¿r>’™´b$^Kغ |ÁLê.¨ï5ÕlÉòR=¡r„ë»»¯îÆíx*u.ªч‘‹PãŠÖ¸)Àd‹OKYc£’»í'êQkå¬ßé›ùí‰ ¨6‰f·-ê2¶*Ô×[Ï\5G¹§^ê «Êþ–âñR‘¼Ø²ÖJ9`£X“Ÿ¥°•êÖ›Êb ¥‡ìo0¥ØpýVŽ!›…ªhZš{ZŒ-ú,.½Rxût¢_똦1ø[~hê5ÞªhJfƒ˜å2_#¥—¤èq»¹]s¯ª]c*•ÂmýÜM-Õ¯«"WoÇÝíýÃúðáÈ0mT~–’‹Íg³.j~a|KÞY}ÕŸ¸Æ½)%öO$SËï ªŽKIWÈK:Nº²×¢‹¥­Öè^¨«Èåz{ýq–í’å˜:m*ÛR©ïKg\bÒÖÒ|Øßm¾ [¬ƒ-zUÌBr3¾;û‰iý·*9T(§”•h`GÃfeŠ…©Döoãá–~â':ÑÛ™·¹…aÿmÈM^®q¦ÙNÑZ°îŸ(ÏÔ8lD„´‘~DÄ ãÒZ¢K­O÷§Óã¼ò÷C¨ù¿•djýuf¢«Ù-þT£7å¶æu°¿ÙŸ8+ôÓdÉ™I*hJ>^Ëü^+`ÈU³§ h]èá‰k^«òIžùõXOø‹8Ìgò\ç­½MKÞ’·µ³)‹[U^çúWR䫸7¶‚°ù]hC²T»ÚAÕ ΰPšQí†-a.˜Œ|7–ÒrUqw­u/UöGú ”ºŠ¬ºκçU‡×´rÂôR³Þ¯·%m©~ô¼ÙïÿDj«>㵺d®?kÖnn˜˜±)ZÕ‘VO *”ò¥¬* þöÓL[RJ¸ÿ§N¸wVƼmÿçWÿíäõendstream endobj 99 0 obj 8616 endobj 97 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R31 31 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 98 0 R >> endobj 101 0 obj <> stream xœµ\ë7rüqÿŠÁ|€4în²ÙÍ|³ïìD¹Gî¼:8ˆ}0F3­Ý‰æ±š™µNùëSE֋ݽkkâÀ°%×T“?ëE²ÈjY/*ü‡þ\ïoÞßÔ‹í[6UÕ.>ÜÄ:.»zÑv½_ºn±¿‰..ûÎ ewÓ ë OÓǸlÃÓÇfÙ7ÁðÜÞ¼_Ô©oþc½_|õúæ‹o]½ðKßWÝâõÛ›Œ«^ôaÙÅÚAaC»x½ÿ¼úíëÿ¾ùúõ⯠2TM½ì[’) Ry¤ò0HåùTðYÕGh <A‚'0› 8 “(¦ðLá˜Âs-Ì~ 5Âô30} Ëޢ̒9#sD渡ïA{DfvÎ/›"Q FáÂ#(…çZ˜]½Œm@˜ý Ì?wÁÂ$Š)<Sx¦ð|"̺¨;l ]v•G˜u5ÅÙ5ôì N¦(NåaœÊÃ8•çJœôåÛ¤—õŒý_D„¾òË!LGB„ºËªS޶ƒ!¶Wø¡–Ý´èþøÍ—~¨®±uo E 2E1 E@j;W¢¬+°`PÖFµÃö;‹“)Š“('S§´s-ΦǶ L;ßu‹Š`‹P&Q L¦(LiçZ˜è†êÆât'*lÛXœLQœD18™¢8¥kqÄÙZœÖ±×¨ümkq2EqÅàdŠâ”v®ÅÙ#ÎÞâl ÎÕ¿- ¦N¦‡ÄÁ©í\‰³A3jj‹Ó†¡Õ?X‡$ÅIƒ“)ŠSÚ¹'šQiGʼnúï¬~ EqÅàdŠâ”v®Å vTÇB?m¼lPÿÕO¡(N¢œLQœÒε8â,ô3ZœÓ„—)Šs0™¢8¥kqBVŒyGéæŸ ˜u„àO‰_§á—vˆ½Ä^» Ãp‚«yÄ_o÷Ãâ‡Ï÷ÛÝn{ÖÇÃæüÃo}êãâ]´’™rì??îß §Åñíb3œ×§íÃåx:#‚—¡‚<&@oàÿ¡µ×›Ïóîý«Íæ7ê—`Ò .uê’Õ*Â…€ÁõùÆÁ²C(>ÀÊ ¸4žnÙ´E„yÕCzÝ"xm@¬zÔm›¿º5mwݲNùPb·t>sÕ5hX4¢ƒ´0E§LA.ErùœT´ïˆ ”3ÁŽ\ ¤~Í­k—u0¡ V m^“ W…B–Q#´Œn À¸ÖxÃÀæ çË\ÐTëD\È…ME#À[#Bh? Q) 4Ä&B[NäŒLД…äk+©.ÍFX¶IBõfG2u FUºØ£Ÿœ¦QÔ p½˜ìõs3ì†Ë *ú=äI°Ðy/}U/þßnŒ¶âb5Úš)Ïk+ˆÈµ¢­0øK]h«ƒ0ŠUiW5yét  2ÕǬ£V³—Z+º-\ªÛªàgÒÄ«>Âl5aÄ•i–+Wè#þÖ8£øyFL™f˜òW…:ºØ“¢±:º®ÇD¨äÊ4Ë•¿3ê Æfé‚QÇ`õÔ<™bXè£BAŠÑº’îú’)“,Sþì …u &ˆµUé8VØá°z³+6k* ¿•)l*P K”Ba!²¹Ba[ÆÞ(lA¬>Z…­Áh½¬Á5 ϵ(¬ºÙ@»EÖÍVÍØÍãf¥äÊ_Êò®R¡üÜ£U~êQG R‰Åн…ÝÏⰺϲ\ùKË•/™¤?æ1ýñ’$‚/ðµõý$ÇmW˜O‰˜‘Α±5úÐ0å¶Ë–´¿l¦;oÐomÁì`l¤Æ EO,WþÒråÖK.푹L¢u0áir”âšeí ó&Õ4öm”U˜òw–)·]0iw̤ÝI$« HÅÎÆ¶ ¢‚peW¡¶É®BlÓ0åï,Sn»`Òî˜I»ûD§nwš*0šVœÎf{{Úµ†ü¦ªº©ß¡ä (~'Sž÷;0)>ØÕyLÿ+*W…´fœN ¹S埈’Ù4dR®–?S\/þ}ɘ‚ÍVÀ©ö‹a¡ºÉñPæGMŠõA¦›g𴲨E“›©@Ý`ÞŸll,ÉýŒl©IRøºžiq4ûÙ¹I#fmA@Zòô€ÅfD‰F”çÔº‡HCüh·–~>` •þf{÷xP™Lqc&X¯Ôî_VCB+´×Hä¼+Q` –¸ “Úixÿ¸= üÁeîüÃÛã)1ƒ ¤Éaµ¾O}W†ñÝûÇá15 á ¦„ÈLJᴺl‡¥˜ž¡L1µŒ¾Y¸’ü]@š[ÃïLQ[QÞOVð²j-ϧoÏ{X@Ò‘Ñ™£ôUÓea”LQ”ÊÃ(•‡Q*Ï•(!a‚œªI›aS” ,b´(‰bP  A)<Ÿº7߃Ctú‚‘æ­†)LuJÉ&Q Lá˜Â#0…çZ˜ÝlŠõŒ4ÛP§ÔZPf‚É‚‘9"s\‹°õKXç=ï)BÜòÖv˜b0 €A)<×ÂÄ­¥.ÙN3#È.Ç’$Š)<Sx¦ð\ 3Vˬ•n*̶Š-æjŠ’)fNxäðZx¥ò\‰²…¶ŠæT˜mÓ9P “(¦ðLá˜Âs-Ì¢M¤sá LãÖ…ú¼œœŠPZGª)_Áb2^ãÖÛ¥óPº„wr‘'â2O·f¦@>©¦L)®Ã½ŸÌÕ7©€‚æ‹Aª&íÉ ßêW1R -³sÝóvlÄ9M@®Ò\£·F9`UšÂ Vr®AÛ@¤AºíiTª€ÜEø “Ö»Jñu>çB.0JÜZSSÁCß[ùä´K¡›¤wt¤7ü4ž5Þù>²ãÜv>æ";&‚I¥cÇ:šÕÝEP‚tlÉJ«Él•Bydĺhó Tï|&Èq<‚ag‚JÑÝN2Uø©ËËl²TèLÏYsf’eJ_YK…ñ9c© +¹*W¦Y®üZª¶Ä–Z´D<ÜŽòh;l§¦¥l§¶!e’¦˜É´D–š&¢éŒ¥º®¿®G\™f¹òwÖR]šok©Ð£gMV®L³\ù;k©@ÃbGµTø¬ÒÓfÊ4Ô¿2†j"K-Zb&iH˜´%±Ô:†|Ê,–Š'ôu?âÊ4Ë•¿ûô]Ú#’k=~ýÌ6­ŸnÓþ)˜ql3e8†]ú¶1ßB:”hµù–Ó#܈–ýXðçÇ}ÞÑõ 6ÜçÞ|LÃØ…÷üñ|öiï¶[&V»]Ú¶}‰XûKH°!˜áoÃùœ·†!×ѽåy·$Q!QV‡ í6·²)ÜÖÍŸ¾JÝa¶ÊÔý°?ž8Ðþ¾5ÙÁeÊéñpØî'ª&s~s†¯nŸF\:LÇËo—GRG‡Ò„„³ÁŸ¾:^îIž*Žýj}¿= )ÁÂb+݇fÓrãÎùÃmcY úÃ6·Ç‚e•çÙi£.wÃê4Ç¿~ò^_±Ÿþ¯Û—yFê[…q€&¤¹[½Ù^:gö꿾ܣ"¥p›ÓLîió‚Ô+Jó:[z6@)'©’»ã_~Ú®H‘Ui¥KLTv{^SOXC#мº¬v w4+IyVxEThþþ‘úêŒ0dÐã5Kþ ¢_ß/IxŠ÷ÏG:ËÐc‹#Êg"†Ëiõ6±:3uŸ­'†ó!U»JZ¹Ay8 gˆçÓs‘Mc‹“e¦ðLƙǓPFcF« õ¥ç¾÷P›i:æñÕÆÐô[UÎËp¾œ“ ›`Tâ›l“ä©èˆh¾-ÅñaxC^ª2Z¸¾^DTÖð=$î(âþl—2UOŒ²1âÝW›¬°xò.Ým—¬½14ÅZ×.ÒY•îÛÓq?éjîsjó<œ~JÄ€ërñ¤¶Ç#3¼=Ò:3øóeuº°Û³Úμ(Î[vx!)X;ì~{u Áê4O²‚ÖŽ,ËŠùÝpÀ£¼ü4Ú‹ï“M…‰YôÜÁ;œrÙF¢¤;­('ãWÝQ{8ÿñ‘ÂSÓX‘¶zêÔWLSïžñÑ*Psæ‰^~O–Œûu1|L³KÌÿ‡~®¥ß·;,=xA. z¶›ûËåAý÷Žó–„wÒÀ’G×4àà!¯÷7_½ºùâÕŸ—ÓãðÅw‹ú‹ƒ¿úËïõÍ«ß/þéæëW¸íñ) Ö\ƒ€¡T) Ïžä òïû<åú½¿k”¬=ÇÍNrZ¬ÞOúÌšýÉãyØ,IU^ßoϤÝÏ;à:A‘ö&b9€%—Ħs$Ú'áÙ¶ÒÿöíÏäÍé¸:_FÀ—¸–l5¤s.ŒT|pGØyC&X½|ý‡¯&>‡ÃtÏßQ°29fN°oüÙ†UË 6Çõ#*u™f¸<2˜½v”h¨Ãc™‹%AôENu0æŠ÷ÛÝ@BT߯À-ÿäÛÚ”\ÉÈ*ò×GV'97†ŒI0{c”ìÁÊ%ê|yhy¬F3€wZí)E2éÞÓÊ¥èQY)ºÖcÏY‰§—Ý'ÅÃð0ñN”8Õê9Œ/a_súHRjz3‡´GHòÇx/¼c’Âv“ç îmª_n+¶Éϲï~4¹ÆGòãL ÔØúŒÎC¥>–™uÝ æ^ÓÐ`š¨ðÄEcÃo;N‰+œ<£©»£&£<§g2Å É÷nKáÏJ3·Õ‰z 8»p)óêŒ+8ß9cìG]©U™/5Ži² æû„ÜÎäCԷݯrL3VFjƒìèC•·{ÎGí)éÂík…=—·j…ëuy :Ĥ+Ð'q˜µÑ»¼ÎsØ*k»,Fº±•µÉ]^;Á䫸î†iZQ‰:‰‘°òl<ª¦6͘QµfTékÖÝ©5ÕâѾo¦±3{«¾AWR¦„¿ 'Hö«Ã:õÑzœfjgë'RUe­ÖšKÅYÚü%Ñ4W±ÛÉrÐEãDÏäœ h8ô¯³AÃQÓ‡•è Ç?–mS¨‹SQ¿›ª”ì’ØU/¸ X+äÉnL,ýáówwûã!_9rј¼ÉWýXl ¸Ü^P?Â$Tõé¶™I¸XKkôv…6“ÁÛˆf^Ï,8ÕË<4›W˜˜ÉQX¬9,ò’ÀlTEclµg—Ö͸9Ån©›äðÖWûã#­„:j/Ù4&|ò&—R³ŠµK7ò§6?àòIJØÐaÁtoï5:‡ûbÑ<<ÑbgqW¯ÅÊíâq 䣉k§p°ª««'§”óÌhQÅ A)<‚Rx>%(#^:ˆIWã>†©U¸ S«p¦ò0Låa˜Ês%ÌYª÷qT '0q·, “(¦ðLá˜Âs-L,¥æÊ„)Ì´sÜ[˜D10…G` ÀžkaâíF5]“ßé”ü’‡€dÁÈñÊ·>¡¾õÑÎ Ž®q D¢ŒÂ# …GP ϵ0, Û||вÃsËÖ¢$ŠA)<‚Rx¥ð\‹…™®x[ÕdŠ)<Sx¦ð|2Lˆ´MñlJ5L”$ŠÜVŸ Q䶺¶ƒ(×xLÜ*+%1(1v: ’Š1 D"(BnãZ€°TécñŒ‚­ãr=•(F¦(H¢”LQ˜Òε8;XV‡gQ·é«¾)ó¡P¦(P¡PmçJ × å»)Eݦw.U€(N"(ÌL0(‰  ¹k16ý24Å[EɦoûÑc$BQ”D10™¢8¥k¶XõW<öP”l­)Š%ŠÊ*í\ ´ƒ¶ãøµÚV(4Ö! E€2ÅøM¦PmçJ m…W0 œV sK?zæcοûÑ3Úε8›8~棨Û{ø¦jÒÔÿ¡nÓÁò«êšôÎGßSÝæñm*ÝÄÝÆ¹úMW‡üâz=ú{òw5DøÑßÃu5œ·A¡­–Í3Eœ¿ž<Ô‘µËÞûùGGæ‹8»%ôîæŠ8Q~©l®xÓõ¡ÂÛíúÊ„‹éµ$óÊ„Ææ*òâ¢ÃÝ›ûÀÒ Ç I`ŸË‡):†üHºrÎqH2yö÷éNsá\ûØc]ñb=–X…Â]<µäÆšMé"ûØü¢Ž|òE:.¼¢P %sñX[X»bôx¹6WÀØSä}*¤VÝ)AÈ ±P¼<íà¼K,‘ ëºZ#Ý[•¯oò“ B$Ô]’Çâ9@žáͬܚyÁG/“C€âÜ|Ë\u¹0sxÿËtÞš mû\©”.r¤CG³àiG®˜Ð¨"ܪ* 6šHÜÆªçkòÞ5¹î“ô™|ªÄR º5:„ÅŠ}Q¼â[@ØŠ¢!Sä÷ :~ D•¯óslÌë¢ÇuZær¹´ÕDƶðÈ¥jËm)EÛj‹EžÚâ=`“¦¹ÁuQcž“‚¾&ã"î¬7…¡çg(¯~NƵX0yNfÆßÌÖ›ªëáeÔõð‹2ϸ1Q6!CáL]’3Eò:!HbÂ¬ê ¸âT]Gv'ÆudSøuBì^¬"ÇaZbÇaZbï"-m‰Ü†i‰Ü†m‰™¤%æ±-‘ß0-‘ß°-1“´Ä<¶¥žŸH’–ÈmØ–˜IZbÓÛº¶Ä¶nZ&nIxlKdž¦%2OÛ3IKÌcZ"ãÔ†Ø8MCÌÃí‹•’Ø&?¯£¶©Ïë(¿¯£\?ÿ¾Îl±îàzÉÃï¹jÓvZmú¥nÅ·æÄ޶âíY‚œw|ügªMõò4ìó >0 sñ‹—øÚA—éGGNzz»æ:¯Î´öýµÛ\¦Pàm¶'È’è¬CO$ði·á°Y.ô¼\ëñÔ€ú|èÕ•o¡_Ū->rÔã 9)­fOJG§^“sÒJ딦'Š 5Ÿ“eM÷ÃŽ«vLmç%óõzT\WÕ‹É\ê±+#Ôó)=VÔãÔ—øLø,a© UÒIÁÔwrÐ=˜¦²Š#µÛJ½™”ÿxƒgåtðXf)j=»;­îó;Séqh¶ùË,«Tº”':,”ͲxÌÓÕ¨îòLçX¦hó$ŸµÚ—ÕŒAž=½[À$Ϲ8ÙÛL–ø´4ÑöeŽÁ#˜Ùcðê—„?yX;s¶ßˆ ΘÜtjÿxЃfgºž-»š«ÃÜo×§ã›á°¾Ï†‹A­Îg2Üýêô.ëUá˜^ßs•Œ©“Ok2ò6Úó›×Yê¨`E56r²bƒ@Ã¥¼3LÆeZÌÕ¼d}<¥WhD*µ‡ãöÀâÆÚ§%M"ÉŸܶ™«^ÓM°P·Í‡¹-}Ô¢–±Ö§DÉÅ[½çªX™«"Äiʯv`¾¦%Ó¿¤gZr•üÃd*e¨jFŸ4Ôzš=Vö!{9—(¾Ä;[½“¡'+¤kÎ4ýÓTéò‚ffΫ}ç*ù>ÍŸÔ( jŒGx ‰égNÙŸŽO„wŽM ng,ˆßBÜÿHƒM·= —s|¼Lz˜INNÃùøxZg/[\ùK.Ó2ï¢Lzîæ ÖrE¹1çÓôsÞû-ÇUˆ_|lö>¨ÉU"øÄ”ËåËÕÕž¾ËÂ3—E¨T ·ö2ávàŠŸ¦Œšâ—OD‘B¾ì’°zz¿ì=>óo=ž­L`Á˜êwY™Z3« L¿½å6[h8œ&³ø}Ì­™>©š8—b’ašÂ.acji¤ûãfû™ÜÃÑ22-UätoÃöü8‚¿Ä»_u,“™óãÃÃñ4uZæ9“¤ê˜ßn>LRÃ]èÀwTµW^$Nsh+ô÷«Ãênàt¥T”­ò0Þ„>r&BÌ¥0\yõ0ª4kL5cx¦“«œwrA¡ûùbFjµž«·-ݤ­2]÷+®å®mÉÑ‘ÆÛÑüin>gúÇÓö2®"ö&MÝ 9¥º=]ÝTi_–n¾¸h®6Lâ`Ê80¯à’æhÕq.ž?W&ëÕ‹Ó]¸Jø\kh³0ŸäN4.|ß©¡õ…¡ÕZ»Hˆò¥¾§Ì^}3ë`ãl õjýô3 óÉK b±:iZêoÝ‹z"sýeû?SâL)êÜ-¦oRÑÞÌ ûôF±«pÉüÿs§¢MOµEìcéútâ~ûõëÛWÿõµÄúÆ•iÎêtZ}$«T~þâÄö3¨ Åà éöö@†$JË%‚~®lïÑ~OlÏ÷“,<È8uP•««Æg³¬Rc1ÌÙÂró*{RŒ¶žMÊu/c¦J~—}y±úUápq.Ë-þç²~XîÏÉöÇ2â’Púà Ý…±t4eÉ*ÚìŸõΨ·k°g*6c] Zg.mÕpy;¯uìÚËåFͲÓOSåO•)UDÈBøj—ùx|A–ÇÚ¬Õ8r¿æ2Ëiàû’ª`×»iÞ¯÷½*³º| )4w&Oï(n«‚±~· ÏàŽøž‰æu”ÄÚ,ž®®9Í3î·w÷²­©:>MsÇiuÉÁtVÃá—o/"×N=»Ä'¯Êð.9Ú<,­‡ çúí8Ìc.cÂ\ ìíÕユ“ÔûÇ<~H-îŸ÷¸Ä`³+8 zSÈ×bî_šë!«w“É/8 ²Wç²×ìåw;¾û¨Ÿnò½ð:˜.N¼|1wAŸŽ9*žÍñ €eGpZ¹Þ”º©éÌ~õíþ1¥÷&3;+Þ(‡Ô7·ÓMÕȾe´ Ö™käoÀhUæ:3V‚ O±~JYÊz·.ÒHí—•¥\ñ} |³ŸLå÷G¾‹Ôˬ¼æ+:Åž\½æô€—ãÓ›U>WñooÄ¡àMâžõ쨩i~Þ°$Š;TbŸªx§ãš7àÜ\ôÔq?€S‡¡¯éÚ¬&­V'e«ôŸ¿Á¡Î…c§¹Ÿ—FöÙnº 6YVè-’âÕÞg"æò^fÍzÅm)‰jæ^À@{ûöô ßœvðö–Ÿ¸`RºâWP4dbKŠbÚÎܱ"³‡YÆW›}PÖañXÔµY®²×éŸ =ÆÉ¿\Mo/˲tt–M¥¨ÿ ¬?]Hendstream endobj 102 0 obj 7964 endobj 100 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R31 31 0 R /A 15 0 R /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 101 0 R >> endobj 105 0 obj <> stream xœµ\[sÝÆ‘ÞÒ¾ñWœòK”*ÂuØ^”ªˆ’Dí ]¥QQìe +ŠWú‹´ŠUTåbOZ–°"¶”Yee!¶Ü\¼ß%öÝôOsؽ|uñâç,ÙåQ^™Ú½z{áKvee*ÉàÁ!»W‡çñŸ_ýÏÅ·¯vÿ`*«**%©ÄA¥ßCTú-ŽJ¹åK©ÔeT–y'”QœY*“5™*©4ÜFI+L&ïA2y ‘É[žJ¦JÊ(Õ–ÌtƒÌ¬ª"‰+‚L¿‡Èô[<™~˓Ʉ“ŠÄ’™m©â¨“d⊠Óï!2ýO¦ßòd2‹2Ò…%3ß ³Œó¨ÈÄA¦ßCdú-žL¿åÉd•³ bM¦Ž )’LZa2y’É[ˆLÞòd2AkRgBjƒÌ4®B¢A¦ßCdú-žL¿å©dꤊrgBz6i¥LÒÈ’°¾ ­$%¬$bO¡óò)˜Y€uÈÌ¢ÌüFBf+ <ú¢˜B¿â)äcžDa'QU–’Ä$Ž•@”§’JZñTâ‚ ’V˜JÌÓ¨LSÐ-©L*sã”^ÒŠ§•´ÂTúcžFežFeRI*³€J>T¼¤O%.*i…©ôÇhxIøc ?Þ_íSPø5d(ÃŒêÊü ÓHÃãÁX‘1Ö=–i=+ µ1z mJEª°ïþá|¸mÇÝðvw½ïÛÝ_‡ã±mæeˆº‚ŠÒ´$‡€»„ÃöÏ¿: }ÿ“Dâ/’$K£Ü¨ÒhÛ-*çvÝJQÁ©õ/ii®r¸¨J ú\fnkˆR%Жø“!E•*á¾UœÁ¿ÒªDÇFUÙ-©Þ È.@å0§p«Š¢ˆT%° „±R)pD)@„RجÒZA@jVLˆ¦“TE™4*x%pVnÄÝ!Ç*sw¿Ág©Ü®Ör-9K+åWì.ϵT›»I>æÀGÜOª¤ð¼…]* ¤ÄíÁoP/À’ƒXQ ÜOVeê¥bv%¥óÂ$§!)ã >1d•B`—â.£^œf“†…BøFˆ8ÍŒZÄJž9ñ™]=ÀµIÌ®ÒîgÕ¸Êa ±ä»°¢qÈC{ 2›€©º:u#´ÊÜ^D*)"”NÌI¨yfO ¹ÐÅ¡¹ñ‡"hQ¹ÀD^• d ÒY³«‚•BhñÐãôX‹ÐB€":Å]pYH HÛaˆ:Ê´Ðÿa1X€ T‘Q†›Ó³Ä› lÊÀ,(’áÜÓÉÀ>Já¦PJÊœæ`$*%ƒMèÜÂ[ܰ9€®4¾’WXqrN ¥J¼eš]ÆÍ)a«7ÂZGSË,¿’)à ÷F¸´VdÓfS’»õF~#ÌÜdÐ:ð¬Æx’X•z00»àͰ_ÂC’T (³[ó+ «1j2€±Q$ ² &K;ì—zà™®²NˆAH¹ñ _½{nÏ-£ükHy@†àî ¢€_áѽÀ|HˆllÀ˜ïV$æÃÎø|^ Þ¹,e:V¦†™ú” sqf”²–ó`™}ÇÜ€\i%£@ƒH:fªD“U¿ÆûôZ‹}AݪDÛ¥¡íSãÊ,Žˆ+Ëåb[’›ìSà`)u^ƒÎòS«Å.·&w¹çÀÁ°T\aT›ô6¹%¹É=&\F…'!ÀY'E'ѦÕž7¹Ç$¾ÄtTx³Ž,/›ÜšÜe“èV”ðÚ,õ覜‡Jõb—[“»Üs’¬CÒ¹À¤¢„;RÒ.\»ð9iþà5„3ÐIM>‘w¹5¹Ë=š?Hµ¬Ÿ/1¬…»ìcAD¹«¢J¥`1AQF[‚ P[|ø®»;­AmQ€tc&ê¿Ì8°Q\£°Ûü"9ô‹Âp{Äþ„·ãp°‹‰8â~žOíøÖ¤‰OªÝ•QëÒüvl«¦ÙüÖø¨±žÛK<  W?Þw½{mäi¹o‘px׎62¶Çf‚˜ÁÒaÄëßÔŽã0Úx<ƒò;?šÀdmBn»ROø,o:ÚCž§)·t×wwÝ­£ò*/dâº+Ùû¾yn~±V€?Û±ѳ7Ï­0i•ÙWÂî â:ó@w´ÄÁ9žußÛµÔäë¸ÖÔS ùRd©·×+ºL,6¼B^šÜ‘êöî:3éd¥‰8~pTd ®káTžåS;~°‹J¨Ÿ“0ªðï8ÔÍ}çx /o²QlR¡+‡áØÍÃØî$J{YsWóËýyìŽwöàX^ ‰Óå5TO¸=«×yÂÇS„Ðê‡Ã4׳e®É ü:2Õd³~í|< ûî™#¯ˆÁ2`1ŽêÔº=Oµä› ¸ îŒg—þ¥òÐá<£ø¶ÞØìšÓÙ®P“ŵV¦®ê³’)³;0¸¬xä^´`QcS×+´šÌX·³³Ãª&µÆI³fNxûØTÃùh¹—%ÂT?'jãvZ(“—U ÙÇ5™ï<ÍZc)˜sïÔö*5 ÂÊYŸNãð[wì?™ *vûó²á ?rø vðpU˜>Õ[“ñ¶ÚT ¶—+.ÿÜöp¨½FžBD Æo×!‚Q«†ñÝú\iãt `@RgpÊh6Š R™…h]¬6§±ÚãlÈrùÀ€X’ AÍ‘šM’TÅ v€ÅƒQG D\ ¤Êí‡#a‹Ú!ieá*§÷v$¨Ø€€ÔØÖ[b0BˆM´ {~úñæú¿í+ñæ©»;Ö=z½R‘6nŸ6YŸ½ÑðæÊ†ÁÁ‰P^>8ðf|p ‘z\ò©畦²«…‘¸ÿ¼8ì)¤`ÏñÏc÷ÛJ67§¶éÈÒ+#^â糦öŠ“ µ}]˜¥Ò*:ã_­5Ó5œÁƒªgÒû ¨,:t\8~g@ Pó€[™îÛ}L¦S jxßÛaÄ3ÿ÷mß­¨±Èáì@r|B=KÍUˆ/HLB \‘v©€}çØw‰Eyó®û½U!m¤›ïQûÓleйðvõ~ß™“bå™0Éî×=Ô$NˆpÇ<¡0g)´ÂòÖ¹‚LsÚ§Ê¿Š€_ ¿*x( ÀÿìA”ìëy“Óýpî­`ÓJ¨”W€4ÿ<©z*– élÃñH¯¼2¤ìB9çk‡z&2N7à€/øæùüéÇôοäˆëÎ<¯ÿvý£+©Fè °²H‰êz×[¨ï)–7MÒ]'¥ÍOóxnfÌGÀ·²Ö‚¯›ëîˆV.Ó’­ òYO±[.ÌnjÆî4;õrŠ[.BÞm £!ÿ˜UÂ'àmþ“‚ŸúºiIU˜Y>®6bKF‹ öa-ÞF"ó™­§…påã÷@m¿‘ûxaXÛyì(6eê7€)Âû—R „åëyn'ÊçØ›;+«Äµoz­·X¡Eww˜¼¦dçwÞm¤Zx2ÔXtkƒR‘®J-™ˆ1©4Eÿ’lq åBhÁqXò³ßH¿,RáUo)T55‚b¡;ò0Ö÷4[ëŽèÓ·Ç;„uyæ–OáŸÇ#j»|û,ä…8u"V&e#ø~pé8¾hw8õíž©}¯­«(I“îÙè,“Â*·sJµd†*ÆÌ*m6´ð¨1i…&e(]ÊÏ3 %˜˜’ý;)b¡ ûq8(»,Å/>Sã§UôÅIØ2æ\YªëñmÛ€CŸòÎà N_S2I:{çì>ðoT!ÐB•B×2s˜Šæ™Ûì>@`—Me #E¸VÞ+ydón½‡,–¾Yd’ç][üxøK$O¤&§–¼WVˆçÏ't[9'ãõi˜¦î¶ë»ù:ÀÄóãqÔÕ~ r„Õs•})+HåDjä $`yÂE:;©ØÆ §ÎcÓ"ŠˆHò~ç5説ŒV*¬5…zÄMFªœ½€“e•§ì%m“5·Ót‰ª(R£uHφ”lEÒè)§gnW¦$ë2v²%çuíKcÁ…-ÁÞûwxI¶”¶õ kߟ»Ñô†' ÓÎíxÀbYè€åPŠ `½ŠGAïå¤Úz3H½„»ý³ÕBÔ”0ö'N±0î¾};cÀ"2#ª§qQ`‰GÈ7|ܪý]„:¨3+6Õ2ââfñ ¸¥¸=Bëz§HAåSƒL˜Êil!•¦`r!8ư@o˜ ÇXC6þUO³hYÅÝ(_lû>BEd¿žVÎÅ'œz$düÖßjãå/QMXŸ Ž€Tú†˜{Õ{H{œ¼ºf|uQ‹,¤B[:P%NuóñŒM¦Q©e¥pìV@¼LŽ[šÚWƒR§gRö9ì"%-ýØ®:øSEÎQ Cr j_`¤Cj/@Û."-ÌBœ½\…[”Ë©Ðj¨b%!×C‚VÑí'¼Áº€œ‹Pj“"‘ËО¦²<þ²<"SE.,èe\Ðûä"T4–%ŽÌCÙip(œ‡rXݠ̵™™êëŒ —÷ûÚÉ8å0cCW9¬Ôˆ!—qþØž'y\nÓ"<Df“y«€l’<ÃX:€^ó¹Ÿ uЈYé¤Q'­#ÉäÍ!›¡Ùf(d.»Lª²nç×ÍL%j~À’Ep¨/k®ÊøËú MÕ¤ ³ä”tKed¨-Q‘¾Ÿ}{IA¦¾k$PãηˆðA˜í½P•>¬’ô]|‘Nø<¥›®ÝØreV Å‘u²d…"É ‚dâvžÖÁÀFc]tAƒ0«¦ÀÆMDTPÍèÛæZtpmsO.Þf˾q~ì¦r‡u2æ¦?ï]ÄšÛ$ÈGÕk“¥ò”4íƒ1ަÓÒ}~®£“õqÌ=°’$§>õ;ª·-,™Õf9¬QX›wÃV-hL&ps? “ŸÆaÿú§kt4¬·ŸVvü²>ÞÕ”i²‡ü~¸;Ód‰@éãºýÍxžš{_¹av H¯S§Ërê‚ã\9 ˆþÒK¡}i.{PL›&=MΞëó|?4‹rµ×ØuøŽ‚×§OÓÜ¢è~Ä`‘ (Èd¿ÛöåÍ7öœ$ë)gâ×>ÕÕ¡gvœõ°C‡ý7ÏS>è±§{jþ‡ô#‰E—vtí Žù@Ób£6[{x™dÝwG D¸—‡d¸„ÏdœF4X.V"DܨžZÈŽ Wþ仺;ú׺€¼Q[÷ÊÀ%®E‡±§òG&XWßQ¨.ú°ÞO’ó[×,ÖÍ.¼Ÿç®ï~çÇiž,(Hÿo­Q4U»ãtò€|s"N  %»á÷‘Šw\¹*m…ÛµgÒÜ…ï4©7'?…ÆŒÝ]GÓl¹ðQÂeÒé‚·[>9z‚ 'srrœ³£º.Q2Œ-üP†k#NV_Ú?@Ù0Í)àÔ€,Ýô@)Ô¤ÃɆ\LŠs‹€ñd:=»·íH]7†ß ‘ý‘€^™?óYN®/OÔ˜,–ñDºÑ§’9ÕšŽeV@ÒV M-Jq=Y”uó BÇ~Ì5ý"¼v.¯ýŠ@-è„C6P-Víf9ÉÔÅ»È<áÏeóî‰S%nè+AŒJ~AV9®@çj:ÕMKaÃô+?îëE‘‚@4aFl… ñ§ºÓÀ“úY0D0ã×aAnP7Ä?pîKÙˆÌçÁÜå5¤ÒØ‚ðžk÷j"ËùzöÚàÒ¨Tè´ôM}¢bÀ}š†q uyJÑ ó… –;ò]N£ÄëÞ?+$2ø_?\-„noðã7ß¢ÌØ,ŸõsKðJp1N!²ü}Èð±E­ºŽN9orÓìVò•r¡[ j’ Ö3„(5#ÖÈrŠý*Ž[-É8öëºå‡®¡†"{1a²’L²¢aâø¿ „³\_¹;Wªr—ʈLÔ=™ùV<Z—ô¥Þ\æ#l xä¾nYEJVDü i*ÙœÃ}‰œðz{ðnTbf½ž!#[”–­r°xÕŸ­‚`BÄfÆÀRáLäC!‡ü@5‘Œpø)¯DægÂøM/]YSUÒKŸZÙzi1,°í¥ÍßryÀKÛ™µuýIB¬HÏ‘R¾Î]ù3ÔPôáÖ†§Q9ñÖ§ ìgl¿Ø³±1¾5æ!5d(®Rί ç¹§:APîõS©7ÞDŹ>¡KJ|_§`$¹u8åÓúÕÙø_8wÀÒ°Èz@íY³(¦Ôëð¸I—/FÙühYJotÈß“lˆ_³Õ¸¦a&?×q69­Ê6^è›YÝVa"©®m„”«Ál» ·æ3Œ¨£¨ÎH2O…¡Âü .ºøz'©2yx¸9@’)¼k±ÎçÕKñßùH§8ïÝ{t ËA­?jk2€þö¾Šbc1ÇËÇ’…çzš|qM-Ì1è}3ØòÓï6¼6ÎQz³Ù’¯Ù e®ÌîmÌÄÄgôÇnn)ãr‘4\© ·žxÈ…›^ñ’­ä’ã¾EZ ü÷ãúDŠgâðXˆwõêi_±ÊsÅC~.·N¡Vƒ5¢Ø*ª©…óÈ­ðm|αñùæ*€wÐ\‰ÖÿVð…êñÿÞî¼Iendstream endobj 106 0 obj 6622 endobj 104 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R31 31 0 R /A 15 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 105 0 R >> endobj 110 0 obj <> stream xœÝ\ërÜFvNé'Ÿ‚²–S$÷‹Å¡(y—^‰’%YN²ÚÚÂÌ49°0ÀÀˆâ¾FR›×M_N÷9î¡(%®J¥\¶,—îsùÎw.Í0ˆŽCñü¹Ú}8úpÉkúÕöøñ›£Å«ò¸ ª<ÎÓã7WGêþ踈‹( "~qû°éƉÕëoßüz”DA–U¿çÍúá{q% ª¨ª u…±Ý(.Æ‘úûÀ¦ýÐ5ݵ|¶ ʤ*ÕO^¾xöìò­¼\a¢_yöL\Šò ËÕ…«~W2õ×iSOòïe›‡Öl\ ÍnR·žFeDYu|Åâ§ûnjZµ¨ 1kض—à ‹ôÅî†ä®£˜|íjè·òÉ8HÍÅiÃÔ2É'šnbåz“8à÷ÂF¦öì6‘›¶Õ×ãJl./oåIî·q‘õîTÊ"K/„,¢ ,ÅOvm³ª§¦ïqW}&> ²z¯*ƒ¬òK7å°ìåT7ZeRItÇ*û3†‘ -Öê“1¨`±ë[¥®\¬îZÕFe>ÒõÓl±;È2S&(/UÄv—ðU²›‘µl5Án² 7Âì»ø8,r7°U»_ÃvÒ<ˆŒ´V=w–a¿?Ìâ G\ÁÒJsiÓ°Á±Ð7Ø"£º«‡Õ†kOŠ'I‰ùûA‰í†¦š©ù«ÚPš Û‰|س=x …«` {ÔP3*K&‘iуôJ³öaßÈ‹E¦–;ø\ݲnç!܃ !H­Ç÷r•ßêÀÊåRÀi´Ü¿ܤD1b*L­$AÇþ4±N+0)ˆµ7¥–lSƒ_ |ˆBh8$J÷½p¼åˆº=QpU¥Âô¶;xkNÔ;îw»~P@ÂA9šo5v¢ÝµáÆ©dœIÑíûÎØd’[[7㮞VøAjÖ]&MÆ>}Ñ€ƒÜ4ÓFé4,„tˆ +µJÛ[¼Êùõ*Ëb{ÂãSuE.îªÀ”ÐQÎûŽûÜ[˜…­S}+wš ‘r<Û¤“ëËR±0£MÝ­[­ý’ìEª¹Ú+këTŠú¸Öà÷tûíR¹1WC9sw„0Dan¥sÀI¸‹Œ€W ÎzÐêY³9ÂËms”]ƒq×&ðÛÁ’Sa’õ])LÐn¶jX·T@¾U7&¸ncûŽßÅöVc³OÇ18¦7”ÖäÍR­ )®3'¬YÛ8.ðQK&Ê ¶Ç†[΢ Fhºo6 P˜ …ïêÒ®åQp‚ÐF¾-€ÜxŸKJYšë¢;®œZŽiM  WS24ˆxn Q"ÚÒú¿fÁ ^€(„ÂÓHÊãH½l™Kˆ‰fi‘Q‚ƒ«:Ü—´ŒDâ¤^Ϫi›éÐ7u¢¾%žX8D!õ†)Š`EtuÑz¬ö >ˆ6V‹¸ÑÕC,™œ½¼€¢KhƒàCåó;ûo”G„ˆfé)˜[³Ýµlk,„cRdT¬x‹äÈÜþÃÀØã×OT˜( ¡Ê<þ°ê·ÛfÒ˜‘׫ Ë×â u¸Ûm­Öa±Ûó·¯Á%Ђ'¾6°Ô*p¯PÁ3NDZˆ™YM†a öUÈ/i}½øŠreCݶ'ðŠÐ¥Ü4¤J•†ä¸çrßÙŽ€ôEæÈÍJ³ðÛO;HU‘_ϤUÌÀ¨ ”Ë%Ý ’­8”R’vÃéÜ µ$ÔMÇdáœmìÇöÖñë«~¯V±‰rH“ J:$¿…{Ñ zßк5Ê»-Û±”¤œõ­Ë~š8íc«÷`“U†aX™|Œ&ïÁõÆïÓ6Î]w+®…øšõGË4‰\BŠ3 E 8uI„eÛt4¾ˆoÑ)SEÖRï§k\çl4;‘˜fެµ©™èäÓP \9†™Üñˆˆè{É`‡–Åèd“o;‡¬ÃΦr#|¥áˆÅA 5AŒmý+¥ Þ]ÏyUê¸÷àKjyVF~Ö#>ÿÂë<;8UX7Ã>KÀ“ý§[LF²BõfR“0諒V°0åk¶Õè‘RäíØÔ1Í2 ÔÖb]ÓPw£àþð’TpU‡å¥ï6§iwü%{0˜ˆ èê­ZhJ 6—é–®Ð0¤Œ4æÚÎ¥ØiáVpÿ&:‘g$f¨BÄb…Q“–„„Êk~–[¶ÈA#Y½îw1y2–̬b©)#r”œdó°|xV:A!'X ÙÑ,>ºÕ„¹%®Ø¨bVL¶äÉ.Ñ›S-–C¿¿Þè„Ñ*IA®VB²Z—lž‘¥"tY/‰yF] @|B0ÂÄ\s¦Ê‰øQ(ë[5×Ú†³¨ÌÄ”ôP› žbŒUÑÏåÛŽ¨_ôXÆcn™€†ÓÃÐŒðjÎÈcfÇ‘Y)®”³·mÍÜ-`†%È(O©ƒk]˾Waß‚`(†V'‹Œ§à„²,§µ4õ«¨åô¢±¯’šå8u 6J ›Ü˼ýجu8G¹Î©EL¤ ©0Ù<¦Æ…Ä_ÿ 4G*ä{§t'˜…þ§GKÛ-àKJ몛à´!¥B>˜ ÀG¹rY‹£R7Ï9BaŒÄìÜMÅ eï9£«,1ñÑuКòFi1q¡ÀÊ6d];P™üâì8’¥#htœŠÿ‰9¿(N©ѽøÇGÿôý?Ë›ý­Ž8•é¦| ÎÅ#ã.¡À&´ÀÓÁ*ÅgTI+S9¬©ÝÆÊ¼MúžÏB-©ò16»©¹R¯/1ÆpQäŽô0 Ö:;A¬\mêîš­¦Ud_¯Ø®åé8 `Lø¹Á¤‚!‘hŽU2Ô™d{Ç}ªV”Ã)~[×¹¯§ÄÍÁVÞ€½Í¿½|ñ䩳^éã„h½ÛïÍ Ü^ 0#Ð!R!GbòºrZëzE&“ˆÒÙúd ÁÛB‘ÞbǦ7¬^Ÿ@ÐÆXеŸŠý^qkg<dC{k´SFT;ÜPTXSy(%)$_$m*Ru¦í+• ÉË4¯ð×ú"ƒ”X3B 5#ÀPn6YåéG¡8Î.^‚“ jÐ…iRQ1=5Ú 4¾¨5!–u¥RbÙ´o’¼Enµ ý•›¢™$‡Ä¢ À5.7„30.dhR![l’‡ŽÆŠO@¡h–¬«—¦À{ˆ-º’š~‰ö¸Ÿ[pÖöýÎѧ bšl(ëQDæ®òlž/l„(¤ëìÓp¹8'ßöt¢1‚Ì í³ê3Ä ô°ߤd¢³ïØNäI1Vë›ëhÙt5i&óþÀ¼òr¢%ÅÒFÊæé›ãŸŽ>ð ¤b]Ê£\”—‰ˆšqÂ_ÿ¸Ú=¾8Z\=U´A»H8·Gê­54øtV)*«[ŠeÍ”¨ðçË‹zXÜ=\L¥e“ ñªÞ™1Št8Í£«ƒtfª Þrëj´Of¤Å~ïÌë÷Gƒ ½tÜsÃ’~ojø.Ä&ÒšaôB=@J§R–Úµi¿­Q*ÉI ½Ñ„Ô€:0ShæGIißíRHa2`\dæn½nT­Iúw’4Ìs1"áÎ'^7àŸè£c¥š17<À)m•zZo)!ßwÐÍTî°õµäeÙÅ”ð=#ÊÖÀ(°m‡æmÛͧ5ˆðéLëºW&O íÆðåØRPhUÄܫր=®Ð¬4â f´õ-œ«'úâùÄ„'KîÈ¢)ìXi#uæØufÍ<Ü"ÃAO¦¬pƒ§ñ¬ ™ÂsÏ',«~qׯáÊÏΕgžªä&³D«øÑaNP'C‰çôr…Õ¯é ˆ‚€5NSS<²‚_z S×ò‰{¨#%øu!; Lîa°ñÏpΩa·6'U0©XÝ6ã¤c[ÑÔLÔêUBiÕ‘/à˜U5³-‘—ÐÆJÉ@ØŠ?Žj¼€6} ãLfGbhY]L 8¨ûóë§PúPcx†·uû: E Œ[ƒü¥¾,C0K¶R¼€ÔY6>Himû¼ï4‚ÐË Ô73R«ÿýîáå|4r6„÷O’¡Ρ1öÙ™;©,òî[݃L@i•P€ªøÿTÉZ¦öØ{@•º¿ø²¤ÿûÈb­ÚÁ”˜bÊhrðZ›ûZà­¼7XðNÎIIFo6çÊⵃjçê âpžzªî!`|?´Î険—- zæ‰AO“íûލeÞÃ7åá.—È/€³VõŽ)ÍŽ¶™L…öóƒO™¦Ö¶L$ÍÍ%Ú¬¤õðYAxuÖ€Y?Gf×iüBt´òþ/ÅÅ’„ ‘ëb ­$ð~x)¸É»oÒß+±ƒ¥éq–ü-Î3e7e¢«þ †øç7 4[3[¼*Rº®”4ÞLÓî»ÅbâÚÛí—c0^7Áªß.ä—³JÜiú ÐB[L»v±ºVmJ¢Ö‡Ÿšnqͦu¿ øÏ¿—ïÐ7h.Þ·í£0ÏÂß­—8Qøûß]‰ G‹ý8Ȇ´‚²©¶ày3¿qQÿEü—ÿ¥P[û«¼U`âÄ’*‡î›‘^zé‰Ñ¤{Ko˜KnßIÉÕËØ+=±ó åþ¾xõô‡çg—Åâ_ΖñËúš½mس÷,‹âijEݼT»Ka§bžN&ÊÜHùûƒhYhJ`Í@.ƾô-òÒÙ/’x£¾RÎÃ$­„½6#oÖñü}×|7Õp©ª´²¦ pθ‘@,¯³ÔÒßÍÍM ~cÇõÐïwA?\ƒSl»´M§ÆXRy¨\ó]î2ß?yqþ(ä0ŠùŒRæÒƒóà‡1ÈÜUÙg)¹70xÃ[¯ÒIÌxá¨ãß¼ï$gß”Bik癲 Aú¬Ôƒ$ý¢p·,쩞;©T}u—˜Ìáµ²XQ"Ç¥¹ß;@ ¬+ò¬OBYÀå½h¤8É@ù‰K»ìQë-œ)«™©/yÚ|1qíe?L†TÐ_ý0õ}« —ä·ý˜_ÇAâÓ,§£óàuW··ccŽ„”4×òÇóØô¡H<§+³â99˜ñÑåsñ)Âæ9Tâ2nç·eúE*¥$të“ÞEõ·¸J•^ùïz|-žTT>¤X‘Q†Q„ĉ+žñð4)B1å<£Ž›ñð4‘3´âA(OÔ‰"£—v&)aE„{*CI 4³«-“)ÅØ_M7"ÏüE¼~ác<ÜFæÉO™Î“ŸêÿµXÝÞ¥-«Ë…x@?ôô8ÑT„hXÐæ¡¥õb¯f"7-5G¿Œf¢ðϾݡš9›Eèß:W(trtÞûĬK ñ÷lÍät”çë+„ä%ϼ_!“.m{jØärKäCˆ»5&2ë¾`\æê‚5=êñ‚~H'+š…îŒz¦ZµyóDuÿIE:[d~‘DÉŸŽþùhendstream endobj 111 0 obj 5821 endobj 109 0 obj << /Type /Page /MediaBox [0 0 612 792] /Parent 2 0 R /Resources << /ProcSet [/PDF /ImageB /Text] /Font << /R74 74 0 R /A 15 0 R /R9 9 0 R /R8 8 0 R /R6 6 0 R >> >> /Contents 110 0 R >> endobj 74 0 obj <> endobj 31 0 obj <> endobj 33 0 obj <> endobj 15 0 obj <>/FontBBox[0 -41 84 176]/FontMatrix[1 0 0 1 0 0]/Widths[ 0 0 0 111 0 0 119 0 78 0 0 41 0 0 0 106 0 54 0 53 0 38 0 36 0 52 0 45 0 60 42 0 58 0 31 37 0 46 0 80 100 35 40 110 51 32 91 0 0 0 0 44 62 39 61 0 0 0 0 0 0 48 0 47]>> endobj 9 0 obj <> endobj 64 0 obj <> endobj 8 0 obj <> endobj 10 0 obj <> endobj 6 0 obj <> endobj 7 0 obj <> endobj 2 0 obj << /Type /Pages /Kids [ 3 0 R 11 0 R 25 0 R 65 0 R 68 0 R 71 0 R 75 0 R 91 0 R 94 0 R 97 0 R 100 0 R 104 0 R 109 0 R ] /Count 13 >> endobj 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 117 0 obj << /CreationDate (D:20010430210556) /Producer (Aladdin Ghostscript 5.50) >> endobj 14 0 obj <> endobj 16 0 obj <> stream 0 0 0 0 84 69 d1 84 0 0 69 0 0 cm BI /IM true/W 84/H 69/BPC 1/F/CCF/DP<> ID * ÀeýòjD€Óä&{¯ß®ÿ ûÃþ¿Ñ:ûïé}ÿ÷é{ïõö¿ðÿ÷ ÿîëû¤ü?ÿú¼:¿ÿiß׸WÿïýûÓúýÿ¬?¯|?×ÚÿWá}÷á÷ð¾? _ß‚\¹ ?ûAþ  EI endstream endobj 17 0 obj <> stream 0 0 0 -69 72 3 d1 72 0 0 72 0 -69 cm BI /IM true/W 72/H 72/BPC 1/F/CCF/DP<> ID & ph$  a ù<+zßO&¡ ҵýáÿ¿÷ßÿƒï‡ÿïÿýßþÿ‡þáþ…ÿë´½/ ±ÃþP ‡‡!²Ÿ}†·ï¸aû}ß·÷»ÿßðý÷ÿïÿýáp×ëü:^² é]K‚ál‹ƒ  EI endstream endobj 18 0 obj <> stream 0 0 0 -55 61 5 d1 61 0 0 60 0 -55 cm BI /IM true/W 61/H 60/BPC 1/F/CCF/DP<> ID & N°œ,X@°Aap`‚áaÁ…‚ …„ .,X\ XAp¢ ƒ“Tx|0x0ø<0ðaáðÁàÃàðÃÁ‡‡ÃƒƒÃ  > Ááᨀ EI endstream endobj 19 0 obj <> stream 111 0 0 0 0 0 d1 endstream endobj 20 0 obj <> stream 0 0 0 -75 24 24 d1 24 0 0 99 0 -75 cm BI /IM true/W 24/H 99/BPC 1/F/CCF/DP<> ID &¡³XZZ ­-pº]~‚Òýzü/Zÿ_¯ õÿý/ÿÿøëÿÿÿÿþMwÿÿÿ·ÿûÿ÷ï÷ïðýïû|=ûß ïÞÞÃß ß€€ EI endstream endobj 21 0 obj <> stream 0 0 0 41 61 47 d1 61 0 0 6 0 41 cm BI /IM true/W 61/H 6/BPC 1/F/CCF/DP<> ID &©|^MR€€ EI endstream endobj 22 0 obj <> stream 119 0 0 0 0 0 d1 endstream endobj 23 0 obj <> stream 0 0 0 -75 24 24 d1 24 0 0 99 0 -75 cm BI /IM true/W 24/H 99/BPC 1/F/CCF/DP<> ID &¨Àhß{{{ì>ûÃ{þÞÃþ÷þðýþÿÞÿÃÿý¿ÿÿþïÿÿÿÿÿÿÿô¿ÿÿô¿ÿ…úÿZÿX_ë^¿AkÖ½a.µÐZ]iuð EI endstream endobj 24 0 obj <> stream 78 0 0 0 0 0 d1 endstream endobj 28 0 obj <> stream 0 0 0 -67 32 -34 d1 32 0 0 33 0 -67 cm BI /IM true/W 32/H 33/BPC 1/F/CCF/DP<> ID &¦l}ýïÿûÿøoÿûßàÿ!n0œš÷¿þûvß¹Ä ìlVÐ> (€ EI endstream endobj 29 0 obj <> stream 0 0 0 -84 36 -34 d1 36 0 0 50 0 -84 cm BI /IM true/W 36/H 50/BPC 1/F/CCF/DP<> ID &£<ðÿ‚z8A£ß[ï¦)‡¨pýÿý¶ÿÿþø÷ûÿ÷ß¿ï¿Ã¿öýßü6Ÿ~Á…ÃkµàÕãþü?ýÿ¿ýÿ  ?ä3|@ EI endstream endobj 30 0 obj <> stream 41 0 0 0 0 0 d1 endstream endobj 32 0 obj <> stream 0 0 0 -60 30 21 d1 30 0 0 81 0 -60 cm BI /IM true/W 30/H 81/BPC 1/F/CCF/DP<> ID & _! þó‚ ^´¿…ÿÿÿÿÿÿÿÿÿÿÿúõ…×K¬èÐ\=ƒÈ´ßoßïÿÿÿÿÿÿÿÿÿÿÿÃýûÛáàɼ<~ EI endstream endobj 34 0 obj <> stream 0 0 0 0 30 81 d1 30 0 0 81 0 0 cm BI /IM true/W 30/H 81/BPC 1/F/CCF/DP<> ID &¨Ì6`ÿ%Láï†ÿÿÿÿÿÿÿÿÿÿÿÿý‡ßì=ðdê…‚Z4,-z×…ÿÿÿÿÿÿÿÿÿÿÿúÿX]‚åiÁpP EI endstream endobj 35 0 obj <> stream 0 0 0 -70 47 2 d1 47 0 0 72 0 -70 cm BI /IM true/W 47/H 72/BPC 1/F/CCF/DP<> ID &±°n|/ð˜AðÑÆ­ï‡ð­ÿ÷¾ýÿþýßÿ¸{ýÿû þ;ׯ¥ÚùÃÃ\.H ÏÞÃæiø~Ûݲ Ÿiû ûnø~íý°þý°ß°ÚÜ8 BðÂïñÃÿðÿþ÷ÿï÷ÿï"Uÿÿbp EI endstream endobj 36 0 obj <> stream 106 0 0 0 0 0 d1 endstream endobj 37 0 obj <> stream 0 0 0 -45 42 20 d1 42 0 0 65 0 -45 cm BI /IM true/W 42/H 65/BPC 1/F/CCF/DP<> ID &¡JÿÿÈL0ÿþÿþÿ!&ÿáà›½cÒ}ïÓ~ñÿ†ïÿwûÿÿ“^ÿÿÝÿ»÷ïýî¿oøxa…Ûû]´q;i<ž ð`Ÿà EI endstream endobj 38 0 obj <> stream 54 0 0 0 0 0 d1 endstream endobj 39 0 obj <> stream 0 0 0 -11 13 20 d1 13 0 0 31 0 -11 cm BI /IM true/W 13/H 31/BPC 1/F/CCF/DP<> ID &¨ÀŸ‡†ûÛß~÷áþû¿ÿ˾cëäÕƒ  EI endstream endobj 40 0 obj <> stream 53 0 0 0 0 0 d1 endstream endobj 41 0 obj <> stream 0 0 0 -67 26 2 d1 26 0 0 69 0 -67 cm BI /IM true/W 26/H 69/BPC 1/F/CCF/DP<> ID &¡ø z<Þ·íûûü?ÝÿÛÿ½÷ßûƒýáøû÷¿Áßï|š­ÿÃðáû÷ûûöýëb°`¿ÿÿò^?ïÿ (€ EI endstream endobj 42 0 obj <> stream 38 0 0 0 0 0 d1 endstream endobj 43 0 obj <> stream 0 0 0 -70 49 2 d1 49 0 0 72 0 -70 cm BI /IM true/W 49/H 72/BPC 1/F/CCF/DP<> ID &¡ Â0Aà˜4zÚÞ0Òo„Æþõ ÷ûíÃÿÃïÿwøþÿþMïÿwþÿa÷þßþÛýáý†»avÓÁƒGzðÅ>Cdÿ¼ßûÿÃÿÿ~ÿ÷þäêÿùÙ€€ EI endstream endobj 44 0 obj <> stream 36 0 0 0 0 0 d1 endstream endobj 45 0 obj <> stream 0 0 0 -45 39 2 d1 39 0 0 47 0 -45 cm BI /IM true/W 39/H 47/BPC 1/F/CCF/DP<> ID &¡Ê‚£Â™‰‡Â ÿ¨0ú}~×öÿÜ`ÿÿ“\ƒ ~RÉÂû܆)Øop}ï¿·ßÃûÛû]‡àÁ‚XzÃn€€ EI endstream endobj 46 0 obj <> stream 52 0 0 0 0 0 d1 endstream endobj 47 0 obj <> stream 0 0 0 -45 54 2 d1 54 0 0 47 0 -45 cm BI /IM true/W 54/H 47/BPC 1/F/CCF/DP<> ID &¤l2Ï„þaÚ8Ãõ½ðßïû÷Þûýßÿ{ÿïpþýÿppÿß¿ûß÷ÿ¿î ÿÿ}¿ß&«¿ÿü> stream 45 0 0 0 0 0 d1 endstream endobj 49 0 obj <> stream 0 0 0 -63 31 2 d1 31 0 0 65 0 -63 cm BI /IM true/W 31/H 65/BPC 1/F/CCF/DP<> ID &¡>0õ½CßÃýûû}þûß¿ýþ;ÿ÷ûÿ÷øþÿýþÿñ"y5_!çßïÿßáÿø{ÿÿï°¾  EI endstream endobj 50 0 obj <> stream 60 0 0 0 0 0 d1 endstream endobj 51 0 obj <> stream 42 0 0 0 0 0 d1 endstream endobj 52 0 obj <> stream 0 0 0 -71 50 21 d1 50 0 0 92 0 -71 cm BI /IM true/W 50/H 92/BPC 1/F/CCF/DP<> ID &£<°@ÿã}†ôöýß¾ðÿûÚ}¯¿ý¿ÿþðÿÿÃýÿÿ¿ßÿÿ¿ßÿûýÿÿ¿Ãÿÿ ¹ÿÈeOûýÿÿ¿ßÿû%{‚ÿ¸ûï÷°ÁíµÇƒXa@@ EI endstream endobj 53 0 obj <> stream 58 0 0 0 0 0 d1 endstream endobj 54 0 obj <> stream 0 0 0 -70 23 2 d1 23 0 0 72 0 -70 cm BI /IM true/W 23/H 72/BPC 1/F/CCF/DP<> ID &¤x/ÂG'¬8ß þýþ÷ÿrkßþãûÿûÿûÿû÷ÿ‡þþÿßþÿßþÿßþýÿáÿ‡%_ðþC EI endstream endobj 55 0 obj <> stream 31 0 0 0 0 0 d1 endstream endobj 56 0 obj <> stream 37 0 0 0 0 0 d1 endstream endobj 57 0 obj <> stream 0 0 0 -45 40 2 d1 40 0 0 47 0 -45 cm BI /IM true/W 40/H 47/BPC 1/F/CCF/DP<> ID &¤l>}þÿýïÿßïÿßáÿø¿ÿÿ÷rj®B÷éý§Ãïü=û†þûs4ßtà oNbb¶0¿€€ EI endstream endobj 58 0 obj <> stream 46 0 0 0 0 0 d1 endstream endobj 59 0 obj <> stream 0 0 0 -55 61 5 d1 61 0 0 60 0 -55 cm BI /IM true/W 61/H 60/BPC 1/F/CCF/DP<> ID &¨àˆpàäÕpÃÁƒáàÃÇÁ‡†‡ƒ > <0x|x`øx0ðÃÃàß‚ÂXAp°AaÁp‚ÂÂÁ„   ,, \,X@°X… MQ€X EI endstream endobj 60 0 obj <> stream 80 0 0 0 0 0 d1 endstream endobj 61 0 obj <> stream 100 0 0 0 0 0 d1 endstream endobj 62 0 obj <> stream 35 0 0 0 0 0 d1 endstream endobj 63 0 obj <> stream 40 0 0 0 0 0 d1 endstream endobj 78 0 obj <> stream 110 0 0 0 0 0 d1 endstream endobj 79 0 obj <> stream 51 0 0 0 0 0 d1 endstream endobj 80 0 obj <> stream 32 0 0 0 0 0 d1 endstream endobj 81 0 obj <> stream 91 0 0 0 0 0 d1 endstream endobj 82 0 obj <> stream 0 0 0 -75 35 26 d1 35 0 0 101 0 -75 cm BI /IM true/W 35/H 101/BPC 1/F/CCF/DP<> ID &¡™òÑÑÁth-.´á¯ÿÿÿÿÿÿÿÿÿÿÿÿÿý­tµÁ,­xQ4ÕÃfFF ï·¿¿ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡ý‡ÞÞɆPÈf EI endstream endobj 83 0 obj <> stream 0 0 0 -45 47 2 d1 47 0 0 47 0 -45 cm BI /IM true/W 47/H 47/BPC 1/F/CCF/DP<> ID &¡ Ã…þ`ƒÁ0hþõµ½ a¤ß ü;êÿAî?÷Þöÿßìßáýɯýÿïûßì>ÿûpÿÛðþÃOoì.ÚàÁ£‹½°ÅCdþ  EI endstream endobj 84 0 obj <> stream 0 0 0 -45 39 2 d1 39 0 0 47 0 -45 cm BI /IM true/W 39/H 47/BPC 1/F/CCF/DP<> ID &¡Ê‚‚>!˜•‡¤AÿH~ü ÃÔ7üm®ð~ÿáòkÿûýû!ž÷÷wû|0ûöÞØ>ûo %½aˆYçø€ EI endstream endobj 85 0 obj <> stream 0 0 0 -70 50 2 d1 50 0 0 72 0 -70 cm BI /IM true/W 50/H 72/BPC 1/F/CCF/DP<> ID &±°Ë>'ð˜AðÑÆü,=öÿû÷Þûýßÿ{ÿïwÿ‡ïûƒ¿÷ïß÷‡ýÿáÿ{þÿ÷ï÷ø~ÿöþ^þÍ4·^ W†h¿ÇÿÃýÿûßÿ¿ßÿ¼‰WÿþA‰À@ EI endstream endobj 86 0 obj <> stream 44 0 0 0 0 0 d1 endstream endobj 87 0 obj <> stream 62 0 0 0 0 0 d1 endstream endobj 88 0 obj <> stream 39 0 0 0 0 0 d1 endstream endobj 89 0 obj <> stream 61 0 0 0 0 0 d1 endstream endobj 90 0 obj <> stream 0 0 0 -75 35 26 d1 35 0 0 101 0 -75 cm BI /IM true/W 35/H 101/BPC 1/F/CCF/DP<> ID 3 ¼† É©ÃÃ÷ï~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÃÛïo è ønÁp‰ÇK­.úÿÿÿÿÿÿÿÿÿÿÿÿÿÿ×ú×­pK.B™†^  EI endstream endobj 103 0 obj <> stream 0 0 0 0 34 25 d1 34 0 0 25 0 0 cm BI /IM true/W 34/H 25/BPC 1/F/CCF/DP<> ID )Üš†SÃ{÷ïÿÿ×Zþ EUB€€ EI endstream endobj 107 0 obj <> stream 0 0 0 0 25 21 d1 25 0 0 21 0 0 cm BI /IM true/W 25/H 21/BPC 1/F/CCF/DP<> ID &£( ôžFAmõ¶+u¿ÿ÷ýIªz]ë°xKc­ëzÃŒ@ EI endstream endobj 108 0 obj <> stream 0 0 0 -67 41 0 d1 41 0 0 67 0 -67 cm BI /IM true/W 41/H 67/BPC 1/F/CCF/DP<> ID &¨ÿì=÷ßùðQÃa÷öý‡{í¼7ß¶½½¼>ÞÃáí÷¾{÷Þßïßü7.ø@úÿÿÿú ‚­‚ûýváv»ÖÃK´¸0\­‚à !ƒÀ@ EI endstream endobj 112 0 obj <> stream 0 0 0 -67 34 0 d1 34 0 0 67 0 -67 cm BI /IM true/W 34/H 67/BPC 1/F/CCF/DP<> ID &«ÿäl/ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ“ØþMBÁ÷Þ  EI endstream endobj 113 0 obj <> stream 0 0 0 -75 39 26 d1 39 0 0 101 0 -75 cm BI /IM true/W 39/H 101/BPC 1/F/CCF/DP<> ID &¹€Òú}ÿì?ýûÿß¿ü7ÿ¿ûÿ~ÿ÷ïÿ ÿïßþÃÿß¿ýûþðÿ¿ûÿ~ÿöÿðýÿïßþÃÿß¿ý¿ü?û÷ÿ°ÿ½…ð EI endstream endobj 114 0 obj <> stream 48 0 0 0 0 0 d1 endstream endobj 115 0 obj <> stream 0 0 0 -69 45 0 d1 45 0 0 69 0 -69 cm BI /IM true/W 45/H 69/BPC 1/F/CCF/DP<> ID & Çÿ `Œ5ÿÿÿÿÿÿœ7æÁA „Õ|0þÿöý¿ý¿‡÷áýý¿¿ý¿aÿáý¿¿ý¿¿‡á¿ýý¿÷ßðûþÿ¾ÿð EI endstream endobj 116 0 obj <> stream 47 0 0 0 0 0 d1 endstream endobj xref 0 118 0000000000 65535 f 0000084611 00000 n 0000084464 00000 n 0000004586 00000 n 0000000015 00000 n 0000004566 00000 n 0000084324 00000 n 0000084411 00000 n 0000084129 00000 n 0000083982 00000 n 0000084218 00000 n 0000011330 00000 n 0000004754 00000 n 0000011309 00000 n 0000084753 00000 n 0000082954 00000 n 0000085991 00000 n 0000086267 00000 n 0000086546 00000 n 0000086811 00000 n 0000086876 00000 n 0000087116 00000 n 0000087291 00000 n 0000087356 00000 n 0000087598 00000 n 0000017834 00000 n 0000011518 00000 n 0000017813 00000 n 0000087662 00000 n 0000087878 00000 n 0000088122 00000 n 0000082808 00000 n 0000088186 00000 n 0000082897 00000 n 0000088424 00000 n 0000088657 00000 n 0000088932 00000 n 0000088997 00000 n 0000089249 00000 n 0000089313 00000 n 0000089512 00000 n 0000089576 00000 n 0000089814 00000 n 0000089878 00000 n 0000090148 00000 n 0000090212 00000 n 0000090456 00000 n 0000090520 00000 n 0000090774 00000 n 0000090838 00000 n 0000091070 00000 n 0000091134 00000 n 0000091198 00000 n 0000091456 00000 n 0000091520 00000 n 0000091750 00000 n 0000091814 00000 n 0000091878 00000 n 0000092108 00000 n 0000092172 00000 n 0000092440 00000 n 0000092504 00000 n 0000092569 00000 n 0000092633 00000 n 0000084072 00000 n 0000023784 00000 n 0000018034 00000 n 0000023763 00000 n 0000029783 00000 n 0000023962 00000 n 0000029762 00000 n 0000033746 00000 n 0000029961 00000 n 0000033725 00000 n 0000082737 00000 n 0000038472 00000 n 0000033938 00000 n 0000038451 00000 n 0000092697 00000 n 0000092762 00000 n 0000092826 00000 n 0000092890 00000 n 0000092954 00000 n 0000093214 00000 n 0000093463 00000 n 0000093698 00000 n 0000093961 00000 n 0000094025 00000 n 0000094089 00000 n 0000094153 00000 n 0000094217 00000 n 0000045699 00000 n 0000038662 00000 n 0000045678 00000 n 0000052389 00000 n 0000045877 00000 n 0000052368 00000 n 0000061266 00000 n 0000052557 00000 n 0000061245 00000 n 0000069506 00000 n 0000061446 00000 n 0000069484 00000 n 0000094471 00000 n 0000076426 00000 n 0000069708 00000 n 0000076404 00000 n 0000094661 00000 n 0000094865 00000 n 0000082535 00000 n 0000076618 00000 n 0000082513 00000 n 0000095117 00000 n 0000095331 00000 n 0000095579 00000 n 0000095644 00000 n 0000095885 00000 n 0000084660 00000 n trailer << /Size 118 /Root 1 0 R /Info 117 0 R >> startxref 95950 %%EOF nutcracker-0.4.1+dfsg/notes/debug.txt0000664000000000000000000001421312542173515016261 0ustar rootroot- strace strace -o strace.txt -ttT -s 1024 -p `pgrep nutcracker` - libyaml (yaml-0.1.4) - yaml tokens: 0 YAML_NO_TOKEN, 1 YAML_STREAM_START_TOKEN, 2 YAML_STREAM_END_TOKEN, 3 YAML_VERSION_DIRECTIVE_TOKEN, 4 YAML_TAG_DIRECTIVE_TOKEN, 5 YAML_DOCUMENT_START_TOKEN, 6 YAML_DOCUMENT_END_TOKEN, 7 YAML_BLOCK_SEQUENCE_START_TOKEN, 8 YAML_BLOCK_MAPPING_START_TOKEN, 9 YAML_BLOCK_END_TOKEN, 10 YAML_FLOW_SEQUENCE_START_TOKEN, 11 YAML_FLOW_SEQUENCE_END_TOKEN, 12 YAML_FLOW_MAPPING_START_TOKEN, 13 YAML_FLOW_MAPPING_END_TOKEN, 14 YAML_BLOCK_ENTRY_TOKEN, 15 YAML_FLOW_ENTRY_TOKEN, 16 YAML_KEY_TOKEN, 17 YAML_VALUE_TOKEN, 18 YAML_ALIAS_TOKEN, 19 YAML_ANCHOR_TOKEN, 20 YAML_TAG_TOKEN, 21 YAML_SCALAR_TOKEN - yaml events 0 YAML_NO_EVENT, 1 YAML_STREAM_START_EVENT, 2 YAML_STREAM_END_EVENT, 3 YAML_DOCUMENT_START_EVENT, 4 YAML_DOCUMENT_END_EVENT, 5 YAML_ALIAS_EVENT, 6 YAML_SCALAR_EVENT, 7 YAML_SEQUENCE_START_EVENT, 8 YAML_SEQUENCE_END_EVENT, 9 YAML_MAPPING_START_EVENT, 10 YAML_MAPPING_END_EVENT - sys/queue.h queue.h is a generic linked list library adapted from BSD. It has three macro knobs that are useful for debugging: - QUEUE_MACRO_SCRUB nullifies links (next and prev pointers) of deleted elements and catches cases where we are attempting to do operations on an element that has already been unlinked. - QUEUE_MACRO_TRACE keeps track of __FILE__ and __LINE__ of last two updates to the list data structure. - QUEUE_MACRO_ASSERT verifies the sanity of list data structure on every operation. - valgrind valgrind --tool=memcheck --leak-check=yes - Core dump ulimit -c unlimited - Generate ENOMEM to test "Out of Memory" ulimit -m # limit maximum memory size ulimit -v # limit virtual memory - get nutcracker stats printf "" | socat - TCP:localhost:22222 | tee stats.txt printf "" | nc localhost 22222 | python -mjson.tool - Signalling and Logging SIGTTIN - To up the log level SIGTTOU - To down the log level SIGHUP - To reopen log file - Error codes: http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_2.html /usr/include/asm-generic/errno-base.h /usr/include/asm-generic/errno.h - epoll (linux) union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; }; struct epoll_event { uint32_t events; /* epoll events */ struct epoll_data data; /* user data variable */ }; /* events */ EPOLLIN = 0x001, EPOLLPRI = 0x002, EPOLLOUT = 0x004, EPOLLERR = 0x008, EPOLLHUP = 0x010, EPOLLRDNORM = 0x040, EPOLLRDBAND = 0x080, EPOLLWRNORM = 0x100, EPOLLWRBAND = 0x200, EPOLLMSG = 0x400, EPOLLRDHUP = 0x2000, EPOLLONESHOT = (1 << 30), EPOLLET = (1 << 31) /* opcodes */ EPOLL_CTL_ADD = 1 /* add a file decriptor to the interface */ EPOLL_CTL_DEL = 2 /* remove a file decriptor from the interface */ EPOLL_CTL_MOD = 3 /* change file decriptor epoll_event structure */ - kqueue (bsd) struct kevent { uintptr_t ident; /* identifier for this event */ int16_t filter; /* filter for event */ uint16_t flags; /* general flags */ uint32_t fflags; /* filter-specific flags */ intptr_t data; /* filter-specific data */ void *udata; /* opaque user data identifier */ }; /* flags / events */ EV_ADD = 0x0001 /* action - add event to kq (implies enable) */ EV_DELETE = 0x0002 /* action - delete event from kq */ EV_ENABLE = 0x0004 /* action - enable event */ EV_DISABLE = 0x0008 /* action - disable event (not reported) */ EV_RECEIPT = 0x0040 /* action - force EV_ERROR on success, data == 0 */ EV_ONESHOT = 0x0010 /* flags - only report one occurrence */ EV_CLEAR = 0x0020 /* flags - clear event state after reporting */ EV_DISPATCH = 0x0080 /* flags - disable event after reporting */ EV_SYSFLAGS = 0xF000 /* flags - reserved by system */ EV_FLAG0 = 0x1000 /* flags - filter-specific flag */ EV_FLAG1 = 0x2000 /* flags - filter-specific flag */ EV_EOF = 0x8000 /* returned values - EOF detected */ EV_ERROR = 0x4000 /* returned values - error, data contains errno */ /* filters */ EVFILT_READ (-1) /* readable */ EVFILT_WRITE (-2) /* writable */ EVFILT_AIO (-3) /* attached to aio requests */ EVFILT_VNODE (-4) /* attached to vnodes */ EVFILT_PROC (-5) /* attached to struct proc */ EVFILT_SIGNAL (-6) /* attached to struct proc */ EVFILT_TIMER (-7) /* timers */ EVFILT_MACHPORT (-8) /* mach portsets */ EVFILT_FS (-9) /* filesystem events */ EVFILT_USER (-10) /* user events */ EVFILT_VM (-12) /* virtual memory events */ EV_CLEAR behaves like EPOLLET because it resets the event after it is returned; without this flag, the event would be repeatedly returned. - poll (unix) POLLIN 0x001 /* there is data to read */ POLLPRI 0x002 /* there is urgent data to read */ POLLOUT 0x004 /* writing now will not block */ POLLRDNORM 0x040 /* normal data may be read */ POLLRDBAND 0x080 /* priority data may be read */ POLLWRNORM 0x100 /* writing now will not block */ POLLWRBAND 0x200 /* priority data may be written */ POLLMSG 0x400 POLLREMOVE 0x1000 POLLRDHUP 0x2000 POLLERR 0x008 /* error condition */ POLLHUP 0x010 /* hung up */ POLLNVAL 0x020 /* invalid polling request */ - event ports (solaris) typedef struct port_event { int portev_events; /* event data is source specific */ ushort_t portev_source; /* event source */ ushort_t portev_pad; /* port internal use */ uintptr_t portev_object; /* source specific object */ void *portev_user; /* user cookie */ } port_event_t; /* port sources */ PORT_SOURCE_AIO 1 PORT_SOURCE_TIMER 2 PORT_SOURCE_USER 3 PORT_SOURCE_FD 4 PORT_SOURCE_ALERT 5 PORT_SOURCE_MQ 6 PORT_SOURCE_FILE 7 nutcracker-0.4.1+dfsg/notes/c-styleguide.txt0000664000000000000000000003627612542173515017606 0ustar rootroot- No literal tabs. Expand tabs to 4 spaces. - Indentation is 4 spaces. - No more than 3 levels of indentation, otherwise you should think about refactoring your code. - Use one statement per line. - Make sure that your editor does not leave space at the end of the line. - snake_case for variable, function and file names. - Use your own judgment when naming variables and functions. Be as Spartan as possible. Eg: Using name like this_variable_is_a_temporary_counter will usually be frowned upon. - Don’t use local variables or parameters that shadow global identifiers. GCC’s ‘-Wshadow’ option can help you to detect this problem. - Avoid using int, char, short, long. Instead use int8_t uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, which are available in . However, when interfacing with system calls and libraries you cannot get away from using int and char. - Use bool for boolean variables. You have to include - Avoid using a bool as type for struct member names. Instead use unsigned 1-bit bit field. Eg: struct foo { unsigned is_bar:1; }; - Always use size_t type when dealing with sizes of objects or memory ranges. - Your code should be 64-bit and 32-bit friendly. Bear in mind problems of printing, comparisons, and structure alignment. You have to include to get generic format specifier macros for printing. - 80 column line limit. - If you have to wrap a long statement (> 80 column), put the operator at the end of the line and indent the next line at the same column as the arguments in the previous column. Eg: while (cnt < 20 && this_variable_name_is_too_long && ep != NULL) { z = a + really + long + statement + that + needs + three + lines + gets + indented + on + the + same + column + as + the + previous + column } and: int a = function(param_a, param_b, param_c, param_d, param_e, param_f, param_g, param_h, param_i, param_j, param_k, param_l); - Always use braces for all conditional blocks (if, switch, for, while, do). This holds good even for single statement conditional blocks. Eg: if (cond) { stmt; } - Placement of braces for non-function statement blocks - put opening brace last on the line and closing brace first. Eg: if (x is true) { we do y } - Placement of brace for functions - put the opening brace at the beginning of the next line and closing brace first. This is useful because several tools look for opening brace in column one to find beginning of C functions. Eg: int function(int x) { body of the function } - Closing brace is empty on a line of its own, except in cases where it is followed by a continuation of the same statement, i.e. a "while" in a do-statement or an "else" in an if-statement, like this: do { body of do-loop } while (condition); and, if (x == y) { .. } else if (x > y) { ... } else { .... } - Column align switch keyword and the corresponding case/default keyword. Eg: switch (alphabet) { case 'a': case 'b': printf("I am a or b\n"); break; default: break; } - Forever loops are done with for, and not while. Eg: for (;;) { stmt; } - Don't use a space after a function name. - Do not needlessly surround the return expression with parentheses. - Use space after keywords. Exceptions are sizeof, typeof, alignof and __attribute__, which look like functions. - Do not add spaces around (inside) parenthesized expressions. s = sizeof( sizeof(*p)) ); /* bad example */ s = sizeof(sizeof(*p)); /* good example */ - Casts should not be followed by space. Eg: int q = *(int *)&p - There is no need to type cast when assigning a void pointer to a non-void pointer, or vice versa. - Avoid using goto statements. However there are some exceptions to this rule when a single goto label within a function and one or more goto statements come in handy when a function exits from multiple locations and some common work such as cleanup has to be done. Eg: int fun(void) { int result = 0; char *buffer; buffer = malloc(1024); if (buffer == NULL) { return -1; } if (condition1) { while (loop1) { ... } result = 1; goto out; } ... out: free(buffer); return result; } - When declaring pointer data, use '*' adjacent to the data name and not adjacent to the type name. Eg: int function(int *p) { char *p; } - Use one space around (on each side of) most binary and ternary operators, such as any of these: = + - < > * / % | & ^ <= >= == != ? : but no space after unary operators: & * + - ~ ! sizeof typeof alignof __attribute__ defined no space before the postfix increment & decrement unary operators: ++ -- and no space around the '.' and "->" structure member operators. - 0 and NULL; use 0 for integers, 0.0 for doubles, NULL for pointers, and '\0' for chars. - Test pointers against NULL. E.g, use: if (p == NULL) not: !(p) - Do not use ! for tests unless it is a boolean. E.g. use: if (*p == '\0') not: if (!*p) - Don't use assignments inside if or while-conditions. E.g, use: struct foo *foo; foo = malloc(sizeof(*foo)); if (foo == NULL) { return -1 } not: struct foo *foo; if ((foo = malloc(sizeof(*foo))) == NULL) { return -1; } - Don't ever use typedef for structure types. Typedefs are problematic because they do not properly hide their underlying type; for example you need to know if the typedef is the structure itself or a pointer to the structure. In addition they must be declared exactly once, whereas an incomplete structure type can be mentioned as many times as necessary. Typedefs are difficult to use in stand-alone header files: the header that defines the typedef must be included before the header that uses it, or by the header that uses it (which causes namespace pollution), or there must be a back-door mechanism for obtaining the typedef. - The only exception for using a typedef is when you are defining a type for a function pointer or a type for an enum. Eg: typedef void (*foo_handler_t)(int, void *); or: typedef enum types { TYPE_1, TYPE_2 } types_t; - Use just one variable declaration per line when variables are part of a struct. This leaves you room for a small comment on each item, explaining its use. Declarations should also be aligned. Eg, use: struct foo { int *foo_a; /* comment for foo_a */ int foo_b; /* comment for foo_b */ unsigned foo_c:1; /* comment for foo_c */ }; and not: struct foo { int *foo_a, foo_b; unsigned foo_c:1; }; - For variable declaration outside a struct, either collect all the declarations of the same type on a single line, or use one variable per line if the variables purpose needs to be commented. Eg: char *a, *b, c; or: char *a, *b; char c; /* comments for c */ - Avoid magic numbers because no-one has a clue (including the author) of what it means after a month. - Function definitions should start the name of the function in column one. This is useful because it makes searching for function definitions fairly trivial. Eg: static char * concat(char *s1, char *s2) { body of the function } - Function and variables local to a file should be static. - Separate two successive functions with one blank line. - Include parameter names with their datypes in function declaration. Eg: void function(int param); - Functions should be short and sweet, and do just one thing. They should fit on one or two screenfuls of text (80 x 24 screen size), and do one thing and do that well. The maximum length of a function is inversely proportional to the complexity and indentation level of that function. So, if you have a conceptually simple function that is just one long (but simple) case-statement, where you have to do lots of small things for a lot of different cases, it's OK to have a longer function. Another measure of the function is the number of local variables. They shouldn't exceed 5-10, or you're doing something wrong. Re-think the function, and split it into smaller pieces. A human brain can generally easily keep track of about 7 different things, anything more and it gets confused. You know you're brilliant, but maybe you'd like to understand what you did 2 weeks from now. - Use const for function parameters passed by reference, if the passed pointer has no side effect. - C style comments only. Don't use // for single line comments. Instead use /* ... */ style. - For multi-line comments use the following style /* * This is the preferred style for multi-line * comments in the Linux kernel source code. * Please use it consistently. * * Description: A column of asterisks on the left side, * with beginning and ending almost-blank lines. */ - To comment out block of code spanning several lines use preprocessor directive "#ifdef 0 ... #endif" - Please write a brief comment at the start of each source file, with the file name and a line or two about the overall purpose of the file. - All major functions should have comments describing what they do at the head of the function. Avoid putting comments in the function body unless absolutely needed. If possible, add a comment on what sorts of arguments the function gets, and what the possible values of arguments mean and what they are used for and the significance of return value if there is one. It is not necessary to duplicate in words the meaning of the C argument declarations, if a C type is being used in its customary fashion. If there is anything nonstandard about its use (such as an argument of type char * which is really the address of the second character of a string, not the first), or any possible values that would not work the way one would expect (such as, that strings containing newlines are not guaranteed to work), be sure to say so. Eg: /* * Try to acquire a physical address lock while a pmap is locked. If we * fail to trylock we unlock and lock the pmap directly and cache the * locked pa in *locked. The caller should then restart their loop in case * the virtual to physical mapping has changed. * * Returns 0 on success and -1 on failure. */ int vm_page_pa_tryrelock(pmap_t pmap, vm_paddr_t pa, vm_paddr_t *locked) { ... - The comment on a function is much clearer if you use the argument names to speak about the argument values. The variable name itself should be lower case, but write it in upper case when you are speaking about the value rather than the variable itself. Thus, “the inode number NODE_NUM†rather than “an inodeâ€. - Every struct definition should have an accompanying comment that describes what it is for and how it should be used. - Finally, while comments are absolutely important to keep the code readable, remember that the best code is self-documenting. Giving sensible names to types and variables is much better than using obscure names that you must then explain through comments. - Recommend using UPPERCASE for macro names. However, sometimes using lowercase for macro names makes sense when macros masquerade as well-known function calls. Eg, it makes sense to write the wrapper for the standard free() function in lowercase to keep the readability consistent: #define my_free(_p) do { \ free(_p); \ (_p) = NULL; \ } while (0) - Use enums when defining more than one related constants. All enumeration values are in UPPERCASE. - Avoid macros as much as possible and use inline functions, enums and const variables wherever you can. - For macros encapsulating compound statements, right justify the backslashes and enclose it in do { ... } while (0) - For parameterized macros, all the parameters used in the macro body must be surrounded by parentheses. Eg: #define ADD_1(_x) ((_x) + 1) - Use sizeof(varname) instead of sizeof(type) whenever possible. Eg: char *p; p = malloc(sizeof(*p)); /* good example */ p = malloc(sizeof(char)); /* bad example */ - All variables should be declared at the beginning of a scope block {..}. It is even preferred to declare all variables at the beginning of the function so that all the local variable declarations is in one place and we can see the comprehensive list in one glance. - Global structs should be declared at the top of the file in which they are used, or in separate header files if they are used in multiple source files. - Declarations of external functions and functions to appear later in the source file should all go in one place near the beginning of the file, somewhere before the first function definition in the file or else should go in a header file. - Use of extern should be considered as evil, if it is used in header files to reference global variables. - Don’t put extern declarations inside functions. - Usually every *.c file should have an associated *.h file. There are some exceptions to this rule, such as unit tests and small *.c files containing just the main() function. - Every header file in the source code must have preprocessor conditional to prevent the header file from being scanned multiple times and avoiding mutual dependency cycles. Alternatively you can use #pragma once directive, as it avoids name clashes and increases the compile speed. Eg, for a header file named foo.h, the entire contents of the header file must be between the guard macros as follows: #ifndef _FOO_H_ #define _FOO_H_ ... #endif /* _FOO_H_ */ Or, #pragma once #ifndef _FOO_H_ #define _FOO_H_ ... #endif /* _FOO_H_ */ - Don't use #include when a forward declaration would suffice. - Functions defined in header files should be static inline. - Don’t make the program ugly just to placate GCC when extra warnings options such as ‘-Wconversion’ or ‘-Wundef’ are used. These options can help in finding bugs, but they can also generate so many false alarms that that it hurts readability to silence them with unnecessary casts, wrappers, and other complications. - Conditional compilation: when supporting configuration options already known when building your program we prefer using if (... ) over conditional compilation, as in the former case the compiler is able to perform more extensive checking of all possible code paths. Eg, use: if (HAS_FOO) ... else ... instead of: #ifdef HAS_FOO ... #else ... #endif A modern compiler such as GCC will generate exactly the same code in both cases and of course, the former method assumes that HAS_FOO is defined as either 0 or 1. - Finally, rules are rules. Sometimes they are sensible and sometimes not and regardless of your preference, we would like you to follow them. A project is easier to follow if all project contributors follow the style rules so that they can all read and understand everyone's code easily. But remember, like all good rules, they are exceptions where it makes sense not to be too rigid on the grounds of common sense and consistency! nutcracker-0.4.1+dfsg/notes/socket.txt0000664000000000000000000001421312542173515016463 0ustar rootroot- int listen(int sockfd, int backlog); Linux: The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds. backlog specifies the queue length for completely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it is silently truncated to that value; the default value in this file is 128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the value 128. BSD: The backlog argument defines the maximum length the queue of pending connections may grow to. The real maximum queue length will be 1.5 times more than the value specified in the backlog argument. A subsequent listen() system call on the listening socket allows the caller to change the maximum queue length using a new backlog argument. If a connection request arrives with the queue full the client may receive an error with an indication of ECONNREFUSED, or, in the case of TCP, the connection will be silently dropped. The listen() system call appeared in 4.2BSD. The ability to configure the maximum backlog at run-time, and to use a negative backlog to request the maximum allowable value, was introduced in FreeBSD 2.2. - SO_LINGER (linger) socket option This option specifies what should happen when the socket of a type that promises reliable delivery still has untransmitted messages when it is closed struct linger { int l_onoff; /* nonzero to linger on close */ int l_linger; /* time to linger (in secs) */ }; l_onoff = 0 (default), then l_linger value is ignored and close returns immediately. But if there is any data still remaining in the socket send buffer, the system will try to deliver the data to the peer l_onoff = nonzero, then close blocks until data is transmitted or the l_linger timeout period expires a) l_linger = 0, TCP aborts connection, discards any data still remaining in the socket send buffer and sends RST to peer. This avoids the TCP's TIME_WAIT state b) l_linger = nonzero, then kernel will linger when socket is closed. If there is any pending data in the socket send buffer, the kernel waits until all the data is sent and acknowledged by peer TCP, or the linger time expires If a socket is set as nonblocking, it will not wait for close to complete even if linger time is nonzero - TIME_WAIT state The end that performs active close i.e. the end that sends the first FIN goes into TIME_WAIT state. After a FIN packet is sent to the peer and after that peers FIN/ACK arrvies and is ACKed, we go into a TIME_WAIT state. The duration that the end point remains in this state is 2 x MSL (maximum segment lifetime). The reason that the duration of the TIME_WAIT state is 2 x MSL is because the maximum amount of time a packet can wander around a network is assumed to be MSL seconds. The factor of 2 is for the round-trip. The recommended value for MSL is 120 seconds, but Berkeley derived implementations normally use 30 seconds instead. This means a TIME_WAIT delay is between 1 and 4 minutes. For Linux, the TIME_WAIT state duration is 1 minute (net/tcp.h): #define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT * state, about 60 seconds */ TIME_WAIT state on client, combined with limited number of ephermeral ports available for TCP connections severely limits the rate at which new connections to the server can be created. On Linux, by default ephemeral ports are in the range of 32768 to 61000: $ cat /proc/sys/net/ipv4/ip_local_port_range 32768 61000 So with a TIME_WAIT state duration of 1 minute, the maximum sustained rate for any client is ~470 new connections per second - TCP keepalive TCP keepalive packet (TCP packet with no data and the ACK flag turned on) is used to assert that connection is still up and running. This is useful because if the remote peer goes away without closing their connection, the keepalive probe will detect this and notice that the connection is broken even if there is no traffic on it. Imagine, the following scenario: You have a valid TCP connection established between two endpoints A and B. B terminates abnormally (think kernel panic or unplugging of network cable) without sending anything over the network to notify A that connection is broken. A, from its side, is ready to receive data, and has no idea that B has gone away. Now B comes back up again, and while A knows about a connection with B and still thinks that it active, B has no such idea. A tries to send data to B over a dead connection, and B replies with an RST packet, causing A to finally close the connection. So, without a keepalive probe A would never close the connection if it never sent data over it. - There are four socket functions that pass a socket address structure from the process to the kernel - bind, connect, sendmsg and sendto. These function are also responsible for passing the length of the sockaddr that they are passing (socklen_t). There are five socket functions that pass a socket from the kernel to the process - accept, recvfrom, recvmsg, getpeername, getsockname. The kernel is also responsible for returning the length of the sockaddr struct that it returns back to the userspace Different sockaddr structs: 1. sockaddr_in 2. sockaddr_in6 3. sockaddr_un Special types of in_addr_t /* Address to accept any incoming messages */ #define INADDR_ANY ((in_addr_t) 0x00000000) /* Address to send to all hosts */ #define INADDR_BROADCAST ((in_addr_t) 0xffffffff) /* Address indicating an error return */ #define INADDR_NONE ((in_addr_t) 0xffffffff) nutcracker-0.4.1+dfsg/notes/memcache.md0000664000000000000000000002133412542173515016520 0ustar rootroot## Memcache Command Support ### Request - Twemproxy implements only the memached ASCII commands - Binary commands are currently unsupported #### Ascii Storage Command +-------------------+------------+--------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+--------------------------------------------------------------------------+ | set | Yes | set [noreply]\r\n\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | add | Yes | add [noreply]\r\n\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | replace | Yes | replace [noreply]\r\n\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | append | Yes | append [noreply]\r\n\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | prepend | Yes | prepend [noreply]\r\n\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | cas | Yes | cas [noreply]\r\n\r\n | +-------------------+------------+--------------------------------------------------------------------------+ * Where, * - uint32_t : data specific client side flags * - uint32_t : expiration time (in seconds) * - uint32_t : size of the data (in bytes) * - uint8_t[]: data block * - uint64_t #### Ascii Retrival Command +-------------------+------------+--------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+--------------------------------------------------------------------------+ | get | Yes | get []+\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | gets | Yes | gets []+\r\n | +-------------------+------------+--------------------------------------------------------------------------+ #### Ascii Delete +-------------------+------------+--------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+--------------------------------------------------------------------------+ | delete | Yes | delete [noreply]\r\n | +-------------------+------------+--------------------------------------------------------------------------+ #### Ascii Arithmetic Command +-------------------+------------+--------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+--------------------------------------------------------------------------+ | incr | Yes | incr [noreply]\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | decr | Yes | decr [noreply]\r\n | +-------------------+------------+--------------------------------------------------------------------------+ * Where, * - uint64_t #### Ascii Misc Command +-------------------+------------+--------------------------------------------------------------------------+ | Command | Supported? | Format | +-------------------+------------+--------------------------------------------------------------------------+ | set | Yes | touch [noreply]\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | quit | Yes | quit\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | flush_all | No | flush_all [] [noreply]\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | version | No | version\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | verbosity | No | verbosity [noreply]\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | stats | No | stats\r\n | +-------------------+------------+--------------------------------------------------------------------------+ | stats | No | stats \r\n | +-------------------+------------+--------------------------------------------------------------------------+ ### Response #### Error Responses ERROR\r\n CLIENT_ERROR [error]\r\n SERVER_ERROR [error]\r\n Where, - ERROR means client sent a non-existent command name - CLIENT_ERROR means that command sent by the client does not conform to the protocol - SERVER_ERROR means that there was an error on the server side that made processing of the command impossible #### Storage Command Responses STORED\r\n NOT_STORED\r\n EXISTS\r\n NOT_FOUND\r\n Where, - STORED indicates success. - NOT_STORED indicates the data was not stored because condition for an add or replace wasn't met. - EXISTS indicates that the item you are trying to store with a cas has been modified since you last fetched it. - NOT_FOUND indicates that the item you are trying to store with a cas does not exist. #### Delete Command Responses NOT_FOUND\r\n DELETED\r\n #### Retrival Responses END\r\n VALUE []\r\n\r\nEND\r\n VALUE []\r\n\r\n[VALUE []\r\n]+\r\nEND\r\n #### Arithmetic Responses NOT_FOUND\r\n \r\n Where, - - uint64_t : new key value after incr or decr operation #### Touch Command Responses NOT_FOUND\r\n TOUCHED\r\n #### Statistics Response [STAT \r\n]+END\r\n #### Misc Responses OK\r\n VERSION \r\n ### Notes - set always creates mapping irrespective of whether it is present on not. - add, adds only if the mapping is not present - replace, only replaces if the mapping is present - append and prepend command ignore flags and expiry values - noreply instructs the server to not send the reply even if there is an error. - decr of 0 is 0, while incr of UINT64_MAX is 0 - maximum length of the key is 250 characters - expiry of 0 means that item never expires, though it could be evicted from the cache - non-zero expiry is either unix time (# seconds since 01/01/1970) or, offset in seconds from the current time (< 60 x 60 x 24 x 30 seconds = 30 days) - expiry time is with respect to the server (not client) - can be zero and when it is, the block is empty. - Thoughts: - ascii protocol is easier to debug - think using strace or tcpdump to see protocol on the wire, Or using telnet or netcat or socat to build memcache requests and responses http://stackoverflow.com/questions/2525188/are-binary-protocols-dead - http://news.ycombinator.com/item?id=1712788 nutcracker-0.4.1+dfsg/notes/recommendation.md0000664000000000000000000003115112542173515017760 0ustar rootrootIf you are deploying nutcracker in your production environment, here are a few recommendations that might be worth considering. ## Log Level By default debug logging is disabled in nutcracker. However, it is worthwhile running nutcracker with debug logging enabled and verbosity level set to LOG_INFO (-v 6 or --verbose=6). This in reality does not add much overhead as you only pay the cost of checking an if condition for every log line encountered during the run time. At LOG_INFO level, nutcracker logs the life cycle of every client and server connection and important events like the server being ejected from the hash ring and so on. Eg. [Thu Aug 2 00:03:09 2012] nc_proxy.c:336 accepted c 7 on p 6 from '127.0.0.1:54009' [Thu Aug 2 00:03:09 2012] nc_server.c:528 connected on s 8 to server '127.0.0.1:11211:1' [Thu Aug 2 00:03:09 2012] nc_core.c:270 req 1 on s 8 timedout [Thu Aug 2 00:03:09 2012] nc_core.c:207 close s 8 '127.0.0.1:11211' on event 0004 eof 0 done 0 rb 0 sb 20: Connection timed out [Thu Aug 2 00:03:09 2012] nc_server.c:406 close s 8 schedule error for req 1 len 20 type 5 from c 7: Connection timed out [Thu Aug 2 00:03:09 2012] nc_server.c:281 update pool 0 'alpha' to delete server '127.0.0.1:11211:1' for next 2 secs [Thu Aug 2 00:03:10 2012] nc_connection.c:314 recv on sd 7 eof rb 20 sb 35 [Thu Aug 2 00:03:10 2012] nc_request.c:334 c 7 is done [Thu Aug 2 00:03:10 2012] nc_core.c:207 close c 7 '127.0.0.1:54009' on event 0001 eof 1 done 1 rb 20 sb 35 [Thu Aug 2 00:03:11 2012] nc_proxy.c:336 accepted c 7 on p 6 from '127.0.0.1:54011' [Thu Aug 2 00:03:11 2012] nc_server.c:528 connected on s 8 to server '127.0.0.1:11212:1' [Thu Aug 2 00:03:12 2012] nc_connection.c:314 recv on sd 7 eof rb 20 sb 8 [Thu Aug 2 00:03:12 2012] nc_request.c:334 c 7 is done [Thu Aug 2 00:03:12 2012] nc_core.c:207 close c 7 '127.0.0.1:54011' on event 0001 eof 1 done 1 rb 20 sb 8 To enable debug logging, you have to compile nutcracker with logging enabled using --enable-debug=log configure option. ## Liveness Failures are a fact of life, especially when things are distributed. To be resilient against failures, it is recommended that you configure the following keys for every server pool. Eg: resilient_pool: auto_eject_hosts: true server_retry_timeout: 30000 server_failure_limit: 3 Enabling `auto_eject_hosts:` ensures that a dead server can be ejected out of the hash ring after `server_failure_limit:` consecutive failures have been encountered on that said server. A non-zero `server_retry_timeout:` ensures that we don't incorrectly mark a server as dead forever especially when the failures were really transient. The combination of `server_retry_timeout:` and `server_failure_limit:` controls the tradeoff between resiliency to permanent and transient failures. Note that an ejected server will not be included in the hash ring for any requests until the retry timeout passes. This will lead to data partitioning as keys originally on the ejected server will now be written to a server still in the pool. To ensure that requests always succeed in the face of server ejections (`auto_eject_hosts:` is enabled), some form of retry must be implemented at the client layer since nutcracker itself does not retry a request. This client-side retry count must be greater than `server_failure_limit:` value, which ensures that the original request has a chance to make it to a live server. ## Timeout It is always a good idea to configure nutcracker `timeout:` for every server pool, rather than purely relying on client-side timeouts. Eg: resilient_pool_with_timeout: auto_eject_hosts: true server_retry_timeout: 30000 server_failure_limit: 3 timeout: 400 Relying only on client-side timeouts has the adverse effect of the original request having timedout on the client to proxy connection, but still pending and outstanding on the proxy to server connection. This further gets exacerbated when client retries the original request. By default, nutcracker waits indefinitely for any request sent to the server. However, when `timeout:` key is configured, a requests for which no response is received from the server in `timeout:` msec is timedout and an error response `SERVER_ERROR Connection timed out\r\n` (memcached) or `-ERR Connection timed out\r\n` (redis) is sent back to the client. ## Error Response Whenever a request encounters failure on a server we usually send to the client a response with the general form - `SERVER_ERROR \r\n` (memcached) or `-ERR ` (redis). For example, when a memcache server is down, this error response is usually: + `SERVER_ERROR Connection refused\r\n` or, + `SERVER_ERROR Connection reset by peer\r\n` When the request timedout, the response is usually: + `SERVER_ERROR Connection timed out\r\n` Seeing a `SERVER_ERROR` or `-ERR` response should be considered as a transient failure by a client which makes the original request an ideal candidate for a retry. ## read, writev and mbuf All memory for incoming requests and outgoing responses is allocated in mbuf. Mbuf enables zero copy for requests and responses flowing through the proxy. By default an mbuf is 16K bytes in size and this value can be tuned between 512 and 16M bytes using -m or --mbuf-size=N argument. Every connection has at least one mbuf allocated to it. This means that the number of concurrent connections nutcracker can support is dependent on the mbuf size. A small mbuf allows us to handle more connections, while a large mbuf allows us to read and write more data to and from kernel socket buffers. If nutcracker is meant to handle a large number of concurrent client connections, you should set the mbuf size to 512 or 1K bytes. ## How to interpret mbuf-size=N argument? Every client connection consumes at least one mbuf. To service a request we need two connections (one from client to proxy and another from proxy to server). So we would need two mbufs. A fragmentable request like 'get foo bar\r\n', which btw gets fragmented to 'get foo\r\n' and 'get bar\r\n' would consume two mbuf for request and two mbuf for response. So a fragmentable request with N fragments needs N * 2 mbufs. The good thing about mbuf is that the memory comes from a reuse pool. Once a mbuf is allocated, it is never freed but just put back into the reuse pool. The bad thing is that once mbuf is allocated it is never freed, since a freed mbuf always goes back to the [reuse pool](https://github.com/twitter/twemproxy/blob/master/src/nc_mbuf.c#L23-L24). This can however be easily fixed if needed by putting a threshold parameter on the reuse pool. So, if nutcracker is handling say 1K client connections and 100 server connections, it would consume (max(1000, 100) * 2 * mbuf-size) memory for mbuf. If we assume that clients are sending non-pipelined request, then with default mbuf-size of 16K this would in total consume 32M. Furthermore, if on average every requests has 10 fragments, then the memory consumption would be 320M. Instead of handling 1K client connections, lets say you were handling 10K, then the memory consumption would be 3.2G. Now instead of using a default mbuf-size of 16K, you used 512 bytes, then memory consumption for the same scenario would drop to 1000 * 2 * 512 * 10 = 10M This is the reason why for 'large number' of connections or for wide multi-get like requests, you want to choose a small value for mbuf-size like 512 ## Maximum Key Length The memcache ascii protocol [specification](notes/memcache.txt) limits the maximum length of the key to 250 characters. The key should not include whitespace, or '\r' or '\n' character. For redis, we have no such limitation. However, nutcracker requires the key to be stored in a contiguous memory region. Since all requests and responses in nutcracker are stored in mbuf, the maximum length of the redis key is limited by the size of the maximum available space for data in mbuf (mbuf_data_size()). This means that if you want your redis instances to handle large keys, you might want to choose large mbuf size set using -m or --mbuf-size=N command-line argument. ## Node Names for Consistent Hashing The server cluster in twemproxy can either be specified as list strings in format 'host:port:weight' or 'host:port:weight name'. servers: - 127.0.0.1:6379:1 - 127.0.0.1:6380:1 - 127.0.0.1:6381:1 - 127.0.0.1:6382:1 Or, servers: - 127.0.0.1:6379:1 server1 - 127.0.0.1:6380:1 server2 - 127.0.0.1:6381:1 server3 - 127.0.0.1:6382:1 server4 In the former configuration, keys are mapped **directly** to **'host:port:weight'** triplet and in the latter they are mapped to **node names** which are then mapped to nodes i.e. host:port pair. The latter configuration gives us the freedom to relocate nodes to a different server without disturbing the hash ring and hence makes this configuration ideal when auto_eject_hosts is set to false. See [issue 25](https://github.com/twitter/twemproxy/issues/25) for details. Note that when using node names for consistent hashing, twemproxy ignores the weight value in the 'host:port:weight name' format string. ## Hash Tags [Hash Tags](http://antirez.com/post/redis-presharding.html) enables you to use part of the key for calculating the hash. When the hash tag is present, we use part of the key within the tag as the key to be used for consistent hashing. Otherwise, we use the full key as is. Hash tags enable you to map different keys to the same server as long as the part of the key within the tag is the same. For example, the configuration of server pool _beta_, also shown below, specifies a two character hash_tag string - "{}". This means that keys "user:{user1}:ids" and "user:{user1}:tweets" map to the same server because we compute the hash on "user1". For a key like "user:user1:ids", we use the entire string "user:user1:ids" to compute the hash and it may map to a different server. beta: listen: 127.0.0.1:22122 hash: fnv1a_64 hash_tag: "{}" distribution: ketama auto_eject_hosts: false timeout: 400 redis: true servers: - 127.0.0.1:6380:1 server1 - 127.0.0.1:6381:1 server2 - 127.0.0.1:6382:1 server3 - 127.0.0.1:6383:1 server4 ## Graphing Cache-pool State When running nutcracker in production, you often would like to know the list of live and ejected servers at any given time. You can easily answer this question, by generating a time series graph of live and/or dead servers that are part of any cache pool. To do this your graphing client must collect the following stats exposed by nutcracker: - **server_eof** which is incremented when server closes the connection normally which should not happen because we use persistent connections. - **server_timedout** is incremented when the connection / request to server timedout. - **server_err** is incremented for any other kinds of errors. So, on a given server, the cumulative number of times a server is ejected can be computed as: ```c (server_err + server_timedout + server_eof) / server_failure_limit ``` A diff of the above value between two successive time intervals would generate a nice timeseries graph for ejected servers. You can also graph the timestamp at which any given server was ejected by graphing `server_ejected_at` stat. ## server_connections: > 1 By design, twemproxy multiplexes several client connections over few server connections. It is important to note that **"read my last write"** constraint doesn't necessarily hold true when twemproxy is configured with `server_connections: > 1`. To illustrate this, consider a scenario where twemproxy is configured with `server_connections: 2`. If a client makes pipelined requests with the first request in pipeline being `set foo 0 0 3\r\nbar\r\n` (write) and the second request being `get foo\r\n` (read), the expectation is that the read of key `foo` would return the value `bar`. However, with configuration of two server connections it is possible that write and read request are sent on different server connections which would mean that their completion could race with one another. In summary, if the client expects "read my last write" constraint, you either configure twemproxy to use `server_connections:1` or use clients that only make synchronous requests to twemproxy. ## twemproxy and python-memcached The implementation of delete command in [python-memcached](https://github.com/linsomniac/python-memcached) conflicts with the one in twemproxy. See [issue 283](https://github.com/twitter/twemproxy/pull/283) for details. The workaround for this issue is to call `delete_multi` in python-memcached as follows: mc.delete_multi([key1, key2, ... keyN], time=None) nutcracker-0.4.1+dfsg/.travis.yml0000664000000000000000000000004612542173515015412 0ustar rootrootlanguage: c script: bash ./travis.sh nutcracker-0.4.1+dfsg/tests/0000775000000000000000000000000012542173515014443 5ustar rootrootnutcracker-0.4.1+dfsg/tests/conf/0000775000000000000000000000000012542173515015370 5ustar rootrootnutcracker-0.4.1+dfsg/tests/conf/redis.conf0000664000000000000000000007334212542173515017356 0ustar rootroot # Redis configuration file example # Note on units: when memory size is needed, it is possible to specify # it in the usual form of 1k 5GB 4M and so forth: # # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 1000000000 bytes # 1gb => 1024*1024*1024 bytes # # units are case insensitive so 1GB 1Gb 1gB are all the same. # By default Redis does not run as a daemon. Use 'yes' if you need it. # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. daemonize yes #whitelist configure #whitelist yes #whitelist-file ./whitelist # When running daemonized, Redis writes a pid file in /var/run/redis.pid by # default. You can specify a custom pid file location here. pidfile ${pidfile} # Accept connections on the specified port, default is 6379. # If port 0 is specified Redis will not listen on a TCP socket. port ${port} # If you want you can bind a single interface, if the bind option is not # specified all the interfaces will listen for incoming connections. # # bind 127.0.0.1 # Specify the path for the unix socket that will be used to listen for # incoming connections. There is no default, so Redis will not listen # on a unix socket when not specified. # # unixsocket /tmp/redis.sock # unixsocketperm 755 # Close the connection after a client is idle for N seconds (0 to disable) timeout 0 # TCP keepalive. # # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence # of communication. This is useful for two reasons: # # 1) Detect dead peers. # 2) Take the connection alive from the point of view of network # equipment in the middle. # # On Linux, the specified value (in seconds) is the period used to send ACKs. # Note that to close the connection the double of the time is needed. # On other kernels the period depends on the kernel configuration. # # A reasonable value for this option is 60 seconds. tcp-keepalive 60 # Specify the server verbosity level. # This can be one of: # debug (a lot of information, useful for development/testing) # verbose (many rarely useful info, but not a mess like the debug level) # notice (moderately verbose, what you want in production probably) # warning (only very important / critical messages are logged) loglevel notice # Specify the log file name. Also 'stdout' can be used to force # Redis to log on the standard output. Note that if you use standard # output for logging but daemonize, logs will be sent to /dev/null logfile ${logfile} # To enable logging to the system logger, just set 'syslog-enabled' to yes, # and optionally update the other syslog parameters to suit your needs. # syslog-enabled no # Specify the syslog identity. # syslog-ident redis # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. # syslog-facility local0 # Set the number of databases. The default database is DB 0, you can select # a different one on a per-connection basis using SELECT where # dbid is a number between 0 and 'databases'-1 databases 16 ################################ SNAPSHOTTING ################################# # # Save the DB on disk: # # save # # Will save the DB if both the given number of seconds and the given # number of write operations against the DB occurred. # # In the example below the behaviour will be to save: # after 900 sec (15 min) if at least 1 key changed # after 300 sec (5 min) if at least 10 keys changed # after 60 sec if at least 10000 keys changed # # Note: you can disable saving at all commenting all the "save" lines. # # It is also possible to remove all the previously configured save # points by adding a save directive with a single empty string argument # like in the following example: # #save 900 1 #save 300 10 #save 60 10000 save "" # By default Redis will stop accepting writes if RDB snapshots are enabled # (at least one save point) and the latest background save failed. # This will make the user aware (in an hard way) that data is not persisting # on disk properly, otherwise chances are that no one will notice and some # distater will happen. # # If the background saving process will start working again Redis will # automatically allow writes again. # # However if you have setup your proper monitoring of the Redis server # and persistence, you may want to disable this feature so that Redis will # continue to work as usually even if there are problems with disk, # permissions, and so forth. stop-writes-on-bgsave-error yes # Compress string objects using LZF when dump .rdb databases? # For default that's set to 'yes' as it's almost always a win. # If you want to save some CPU in the saving child set it to 'no' but # the dataset will likely be bigger if you have compressible values or keys. rdbcompression yes # Since version 5 of RDB a CRC64 checksum is placed at the end of the file. # This makes the format more resistant to corruption but there is a performance # hit to pay (around 10%) when saving and loading RDB files, so you can disable it # for maximum performances. # # RDB files created with checksum disabled have a checksum of zero that will # tell the loading code to skip the check. rdbchecksum yes # The filename where to dump the DB dbfilename dump.rdb # The working directory. # # The DB will be written inside this directory, with the filename specified # above using the 'dbfilename' configuration directive. # # The Append Only File will also be created inside this directory. # # Note that you must specify a directory here, not a file name. dir ${dir} ################################# REPLICATION ################################# # Master-Slave replication. Use slaveof to make a Redis instance a copy of # another Redis server. Note that the configuration is local to the slave # so for example it is possible to configure the slave to save the DB with a # different interval, or to listen to another port, and so on. # # slaveof # If the master is password protected (using the "requirepass" configuration # directive below) it is possible to tell the slave to authenticate before # starting the replication synchronization process, otherwise the master will # refuse the slave request. # # masterauth # When a slave loses its connection with the master, or when the replication # is still in progress, the slave can act in two different ways: # # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will # still reply to client requests, possibly with out of date data, or the # data set may just be empty if this is the first synchronization. # # 2) if slave-serve-stale-data is set to 'no' the slave will reply with # an error "SYNC with master in progress" to all the kind of commands # but to INFO and SLAVEOF. # slave-serve-stale-data yes # You can configure a slave instance to accept writes or not. Writing against # a slave instance may be useful to store some ephemeral data (because data # written on a slave will be easily deleted after resync with the master) but # may also cause problems if clients are writing to it because of a # misconfiguration. # # Since Redis 2.6 by default slaves are read-only. # # Note: read only slaves are not designed to be exposed to untrusted clients # on the internet. It's just a protection layer against misuse of the instance. # Still a read only slave exports by default all the administrative commands # such as CONFIG, DEBUG, and so forth. To a limited extend you can improve # security of read only slaves using 'rename-command' to shadow all the # administrative / dangerous commands. slave-read-only yes # Slaves send PINGs to server in a predefined interval. It's possible to change # this interval with the repl_ping_slave_period option. The default value is 10 # seconds. # # repl-ping-slave-period 10 # The following option sets a timeout for both Bulk transfer I/O timeout and # master data or ping response timeout. The default value is 60 seconds. # # It is important to make sure that this value is greater than the value # specified for repl-ping-slave-period otherwise a timeout will be detected # every time there is low traffic between the master and the slave. # repl-timeout 120 # Disable TCP_NODELAY on the slave socket after SYNC? # # If you select "yes" Redis will use a smaller number of TCP packets and # less bandwidth to send data to slaves. But this can add a delay for # the data to appear on the slave side, up to 40 milliseconds with # Linux kernels using a default configuration. # # If you select "no" the delay for data to appear on the slave side will # be reduced but more bandwidth will be used for replication. # # By default we optimize for low latency, but in very high traffic conditions # or when the master and slaves are many hops away, turning this to "yes" may # be a good idea. repl-disable-tcp-nodelay no # Set the replication backlog size. The backlog is a buffer that accumulates # slave data when slaves are disconnected for some time, so that when a slave # wants to reconnect again, often a full resync is not needed, but a partial # resync is enough, just passing the portion of data the slave missed while # disconnected. # # The biggest the replication backlog, the longer the time the slave can be # disconnected and later be able to perform a partial resynchronization. # # The backlog is only allocated once there is at least a slave connected. # repl-backlog-size 64mb # After a master has no longer connected slaves for some time, the backlog # will be freed. The following option configures the amount of seconds that # need to elapse, starting from the time the last slave disconnected, for # the backlog buffer to be freed. # # A value of 0 means to never release the backlog. # # repl-backlog-ttl 3600 # The slave priority is an integer number published by Redis in the INFO output. # It is used by Redis Sentinel in order to select a slave to promote into a # master if the master is no longer working correctly. # # A slave with a low priority number is considered better for promotion, so # for instance if there are three slaves with priority 10, 100, 25 Sentinel will # pick the one wtih priority 10, that is the lowest. # # However a special priority of 0 marks the slave as not able to perform the # role of master, so a slave with priority of 0 will never be selected by # Redis Sentinel for promotion. # # By default the priority is 100. slave-priority 100 # It is possible for a master to stop accepting writes if there are less than # N slaves connected, having a lag less or equal than M seconds. # # The N slaves need to be in "online" state. # # The lag in seconds, that must be <= the specified value, is calculated from # the last ping received from the slave, that is usually sent every second. # # This option does not GUARANTEES that N replicas will accept the write, but # will limit the window of exposure for lost writes in case not enough slaves # are available, to the specified number of seconds. # # For example to require at least 3 slaves with a lag <= 10 seconds use: # # min-slaves-to-write 3 # min-slaves-max-lag 10 # # Setting one or the other to 0 disables the feature. # # By default min-slaves-to-write is set to 0 (feature disabled) and # min-slaves-max-lag is set to 10. ################################## SECURITY ################################### # Require clients to issue AUTH before processing any other # commands. This might be useful in environments in which you do not trust # others with access to the host running redis-server. # # This should stay commented out for backward compatibility and because most # people do not need auth (e.g. they run their own servers). # # Warning: since Redis is pretty fast an outside user can try up to # 150k passwords per second against a good box. This means that you should # use a very strong password otherwise it will be very easy to break. # # requirepass foobared # Command renaming. # # It is possible to change the name of dangerous commands in a shared # environment. For instance the CONFIG command may be renamed into something # hard to guess so that it will still be available for internal-use tools # but not available for general clients. # # Example: # # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52 # # It is also possible to completely kill a command by renaming it into # an empty string: # # rename-command CONFIG "" # # Please note that changing the name of commands that are logged into the # AOF file or transmitted to slaves may cause problems. ################################### LIMITS #################################### # Set the max number of connected clients at the same time. By default # this limit is set to 10000 clients, however if the Redis server is not # able to configure the process file limit to allow for the specified limit # the max number of allowed clients is set to the current file limit # minus 32 (as Redis reserves a few file descriptors for internal uses). # # Once the limit is reached Redis will close all the new connections sending # an error 'max number of clients reached'. # # maxclients 10000 # Don't use more memory than the specified amount of bytes. # When the memory limit is reached Redis will try to remove keys # accordingly to the eviction policy selected (see maxmemmory-policy). # # If Redis can't remove keys according to the policy, or if the policy is # set to 'noeviction', Redis will start to reply with errors to commands # that would use more memory, like SET, LPUSH, and so on, and will continue # to reply to read-only commands like GET. # # This option is usually useful when using Redis as an LRU cache, or to set # an hard memory limit for an instance (using the 'noeviction' policy). # # WARNING: If you have slaves attached to an instance with maxmemory on, # the size of the output buffers needed to feed the slaves are subtracted # from the used memory count, so that network problems / resyncs will # not trigger a loop where keys are evicted, and in turn the output # buffer of slaves is full with DELs of keys evicted triggering the deletion # of more keys, and so forth until the database is completely emptied. # # In short... if you have slaves attached it is suggested that you set a lower # limit for maxmemory so that there is some free RAM on the system for slave # output buffers (but this is not needed if the policy is 'noeviction'). # maxmemory 5368709120 # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory # is reached. You can select among five behaviors: # # volatile-lru -> remove the key with an expire set using an LRU algorithm # allkeys-lru -> remove any key accordingly to the LRU algorithm # volatile-random -> remove a random key with an expire set # allkeys-random -> remove a random key, any key # volatile-ttl -> remove the key with the nearest expire time (minor TTL) # noeviction -> don't expire at all, just return an error on write operations # # Note: with any of the above policies, Redis will return an error on write # operations, when there are not suitable keys for eviction. # # At the date of writing this commands are: set setnx setex append # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby # getset mset msetnx exec sort # # The default is: # maxmemory-policy volatile-lru # LRU and minimal TTL algorithms are not precise algorithms but approximated # algorithms (in order to save memory), so you can select as well the sample # size to check. For instance for default Redis will check three keys and # pick the one that was used less recently, you can change the sample size # using the following configuration directive. # maxmemory-samples 3 ############################## APPEND ONLY MODE ############################### # By default Redis asynchronously dumps the dataset on disk. This mode is # good enough in many applications, but an issue with the Redis process or # a power outage may result into a few minutes of writes lost (depending on # the configured save points). # # The Append Only File is an alternative persistence mode that provides # much better durability. For instance using the default data fsync policy # (see later in the config file) Redis can lose just one second of writes in a # dramatic event like a server power outage, or a single write if something # wrong with the Redis process itself happens, but the operating system is # still running correctly. # # AOF and RDB persistence can be enabled at the same time without problems. # If the AOF is enabled on startup Redis will load the AOF, that is the file # with the better durability guarantees. # # Please check http://redis.io/topics/persistence for more information. appendonly yes # The name of the append only file (default: "appendonly.aof") # appendfilename appendonly.aof # The fsync() call tells the Operating System to actually write data on disk # instead to wait for more data in the output buffer. Some OS will really flush # data on disk, some other OS will just try to do it ASAP. # # Redis supports three different modes: # # no: don't fsync, just let the OS flush the data when it wants. Faster. # always: fsync after every write to the append only log . Slow, Safest. # everysec: fsync only one time every second. Compromise. # # The default is "everysec", as that's usually the right compromise between # speed and data safety. It's up to you to understand if you can relax this to # "no" that will let the operating system flush the output buffer when # it wants, for better performances (but if you can live with the idea of # some data loss consider the default persistence mode that's snapshotting), # or on the contrary, use "always" that's very slow but a bit safer than # everysec. # # More details please check the following article: # http://antirez.com/post/redis-persistence-demystified.html # # If unsure, use "everysec". # appendfsync always appendfsync everysec # appendfsync no # When the AOF fsync policy is set to always or everysec, and a background # saving process (a background save or AOF log background rewriting) is # performing a lot of I/O against the disk, in some Linux configurations # Redis may block too long on the fsync() call. Note that there is no fix for # this currently, as even performing fsync in a different thread will block # our synchronous write(2) call. # # In order to mitigate this problem it's possible to use the following option # that will prevent fsync() from being called in the main process while a # BGSAVE or BGREWRITEAOF is in progress. # # This means that while another child is saving, the durability of Redis is # the same as "appendfsync none". In practical terms, this means that it is # possible to lose up to 30 seconds of log in the worst scenario (with the # default Linux settings). # # If you have latency problems turn this to "yes". Otherwise leave it as # "no" that is the safest pick from the point of view of durability. no-appendfsync-on-rewrite no # Automatic rewrite of the append only file. # Redis is able to automatically rewrite the log file implicitly calling # BGREWRITEAOF when the AOF log size grows by the specified percentage. # # This is how it works: Redis remembers the size of the AOF file after the # latest rewrite (if no rewrite has happened since the restart, the size of # the AOF at startup is used). # # This base size is compared to the current size. If the current size is # bigger than the specified percentage, the rewrite is triggered. Also # you need to specify a minimal size for the AOF file to be rewritten, this # is useful to avoid rewriting the AOF file even if the percentage increase # is reached but it is still pretty small. # # Specify a percentage of zero in order to disable the automatic AOF # rewrite feature. auto-aof-rewrite-percentage 0 auto-aof-rewrite-min-size 64mb ################################ LUA SCRIPTING ############################### # Max execution time of a Lua script in milliseconds. # # If the maximum execution time is reached Redis will log that a script is # still in execution after the maximum allowed time and will start to # reply to queries with an error. # # When a long running script exceed the maximum execution time only the # SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be # used to stop a script that did not yet called write commands. The second # is the only way to shut down the server in the case a write commands was # already issue by the script but the user don't want to wait for the natural # termination of the script. # # Set it to 0 or a negative value for unlimited execution without warnings. lua-time-limit 5000 ################################ REDIS CLUSTER ############################### # # Normal Redis instances can't be part of a Redis Cluster; only nodes that are # started as cluster nodes can. In order to start a Redis instance as a # cluster node enable the cluster support uncommenting the following: # # cluster-enabled yes # Every cluster node has a cluster configuration file. This file is not # intended to be edited by hand. It is created and updated by Redis nodes. # Every Redis Cluster node requires a different cluster configuration file. # Make sure that instances running in the same system does not have # overlapping cluster configuration file names. # # cluster-config-file nodes-6379.conf # Cluster node timeout is the amount of milliseconds a node must be unreachable # for it to be considered in failure state. # Most other internal time limits are multiple of the node timeout. # # cluster-node-timeout 15000 # In order to setup your cluster make sure to read the documentation # available at http://redis.io web site. ################################## SLOW LOG ################################### # The Redis Slow Log is a system to log queries that exceeded a specified # execution time. The execution time does not include the I/O operations # like talking with the client, sending the reply and so forth, # but just the time needed to actually execute the command (this is the only # stage of command execution where the thread is blocked and can not serve # other requests in the meantime). # # You can configure the slow log with two parameters: one tells Redis # what is the execution time, in microseconds, to exceed in order for the # command to get logged, and the other parameter is the length of the # slow log. When a new command is logged the oldest one is removed from the # queue of logged commands. # The following time is expressed in microseconds, so 1000000 is equivalent # to one second. Note that a negative number disables the slow log, while # a value of zero forces the logging of every command. slowlog-log-slower-than 10000 # There is no limit to this length. Just be aware that it will consume memory. # You can reclaim memory used by the slow log with SLOWLOG RESET. slowlog-max-len 128 ############################# Event notification ############################## # Redis can notify Pub/Sub clients about events happening in the key space. # This feature is documented at http://redis.io/topics/keyspace-events # # For instance if keyspace events notification is enabled, and a client # performs a DEL operation on key "foo" stored in the Database 0, two # messages will be published via Pub/Sub: # # PUBLISH __keyspace@0__:foo del # PUBLISH __keyevent@0__:del foo # # It is possible to select the events that Redis will notify among a set # of classes. Every class is identified by a single character: # # K Keyspace events, published with __keyspace@__ prefix. # E Keyevent events, published with __keyevent@__ prefix. # g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ... # \ String commands # l List commands # s Set commands # h Hash commands # z Sorted set commands # x Expired events (events generated every time a key expires) # e Evicted events (events generated when a key is evicted for maxmemory) # A Alias for g\lshzxe, so that the "AKE" string means all the events. # # The "notify-keyspace-events" takes as argument a string that is composed # by zero or multiple characters. The empty string means that notifications # are disabled at all. # # Example: to enable list and generic events, from the point of view of the # event name, use: # # notify-keyspace-events Elg # # Example 2: to get the stream of the expired keys subscribing to channel # name __keyevent@0__:expired use: # # notify-keyspace-events Ex # # By default all notifications are disabled because most users don't need # this feature and the feature has some overhead. Note that if you don't # specify at least one of K or E, no events will be delivered. notify-keyspace-events "" ############################### ADVANCED CONFIG ############################### # Hashes are encoded using a memory efficient data structure when they have a # small number of entries, and the biggest entry does not exceed a given # threshold. These thresholds can be configured using the following directives. hash-max-ziplist-entries 512 hash-max-ziplist-value 64 # Similarly to hashes, small lists are also encoded in a special way in order # to save a lot of space. The special representation is only used when # you are under the following limits: list-max-ziplist-entries 512 list-max-ziplist-value 64 # Sets have a special encoding in just one case: when a set is composed # of just strings that happens to be integers in radix 10 in the range # of 64 bit signed integers. # The following configuration setting sets the limit in the size of the # set in order to use this special memory saving encoding. set-max-intset-entries 512 # Similarly to hashes and lists, sorted sets are also specially encoded in # order to save a lot of space. This encoding is only used when the length and # elements of a sorted set are below the following limits: zset-max-ziplist-entries 128 zset-max-ziplist-value 64 # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in # order to help rehashing the main Redis hash table (the one mapping top-level # keys to values). The hash table implementation Redis uses (see dict.c) # performs a lazy rehashing: the more operation you run into an hash table # that is rehashing, the more rehashing "steps" are performed, so if the # server is idle the rehashing is never complete and some more memory is used # by the hash table. # # The default is to use this millisecond 10 times every second in order to # active rehashing the main dictionaries, freeing memory when possible. # # If unsure: # use "activerehashing no" if you have hard latency requirements and it is # not a good thing in your environment that Redis can reply form time to time # to queries with 2 milliseconds delay. # # use "activerehashing yes" if you don't have such hard requirements but # want to free memory asap when possible. activerehashing yes # The client output buffer limits can be used to force disconnection of clients # that are not reading data from the server fast enough for some reason (a # common reason is that a Pub/Sub client can't consume messages as fast as the # publisher can produce them). # # The limit can be set differently for the three different classes of clients: # # normal -> normal clients # slave -> slave clients and MONITOR clients # pubsub -> clients subcribed to at least one pubsub channel or pattern # # The syntax of every client-output-buffer-limit directive is the following: # # client-output-buffer-limit # # A client is immediately disconnected once the hard limit is reached, or if # the soft limit is reached and remains reached for the specified number of # seconds (continuously). # So for instance if the hard limit is 32 megabytes and the soft limit is # 16 megabytes / 10 seconds, the client will get disconnected immediately # if the size of the output buffers reach 32 megabytes, but will also get # disconnected if the client reaches 16 megabytes and continuously overcomes # the limit for 10 seconds. # # By default normal clients are not limited because they don't receive data # without asking (in a push way), but just after a request, so only # asynchronous clients may create a scenario where data is requested faster # than it can read. # # Instead there is a default limit for pubsub and slave clients, since # subscribers and slaves receive data in a push fashion. # # Both the hard or the soft limit can be disabled by setting them to zero. client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 # Redis calls an internal function to perform many background tasks, like # closing connections of clients in timeot, purging expired keys that are # never requested, and so forth. # # Not all tasks are perforemd with the same frequency, but Redis checks for # tasks to perform accordingly to the specified "hz" value. # # By default "hz" is set to 10. Raising the value will use more CPU when # Redis is idle, but at the same time will make Redis more responsive when # there are many keys expiring at the same time, and timeouts may be # handled with more precision. # # The range is between 1 and 500, however a value over 100 is usually not # a good idea. Most users should use the default of 10 and raise this up to # 100 only in environments where very low latency is required. hz 10 # When a child rewrites the AOF file, if the following option is enabled # the file will be fsync-ed every 32 MB of data generated. This is useful # in order to commit the file to the disk more incrementally and avoid # big latency spikes. aof-rewrite-incremental-fsync yes ################################## INCLUDES ################################### # Include one or more other config files here. This is useful if you # have a standard template that goes to all Redis server but also need # to customize a few per-server settings. Include files can include # other files, so use this wisely. # # include /path/to/local.conf # include /path/to/other.conf nutcracker-0.4.1+dfsg/tests/conf/control.sh0000775000000000000000000000065212542173515017412 0ustar rootroot#!/bin/bash start() { stop ulimit -c unlimited pushd . > /dev/null cd `dirname $$0` ${startcmd} popd } stop() { pkill -9 -f '${runcmd}' } case C"$$1" in C) echo "Usage: $$0 {start|stop}" ;; Cstart) start echo "Done!" ;; Cstop) stop echo "Done!" ;; C*) echo "Usage: $$0 {start|stop}" ;; esac nutcracker-0.4.1+dfsg/tests/conf/conf.py0000664000000000000000000000066312542173515016674 0ustar rootroot#coding: utf-8 import os import sys PWD = os.path.dirname(os.path.realpath(__file__)) WORKDIR = os.path.join(PWD, '../') BINARYS = { 'REDIS_SERVER_BINS' : os.path.join(WORKDIR, '_binaries/redis-*'), 'REDIS_CLI' : os.path.join(WORKDIR, '_binaries/redis-cli'), 'MEMCACHED_BINS' : os.path.join(WORKDIR, '_binaries/memcached'), 'NUTCRACKER_BINS' : os.path.join(WORKDIR, '_binaries/nutcracker'), } nutcracker-0.4.1+dfsg/tests/test_system/0000775000000000000000000000000012542173515017026 5ustar rootrootnutcracker-0.4.1+dfsg/tests/test_system/__init__.py0000664000000000000000000000000012542173515021125 0ustar rootrootnutcracker-0.4.1+dfsg/tests/test_system/test_reload.py0000664000000000000000000001141712542173515021711 0ustar rootroot#!/usr/bin/env python #coding: utf-8 #file : test_reload.py #author : ning #date : 2014-09-03 12:28:16 import os import sys import redis PWD = os.path.dirname(os.path.realpath(__file__)) WORKDIR = os.path.join(PWD,'../') sys.path.append(os.path.join(WORKDIR,'lib/')) sys.path.append(os.path.join(WORKDIR,'conf/')) import conf from server_modules import * from utils import * from nose import with_setup CLUSTER_NAME = 'ntest' nc_verbose = int(getenv('T_VERBOSE', 5)) mbuf = int(getenv('T_MBUF', 512)) large = int(getenv('T_LARGE', 1000)) T_RELOAD_DELAY = 3 + 1 all_redis = [ RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/', CLUSTER_NAME, 'redis-2100'), RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/', CLUSTER_NAME, 'redis-2101'), ] nc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME, all_redis, mbuf=mbuf, verbose=nc_verbose) def _setup(): print 'setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose) for r in all_redis + [nc]: r.deploy() r.stop() r.start() def _teardown(): for r in all_redis + [nc]: assert(r._alive()) r.stop() def get_tcp_conn(host, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.settimeout(.3) return s def send_cmd(s, req, resp): s.sendall(req) data = s.recv(10000) assert(data == resp) @with_setup(_setup, _teardown) def test_reload_with_old_conf(): if nc.version() < '0.4.2': print 'Ignore test_reload for version %s' % nc.version() return pid = nc.pid() # print 'old pid:', pid r = redis.Redis(nc.host(), nc.port()) r.set('k', 'v') conn = get_tcp_conn(nc.host(), nc.port()) send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') # nc.reload() is same as nc.stop() and nc.start() nc.reload() time.sleep(.01) #it need time for the old process fork new process. # the old connection is still ok in T_RELOAD_DELAY seconds send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') # conn2 should connect to new instance conn2 = get_tcp_conn(nc.host(), nc.port()) send_cmd(conn2, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') # the old connection is still ok in T_RELOAD_DELAY seconds send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') time.sleep(T_RELOAD_DELAY) assert(pid != nc.pid()) # assert the old connection is closed. send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '') # conn2 should survive send_cmd(conn2, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') r = redis.Redis(nc.host(), nc.port()) rst = r.set('k', 'v') assert(r.get('k') == 'v') @with_setup(_setup, _teardown) def test_new_port(): if nc.version() < '0.4.2': print 'Ignore test_reload for version %s' % nc.version() return r = redis.Redis(nc.host(), nc.port()) r.set('k', 'v') content = ''' reload_test: listen: 0.0.0.0:4101 hash: fnv1a_64 distribution: modula redis: true timeout: 400 servers: - 127.0.0.1:2100:1 redis-2100 - 127.0.0.1:2101:1 redis-2101 ''' nc.set_config(content) time.sleep(T_RELOAD_DELAY) r1 = redis.Redis(nc.host(), nc.port()) r2 = redis.Redis(nc.host(), 4101) assert_fail('Connection refused', r1.get, 'k') assert(r2.get('k') == 'v') @with_setup(_setup, _teardown) def test_pool_add_del(): if nc.version() < '0.4.2': print 'Ignore test_reload for version %s' % nc.version() return r = redis.Redis(nc.host(), nc.port()) r.set('k', 'v') content = ''' reload_test: listen: 0.0.0.0:4100 hash: fnv1a_64 distribution: modula redis: true servers: - 127.0.0.1:2100:1 redis-2100 - 127.0.0.1:2101:1 redis-2101 reload_test2: listen: 0.0.0.0:4101 hash: fnv1a_64 distribution: modula redis: true servers: - 127.0.0.1:2100:1 redis-2100 - 127.0.0.1:2101:1 redis-2101 ''' nc.set_config(content) time.sleep(T_RELOAD_DELAY) r1 = redis.Redis(nc.host(), nc.port()) r2 = redis.Redis(nc.host(), 4101) assert(r1.get('k') == 'v') assert(r2.get('k') == 'v') content = ''' reload_test: listen: 0.0.0.0:4102 hash: fnv1a_64 distribution: modula redis: true preconnect: true servers: - 127.0.0.1:2100:1 redis-2100 - 127.0.0.1:2101:1 redis-2101 ''' nc.set_config(content) time.sleep(T_RELOAD_DELAY) pid = nc.pid() print system('ls -l /proc/%s/fd/' % pid) r3 = redis.Redis(nc.host(), 4102) assert_fail('Connection refused', r1.get, 'k') assert_fail('Connection refused', r2.get, 'k') assert(r3.get('k') == 'v') fds = system('ls -l /proc/%s/fd/' % pid) sockets = [s for s in fds.split('\n') if strstr(s, 'socket:') ] # pool + stat + 2 backend + 1 client assert(len(sockets) == 5) nutcracker-0.4.1+dfsg/tests/test_redis/0000775000000000000000000000000012542173515016610 5ustar rootrootnutcracker-0.4.1+dfsg/tests/test_redis/test_pipeline.py0000664000000000000000000000267512542173515022040 0ustar rootroot#!/usr/bin/env python #coding: utf-8 from common import * def test_pipeline(): r = getconn() pipe = r.pipeline(transaction = False) pipe.set('a', 'a1').get('a').zadd('z', z1=1).zadd('z', z2=4) pipe.zincrby('z', 'z1').zrange('z', 0, 5, withscores=True) assert pipe.execute() == \ [ True, 'a1', True, True, 2.0, [('z1', 2.0), ('z2', 4)], ] def test_invalid_pipeline(): r = getconn() pipe = r.pipeline(transaction = False) pipe.set('a', 1).set('b', 2).lpush('a', 3).set('d', 4).get('a') result = pipe.execute(raise_on_error = False) assert result[0] assert result[1] # we can't lpush to a key that's a string value, so this should # be a ResponseError exception assert isinstance(result[2], redis.ResponseError) # since this isn't a transaction, the other commands after the # error are still executed assert result[3] assert result[4] == '1' # make sure the pipe was restored to a working state assert pipe.set('z', 'zzz').execute() == [True] def test_parse_error_raised(): r = getconn() pipe = r.pipeline(transaction = False) # the zrem is invalid because we don't pass any keys to it pipe.set('a', 1).zrem('b').set('b', 2) result = pipe.execute(raise_on_error = False) assert result[0] assert isinstance(result[1], redis.ResponseError) assert result[2] nutcracker-0.4.1+dfsg/tests/test_redis/test_commands.py0000664000000000000000000000414212542173515022023 0ustar rootroot#!/usr/bin/env python #coding: utf-8 from common import * def test_linsert(): r = getconn() r.rpush('mylist', 'Hello') r.rpush('mylist', 'World') r.linsert('mylist', 'BEFORE', 'World', 'There') rst = r.lrange('mylist', 0, -1) assert(rst == ['Hello', 'There', 'World']) def test_lpush_lrange(): r = getconn() vals = ['vvv-%s' % i for i in range(10) ] assert([] == r.lrange('mylist', 0, -1)) r.lpush('mylist', *vals) rst = r.lrange('mylist', 0, -1) assert(10 == len(rst)) def test_hscan(): r = getconn() kv = {'kkk-%s' % i : 'vvv-%s' % i for i in range(10)} r.hmset('a', kv) cursor, dic = r.hscan('a') assert(str(cursor) == '0') assert(dic == kv) cursor, dic = r.hscan('a', match='kkk-5') assert(str(cursor) == '0') assert(dic == {'kkk-5': 'vvv-5'}) def test_hscan_large(): r = getconn() kv = {'x'* 100 + 'kkk-%s' % i : 'vvv-%s' % i for i in range(1000)} r.hmset('a', kv) cursor = '0' dic = {} while True: cursor, t = r.hscan('a', cursor, count=10) for k, v in t.items(): dic[k] = v if '0' == str(cursor): break assert(dic == kv) cursor, dic = r.hscan('a', '0', match='*kkk-5*', count=1000) if str(cursor) == '0': assert(len(dic) == 111) else: assert(len(dic) == 111) #again. cursor, dic = r.hscan('a', cursor, match='*kkk-5*', count=1000) assert(str(cursor) == '0') assert(len(dic) == 0) def test_zscan(): r = getconn() r.zadd('a', 'a', 1, 'b', 2, 'c', 3) cursor, pairs = r.zscan('a') assert(str(cursor) == '0') assert(set(pairs) == set([('a', 1), ('b', 2), ('c', 3)])) cursor, pairs = r.zscan('a', match='a') assert(str(cursor) == '0') assert(set(pairs) == set([('a', 1)])) def test_sscan(): r = getconn() r.sadd('a', 1, 2, 3) cursor, members = r.sscan('a') assert(str(cursor) == '0') assert(set(members) == set(['1', '2', '3'])) cursor, members = r.sscan('a', match='1') assert(str(cursor) == '0') assert(set(members) == set(['1'])) nutcracker-0.4.1+dfsg/tests/test_redis/test_basic.py0000664000000000000000000000540412542173515021305 0ustar rootroot#!/usr/bin/env python #coding: utf-8 from common import * def test_setget(): r = getconn() rst = r.set('k', 'v') assert(r.get('k') == 'v') def test_msetnx(): r = getconn() #not supported keys = default_kv.keys() assert_fail('Socket closed|Connection closed', r.msetnx,**default_kv) def test_null_key(): r = getconn() rst = r.set('', 'v') assert(r.get('') == 'v') rst = r.set('', '') assert(r.get('') == '') kv = {'' : 'val', 'k': 'v'} ret = r.mset(**kv) assert(r.get('') == 'val') def test_ping_quit(): r = getconn() assert(r.ping() == True) #get set rst = r.set('k', 'v') assert(r.get('k') == 'v') assert_fail('Socket closed|Connection closed', r.execute_command, 'QUIT') def test_slow_req(): r = getconn() kv = {'mkkk-%s' % i : 'mvvv-%s' % i for i in range(500000)} pipe = r.pipeline(transaction=False) pipe.set('key-1', 'v1') pipe.get('key-1') pipe.hmset('xxx', kv) pipe.get('key-2') pipe.get('key-3') assert_fail('timed out', pipe.execute) def test_signal(): #init nc.cleanlog() nc.signal('HUP') nc.signal('HUP') nc.signal('TTIN') nc.signal('TTOU') nc.signal('SEGV') time.sleep(.3) log = file(nc.logfile()).read() assert(strstr(log, 'HUP')) assert(strstr(log, 'TTIN')) assert(strstr(log, 'TTOU')) assert(strstr(log, 'SEGV')) #recover nc.start() def test_nc_stats(): nc.stop() #reset counters nc.start() r = getconn() kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(10)} for k, v in kv.items(): r.set(k, v) r.get(k) def get_stat(name): time.sleep(1) stat = nc._info_dict() #pprint(stat) if name in ['client_connections', 'client_eof', 'client_err', \ 'forward_error', 'fragments', 'server_ejects']: return stat[CLUSTER_NAME][name] #sum num of each server ret = 0 for k, v in stat[CLUSTER_NAME].items(): if type(v) == dict: ret += v[name] return ret assert(get_stat('requests') == 20) assert(get_stat('responses') == 20) ##### mget keys = kv.keys() r.mget(keys) #for version<=0.3.0 #assert(get_stat('requests') == 30) #assert(get_stat('responses') == 30) #for mget-improve assert(get_stat('requests') == 22) assert(get_stat('responses') == 22) def test_issue_323(): # do on redis r = all_redis[0] c = redis.Redis(r.host(), r.port()) assert([1, 'OK'] == c.eval("return {1, redis.call('set', 'x', '1')}", 1, 'tmp')) # do on twemproxy c = getconn() assert([1, 'OK'] == c.eval("return {1, redis.call('set', 'x', '1')}", 1, 'tmp')) def setup_and_wait(): time.sleep(60*60) nutcracker-0.4.1+dfsg/tests/test_redis/test_protocol.py0000664000000000000000000000334512542173515022067 0ustar rootroot#!/usr/bin/env python from common import * from pprint import pprint def get_conn(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((nc.host(), nc.port())) s.settimeout(.3) return s def _test(req, resp, sleep=0): s = get_conn() for i in req: s.sendall(i) time.sleep(sleep) s.settimeout(.3) data = s.recv(10000) assert(data == resp) def test_slow(): req = '*1\r\n$4\r\nPING\r\n' resp = '+PONG\r\n' if large > 1000: sleep = 1 else: sleep = .1 _test(req, resp, sleep) def test_pingpong(): req = '*1\r\n$4\r\nPING\r\n' resp = '+PONG\r\n' _test(req, resp) def test_quit(): if nc.version() < '0.4.2': return req = '*1\r\n$4\r\nQUIT\r\n' resp = '+OK\r\n' _test(req, resp) def test_quit_without_recv(): if nc.version() < '0.4.2': return req = '*1\r\n$4\r\nQUIT\r\n' resp = '+OK\r\n' s = get_conn() s.sendall(req) s.close() info = nc._info_dict() #pprint(info) assert(info['ntest']['client_err'] == 1) def _test_bad(req): s = get_conn() s.sendall(req) data = s.recv(10000) print data assert('' == s.recv(1000)) # peer is closed def test_badreq(): reqs = [ # '*1\r\n$3\r\nPING\r\n', '\r\n', # '*3abcdefg\r\n', '*3\r\n*abcde\r\n', '*4\r\n$4\r\nMSET\r\n$1\r\nA\r\n$1\r\nA\r\n$1\r\nA\r\n', '*2\r\n$4\r\nMSET\r\n$1\r\nA\r\n', # '*3\r\n$abcde\r\n', # '*3\r\n$3abcde\r\n', # '*3\r\n$3\r\nabcde\r\n', ] for req in reqs: _test_bad(req) def test_wrong_argc(): s = get_conn() s.sendall('*1\r\n$3\r\nGET\r\n') assert('' == s.recv(1000)) # peer is closed nutcracker-0.4.1+dfsg/tests/test_redis/test_mget_mset.py0000664000000000000000000001364712542173515022220 0ustar rootroot#!/usr/bin/env python #coding: utf-8 from common import * def test_mget_mset(kv=default_kv): r = getconn() def insert_by_pipeline(): pipe = r.pipeline(transaction=False) for k, v in kv.items(): pipe.set(k, v) pipe.execute() def insert_by_mset(): ret = r.mset(**kv) #insert_by_mset() #only the mget-imporve branch support this try: insert_by_mset() #only the mget-imporve branch support this except: insert_by_pipeline() keys = kv.keys() #mget to check vals = r.mget(keys) for i, k in enumerate(keys): assert(kv[k] == vals[i]) #del assert (len(keys) == r.delete(*keys) ) #mget again vals = r.mget(keys) for i, k in enumerate(keys): assert(None == vals[i]) def test_mget_mset_on_key_not_exist(kv=default_kv): r = getconn() def insert_by_pipeline(): pipe = r.pipeline(transaction=False) for k, v in kv.items(): pipe.set(k, v) pipe.execute() def insert_by_mset(): ret = r.mset(**kv) try: insert_by_mset() #only the mget-imporve branch support this except: insert_by_pipeline() keys = kv.keys() keys2 = ['x-'+k for k in keys] keys = keys + keys2 random.shuffle(keys) #mget to check vals = r.mget(keys) for i, k in enumerate(keys): if k in kv: assert(kv[k] == vals[i]) else: assert(vals[i] == None) #del assert (len(kv) == r.delete(*keys) ) #mget again vals = r.mget(keys) for i, k in enumerate(keys): assert(None == vals[i]) def test_mget_mset_large(): for cnt in range(171, large, 171): kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(cnt)} test_mget_mset(kv) def test_mget_special_key(cnt=5): #key length = 512-48-1 kv = {} for i in range(cnt): k = 'kkk-%s' % i k = k + 'x'*(512-48-1-len(k)) kv[k] = 'vvv' test_mget_mset(kv) def test_mget_special_key_2(cnt=5): #key length = 512-48-2 kv = {} for i in range(cnt): k = 'kkk-%s' % i k = k + 'x'*(512-48-2-len(k)) kv[k] = 'vvv'*9 test_mget_mset(kv) def test_mget_on_backend_down(): #one backend down r = redis.Redis(nc.host(), nc.port()) assert_equal(None, r.get('key-2')) assert_equal(None, r.get('key-1')) all_redis[0].stop() assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-1') assert_fail('Connection refused|reset by peer|Broken pipe', r.get, 'key-1') assert_equal(None, r.get('key-2')) keys = ['key-1', 'key-2', 'kkk-3'] assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, *keys) #all backend down all_redis[1].stop() r = redis.Redis(nc.host(), nc.port()) assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-1') assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-2') keys = ['key-1', 'key-2', 'kkk-3'] assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, *keys) for r in all_redis: r.start() def test_mset_on_backend_down(): all_redis[0].stop() r = redis.Redis(nc.host(),nc.port()) assert_fail('Connection refused|Broken pipe',r.mset,default_kv) all_redis[1].stop() assert_fail('Connection refused|Broken pipe',r.mset,default_kv) for r in all_redis: r.start() def test_mget_pipeline(): r = getconn() pipe = r.pipeline(transaction=False) for k,v in default_kv.items(): pipe.set(k,v) keys = default_kv.keys() pipe.mget(keys) kv = {} for i in range(large): kv['kkk-%s' % i] = os.urandom(100) for k,v in kv.items(): pipe.set(k,v) for k in kv.keys(): pipe.get(k) rst = pipe.execute() #print rst #check the result keys = default_kv.keys() #mget to check vals = r.mget(keys) for i, k in enumerate(keys): assert(kv[k] == vals[i]) #del assert (len(keys) == r.delete(*keys) ) #mget again vals = r.mget(keys) for i, k in enumerate(keys): assert(None == vals[i]) def test_multi_delete_normal(): r = getconn() for i in range(100): r.set('key-%s'%i, 'val-%s'%i) for i in range(100): assert_equal('val-%s'%i, r.get('key-%s'%i) ) keys = ['key-%s'%i for i in range(100)] assert_equal(100, r.delete(*keys)) for i in range(100): assert_equal(None, r.get('key-%s'%i) ) def test_multi_delete_on_readonly(): all_redis[0].slaveof(all_redis[1].args['host'], all_redis[1].args['port']) r = redis.Redis(nc.host(), nc.port()) # got "READONLY You can't write against a read only slave" assert_fail('READONLY|Invalid', r.delete, 'key-1') assert_equal(0, r.delete('key-2')) assert_fail('READONLY|Invalid', r.delete, 'key-3') keys = ['key-1', 'key-2', 'kkk-3'] assert_fail('Invalid argument', r.delete, *keys) # got "Invalid argument" def test_multi_delete_on_backend_down(): #one backend down all_redis[0].stop() r = redis.Redis(nc.host(), nc.port()) assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, 'key-1') assert_equal(None, r.get('key-2')) keys = ['key-1', 'key-2', 'kkk-3'] assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, *keys) #all backend down all_redis[1].stop() r = redis.Redis(nc.host(), nc.port()) assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, 'key-1') assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, 'key-2') keys = ['key-1', 'key-2', 'kkk-3'] assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, *keys) for r in all_redis: r.start() def test_multi_delete_20140525(): r = getconn() cnt = 126 keys = ['key-%s'%i for i in range(cnt)] pipe = r.pipeline(transaction=False) pipe.mget(keys) pipe.delete(*keys) pipe.execute() nutcracker-0.4.1+dfsg/tests/test_redis/common.py0000664000000000000000000000240012542173515020446 0ustar rootroot#!/usr/bin/env python #coding: utf-8 import os import sys import redis PWD = os.path.dirname(os.path.realpath(__file__)) WORKDIR = os.path.join(PWD,'../') sys.path.append(os.path.join(WORKDIR,'lib/')) sys.path.append(os.path.join(WORKDIR,'conf/')) import conf from server_modules import * from utils import * CLUSTER_NAME = 'ntest' nc_verbose = int(getenv('T_VERBOSE', 5)) mbuf = int(getenv('T_MBUF', 512)) large = int(getenv('T_LARGE', 1000)) all_redis = [ RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/', CLUSTER_NAME, 'redis-2100'), RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/', CLUSTER_NAME, 'redis-2101'), ] nc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME, all_redis, mbuf=mbuf, verbose=nc_verbose) def setup(): print 'setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose) for r in all_redis + [nc]: r.clean() r.deploy() r.stop() r.start() def teardown(): for r in all_redis + [nc]: assert(r._alive()) r.stop() default_kv = {'kkk-%s' % i : 'vvv-%s' % i for i in range(10)} def getconn(): for r in all_redis: c = redis.Redis(r.host(), r.port()) c.flushdb() r = redis.Redis(nc.host(), nc.port()) return r nutcracker-0.4.1+dfsg/tests/test_redis/__init__.py0000664000000000000000000000000012542173515020707 0ustar rootrootnutcracker-0.4.1+dfsg/tests/test_redis/test_auth.py0000664000000000000000000000732312542173515021167 0ustar rootroot#!/usr/bin/env python #coding: utf-8 from common import * all_redis = [ RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/', CLUSTER_NAME, 'redis-2100', auth = 'hellopasswd'), RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/', CLUSTER_NAME, 'redis-2101', auth = 'hellopasswd'), ] nc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME, all_redis, mbuf=mbuf, verbose=nc_verbose, redis_auth = 'hellopasswd') nc_badpass = NutCracker('127.0.0.1', 4101, '/tmp/r/nutcracker-4101', CLUSTER_NAME, all_redis, mbuf=mbuf, verbose=nc_verbose, redis_auth = 'badpasswd') nc_nopass = NutCracker('127.0.0.1', 4102, '/tmp/r/nutcracker-4102', CLUSTER_NAME, all_redis, mbuf=mbuf, verbose=nc_verbose) def setup(): print 'setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose) for r in all_redis + [nc, nc_badpass, nc_nopass]: r.clean() r.deploy() r.stop() r.start() def teardown(): for r in all_redis + [nc, nc_badpass, nc_nopass]: assert(r._alive()) r.stop() default_kv = {'kkk-%s' % i : 'vvv-%s' % i for i in range(10)} def getconn(): r = redis.Redis(nc.host(), nc.port()) return r ''' cases: redis proxy case 1 1 test_auth_basic 1 bad test_badpass_on_proxy 1 0 test_nopass_on_proxy 0 0 already tested on other case 0 1 ''' def test_auth_basic(): # we hope to have same behavior when the server is redis or twemproxy conns = [ redis.Redis(all_redis[0].host(), all_redis[0].port()), redis.Redis(nc.host(), nc.port()), ] for r in conns: assert_fail('NOAUTH|operation not permitted', r.ping) assert_fail('NOAUTH|operation not permitted', r.set, 'k', 'v') assert_fail('NOAUTH|operation not permitted', r.get, 'k') # bad passwd assert_fail('invalid password', r.execute_command, 'AUTH', 'badpasswd') # everything is ok after auth r.execute_command('AUTH', 'hellopasswd') r.set('k', 'v') assert(r.ping() == True) assert(r.get('k') == 'v') # auth fail here, should we return ok or not => we will mark the conn state as not authed assert_fail('invalid password', r.execute_command, 'AUTH', 'badpasswd') assert_fail('NOAUTH|operation not permitted', r.ping) assert_fail('NOAUTH|operation not permitted', r.get, 'k') def test_nopass_on_proxy(): r = redis.Redis(nc_nopass.host(), nc_nopass.port()) # if you config pass on redis but not on twemproxy, # twemproxy will reply ok for ping, but once you do get/set, you will get errmsg from redis assert(r.ping() == True) assert_fail('NOAUTH|operation not permitted', r.set, 'k', 'v') assert_fail('NOAUTH|operation not permitted', r.get, 'k') # proxy has no pass, when we try to auth assert_fail('Client sent AUTH, but no password is set', r.execute_command, 'AUTH', 'anypasswd') pass def test_badpass_on_proxy(): r = redis.Redis(nc_badpass.host(), nc_badpass.port()) assert_fail('NOAUTH|operation not permitted', r.ping) assert_fail('NOAUTH|operation not permitted', r.set, 'k', 'v') assert_fail('NOAUTH|operation not permitted', r.get, 'k') # we can auth with bad pass (twemproxy will say ok for this) r.execute_command('AUTH', 'badpasswd') # after that, we still got NOAUTH for get/set (return from redis-server) assert(r.ping() == True) assert_fail('NOAUTH|operation not permitted', r.set, 'k', 'v') assert_fail('NOAUTH|operation not permitted', r.get, 'k') def setup_and_wait(): time.sleep(60*60) nutcracker-0.4.1+dfsg/tests/test_redis/test_mget_large_binary.py0000664000000000000000000000153512542173515023677 0ustar rootroot#!/usr/bin/env python #coding: utf-8 from common import * from test_mget_mset import test_mget_mset as _mget_mset #force to use large mbuf, we need to copy the setup/teardown here.. mbuf = 64*1024 nc = NutCracker(nc.host(), nc.port(), '/tmp/r/nutcracker-4100', CLUSTER_NAME, all_redis, mbuf=mbuf, verbose=nc_verbose) def setup(): print 'special setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose) for r in all_redis + [nc]: r.deploy() r.stop() r.start() def teardown(): for r in all_redis + [nc]: assert(r._alive()) r.stop() ###################################################### def test_mget_binary_value(cnt=5): kv = {} for i in range(cnt): kv['kkk-%s' % i] = os.urandom(1024*1024*16+1024) #16M for i in range(cnt): kv['kkk2-%s' % i] = '' _mget_mset(kv) nutcracker-0.4.1+dfsg/tests/README.rst0000664000000000000000000000274512542173515016142 0ustar rootrootPython testing facilities for twemproxy, this test suite is based on https://github.com/idning/redis-mgr already add to https://travis-ci.org/idning/twemproxy as travis-ci see https://github.com/idning/twemproxy/blob/travis-ci/travis.sh usage ===== 1. install dependency:: pip install nose pip install git+https://github.com/andymccurdy/redis-py.git@2.9.0 pip install git+https://github.com/idning/python-memcached.git#egg=memcache 2. copy binarys to _binaries/:: _binaries/ |-- nutcracker |-- redis-benchmark |-- redis-check-aof |-- redis-check-dump |-- redis-cli |-- redis-sentinel |-- redis-server |-- memcached 3. run:: $ nosetests -v test_del.test_multi_delete_on_readonly ... ok test_mget.test_mget ... ok ---------------------------------------------------------------------- Ran 2 tests in 4.483s OK 4. add A case:: cp tests/test_del.py tests/test_xxx.py vim tests/test_xxx.py variables ========= :: export T_VERBOSE=9 will start nutcracker with '-v 9' (default:4) export T_MBUF=512 will start nutcracker whit '-m 512' (default:521) export T_LARGE=10000 will test 10000 keys for mget/mset (default:1000) T_LOGFILE: - to put test log on stderr:: export T_LOGFILE=- - to put test log on t.log:: export T_LOGFILE=t.log or:: unset T_LOGFILE notes ===== - After all the tests. you may got a core because we have a case in test_signal which will send SEGV to nutcracker nutcracker-0.4.1+dfsg/tests/lib/0000775000000000000000000000000012542173515015211 5ustar rootrootnutcracker-0.4.1+dfsg/tests/lib/server_modules.py0000664000000000000000000002422712542173515020630 0ustar rootroot#!/usr/bin/env python #coding: utf-8 #file : server_modules.py #author : ning #date : 2014-02-24 13:00:28 import os import sys from utils import * import conf class Base: ''' Sub class should implement: _alive, _pre_deploy, status, and init self.args ''' def __init__(self, name, host, port, path): self.args = { 'name' : name, 'host' : host, 'port' : port, 'path' : path, #startcmd and runcmd will used to generate the control script #used for the start cmd 'startcmd' : '', #process name you see in `ps -aux`, used this to generate stop cmd 'runcmd' : '', 'logfile' : '', } def __str__(self): return TT('[$name:$host:$port]', self.args) def deploy(self): logging.info('deploy %s' % self) self._run(TTCMD('mkdir -p $path/bin && \ mkdir -p $path/conf && \ mkdir -p $path/log && \ mkdir -p $path/data', self.args)) self._pre_deploy() self._gen_control_script() def _gen_control_script(self): content = file(os.path.join(WORKDIR, 'conf/control.sh')).read() content = TT(content, self.args) control_filename = TT('${path}/${name}_control', self.args) fout = open(control_filename, 'w+') fout.write(content) fout.close() os.chmod(control_filename, 0755) def start(self): if self._alive(): logging.warn('%s already running' %(self) ) return logging.debug('starting %s' % self) t1 = time.time() sleeptime = .1 cmd = TT("cd $path && ./${name}_control start", self.args) self._run(cmd) while not self._alive(): lets_sleep(sleeptime) if sleeptime < 5: sleeptime *= 2 else: sleeptime = 5 logging.warn('%s still not alive' % self) t2 = time.time() logging.info('%s start ok in %.2f seconds' %(self, t2-t1) ) def stop(self): if not self._alive(): logging.warn('%s already stop' %(self) ) return cmd = TT("cd $path && ./${name}_control stop", self.args) self._run(cmd) t1 = time.time() while self._alive(): lets_sleep() t2 = time.time() logging.info('%s stop ok in %.2f seconds' %(self, t2-t1) ) def pid(self): cmd = TT("pgrep -f '^$runcmd'", self.args) return self._run(cmd) def status(self): logging.warn("status: not implement") def _alive(self): logging.warn("_alive: not implement") def _run(self, raw_cmd): ret = system(raw_cmd, logging.debug) logging.debug('return : [%d] [%s] ' % (len(ret), shorten(ret)) ) return ret def clean(self): cmd = TT("rm -rf $path", self.args) self._run(cmd) def host(self): return self.args['host'] def port(self): return self.args['port'] class RedisServer(Base): def __init__(self, host, port, path, cluster_name, server_name, auth = None): Base.__init__(self, 'redis', host, port, path) self.args['startcmd'] = TT('bin/redis-server conf/redis.conf', self.args) self.args['runcmd'] = TT('redis-server \*:$port', self.args) self.args['conf'] = TT('$path/conf/redis.conf', self.args) self.args['pidfile'] = TT('$path/log/redis.pid', self.args) self.args['logfile'] = TT('$path/log/redis.log', self.args) self.args['dir'] = TT('$path/data', self.args) self.args['REDIS_CLI'] = conf.BINARYS['REDIS_CLI'] self.args['cluster_name'] = cluster_name self.args['server_name'] = server_name self.args['auth'] = auth def _info_dict(self): cmd = TT('$REDIS_CLI -h $host -p $port INFO', self.args) if self.args['auth']: cmd = TT('$REDIS_CLI -h $host -p $port -a $auth INFO', self.args) info = self._run(cmd) info = [line.split(':', 1) for line in info.split('\r\n') if not line.startswith('#')] info = [i for i in info if len(i) > 1] return defaultdict(str, info) #this is a defaultdict, be Notice def _ping(self): cmd = TT('$REDIS_CLI -h $host -p $port PING', self.args) if self.args['auth']: cmd = TT('$REDIS_CLI -h $host -p $port -a $auth PING', self.args) return self._run(cmd) def _alive(self): return strstr(self._ping(), 'PONG') def _gen_conf(self): content = file(os.path.join(WORKDIR, 'conf/redis.conf')).read() content = TT(content, self.args) if self.args['auth']: content += '\r\nrequirepass %s' % self.args['auth'] return content def _pre_deploy(self): self.args['BINS'] = conf.BINARYS['REDIS_SERVER_BINS'] self._run(TT('cp $BINS $path/bin/', self.args)) fout = open(TT('$path/conf/redis.conf', self.args), 'w+') fout.write(self._gen_conf()) fout.close() def status(self): uptime = self._info_dict()['uptime_in_seconds'] if uptime: logging.info('%s uptime %s seconds' % (self, uptime)) else: logging.error('%s is down' % self) def isslaveof(self, master_host, master_port): info = self._info_dict() if info['master_host'] == master_host and \ int(info['master_port']) == master_port: logging.debug('already slave of %s:%s' % (master_host, master_port)) return True def slaveof(self, master_host, master_port): cmd = 'SLAVEOF %s %s' % (master_host, master_port) return self.rediscmd(cmd) def rediscmd(self, cmd): args = copy.deepcopy(self.args) args['cmd'] = cmd cmd = TT('$REDIS_CLI -h $host -p $port $cmd', args) logging.info('%s %s' % (self, cmd)) return self._run(cmd) class Memcached(Base): def __init__(self, host, port, path, cluster_name, server_name): Base.__init__(self, 'memcached', host, port, path) self.args['startcmd'] = TT('bin/memcached -d -p $port', self.args) self.args['runcmd'] = self.args['startcmd'] self.args['cluster_name'] = cluster_name self.args['server_name'] = server_name def _alive(self): cmd = TT('echo "stats" | socat - TCP:$host:$port', self.args) ret = self._run(cmd) return strstr(ret, 'END') def _pre_deploy(self): self.args['BINS'] = conf.BINARYS['MEMCACHED_BINS'] self._run(TT('cp $BINS $path/bin/', self.args)) class NutCracker(Base): def __init__(self, host, port, path, cluster_name, masters, mbuf=512, verbose=5, is_redis=True, redis_auth=None): Base.__init__(self, 'nutcracker', host, port, path) self.masters = masters self.args['mbuf'] = mbuf self.args['verbose'] = verbose self.args['redis_auth'] = redis_auth self.args['conf'] = TT('$path/conf/nutcracker.conf', self.args) self.args['pidfile'] = TT('$path/log/nutcracker.pid', self.args) self.args['logfile'] = TT('$path/log/nutcracker.log', self.args) self.args['status_port'] = self.args['port'] + 1000 self.args['startcmd'] = TTCMD('bin/nutcracker -d -c $conf -o $logfile \ -p $pidfile -s $status_port \ -v $verbose -m $mbuf -i 1', self.args) self.args['runcmd'] = TTCMD('bin/nutcracker -d -c $conf -o $logfile \ -p $pidfile -s $status_port', self.args) self.args['cluster_name']= cluster_name self.args['is_redis']= str(is_redis).lower() def _alive(self): return self._info_dict() def _gen_conf_section(self): template = ' - $host:$port:1 $server_name' cfg = '\n'.join([TT(template, master.args) for master in self.masters]) return cfg def _gen_conf(self): content = ''' $cluster_name: listen: 0.0.0.0:$port hash: fnv1a_64 distribution: modula preconnect: true auto_eject_hosts: false redis: $is_redis backlog: 512 timeout: 400 client_connections: 0 server_connections: 1 server_retry_timeout: 2000 server_failure_limit: 2 servers: ''' if self.args['redis_auth']: content = content.replace('redis: $is_redis', 'redis: $is_redis\r\n redis_auth: $redis_auth') content = TT(content, self.args) return content + self._gen_conf_section() def _pre_deploy(self): self.args['BINS'] = conf.BINARYS['NUTCRACKER_BINS'] self._run(TT('cp $BINS $path/bin/', self.args)) fout = open(TT('$path/conf/nutcracker.conf', self.args), 'w+') fout.write(self._gen_conf()) fout.close() def version(self): #This is nutcracker-0.4.0 s = self._run(TT('$BINS --version', self.args)) return s.strip().replace('This is nutcracker-', '') def _info_dict(self): try: c = telnetlib.Telnet(self.args['host'], self.args['status_port']) ret = c.read_all() return json_decode(ret) except Exception, e: logging.debug('can not get _info_dict of nutcracker, \ [Exception: %s]' % (e, )) return None def reconfig(self, masters): self.masters = masters self.stop() self.deploy() self.start() logging.info('proxy %s:%s is updated' % (self.args['host'], self.args['port'])) def logfile(self): return self.args['logfile'] def cleanlog(self): cmd = TT("rm '$logfile'", self.args) self._run(cmd) def signal(self, signo): self.args['signo'] = signo cmd = TT("pkill -$signo -f '^$runcmd'", self.args) self._run(cmd) def reload(self): self.signal('USR1') def set_config(self, content): fout = open(TT('$path/conf/nutcracker.conf', self.args), 'w+') fout.write(content) fout.close() self.reload() nutcracker-0.4.1+dfsg/tests/lib/utils.py0000664000000000000000000000565612542173515016737 0ustar rootrootimport os import re import sys import time import copy import thread import socket import threading import logging import inspect import argparse import telnetlib import redis import random import redis import json import glob import commands from collections import defaultdict from argparse import RawTextHelpFormatter from string import Template PWD = os.path.dirname(os.path.realpath(__file__)) WORKDIR = os.path.join(PWD, '../') def getenv(key, default): if key in os.environ: return os.environ[key] return default logfile = getenv('T_LOGFILE', 'log/t.log') if logfile == '-': logging.basicConfig(level=logging.DEBUG, format="%(asctime)-15s [%(threadName)s] [%(levelname)s] %(message)s") else: logging.basicConfig(filename=logfile, level=logging.DEBUG, format="%(asctime)-15s [%(threadName)s] [%(levelname)s] %(message)s") logging.info("test running") def strstr(s1, s2): return s1.find(s2) != -1 def lets_sleep(SLEEP_TIME = 0.1): time.sleep(SLEEP_TIME) def TT(template, args): #todo: modify all return Template(template).substitute(args) def TTCMD(template, args): #todo: modify all ''' Template for cmd (we will replace all spaces) ''' ret = TT(template, args) return re.sub(' +', ' ', ret) def nothrow(ExceptionToCheck=Exception, logger=None): def deco_retry(f): def f_retry(*args, **kwargs): try: return f(*args, **kwargs) except ExceptionToCheck, e: if logger: logger.info(e) else: print str(e) return f_retry # true decorator return deco_retry @nothrow(Exception) def test_nothrow(): raise Exception('exception: xx') def json_encode(j): return json.dumps(j, indent=4, cls=MyEncoder) def json_decode(j): return json.loads(j) #commands dose not work on windows.. def system(cmd, log_fun=logging.info): if log_fun: log_fun(cmd) r = commands.getoutput(cmd) return r def shorten(s, l=80): if len(s)<=l: return s return s[:l-3] + '...' def assert_true(a): assert a, 'assert fail: except true, got %s' % a def assert_equal(a, b): assert a == b, 'assert fail: %s vs %s' % (shorten(str(a)), shorten(str(b))) def assert_raises(exception_cls, callable, *args, **kwargs): try: callable(*args, **kwargs) except exception_cls as e: return e except Exception as e: assert False, 'assert_raises %s but raised: %s' % (exception_cls, e) assert False, 'assert_raises %s but nothing raise' % (exception_cls) def assert_fail(err_response, callable, *args, **kwargs): try: callable(*args, **kwargs) except Exception as e: assert re.search(err_response, str(e)), \ 'assert "%s" but got "%s"' % (err_response, e) return assert False, 'assert_fail %s but nothing raise' % (err_response) if __name__ == "__main__": test_nothrow() nutcracker-0.4.1+dfsg/tests/_binaries/0000775000000000000000000000000012542173515016376 5ustar rootrootnutcracker-0.4.1+dfsg/tests/_binaries/.gitignore0000664000000000000000000000000212542173515020356 0ustar rootroot* nutcracker-0.4.1+dfsg/tests/log/0000775000000000000000000000000012542173515015224 5ustar rootrootnutcracker-0.4.1+dfsg/tests/log/.gitignore0000664000000000000000000000000612542173515017210 0ustar rootroot*.log nutcracker-0.4.1+dfsg/tests/test_memcache/0000775000000000000000000000000012542173515017244 5ustar rootrootnutcracker-0.4.1+dfsg/tests/test_memcache/__init__.py0000664000000000000000000000000012542173515021343 0ustar rootrootnutcracker-0.4.1+dfsg/tests/test_memcache/test_gets.py0000664000000000000000000000472112542173515021623 0ustar rootroot#!/usr/bin/env python #coding: utf-8 import os import sys import redis import memcache PWD = os.path.dirname(os.path.realpath(__file__)) WORKDIR = os.path.join(PWD, '../') sys.path.append(os.path.join(WORKDIR, 'lib/')) sys.path.append(os.path.join(WORKDIR, 'conf/')) import conf from server_modules import * from utils import * CLUSTER_NAME = 'ntest' all_mc= [ Memcached('127.0.0.1', 2200, '/tmp/r/memcached-2200/', CLUSTER_NAME, 'mc-2200'), Memcached('127.0.0.1', 2201, '/tmp/r/memcached-2201/', CLUSTER_NAME, 'mc-2201'), ] nc_verbose = int(getenv('T_VERBOSE', 4)) mbuf = int(getenv('T_MBUF', 512)) large = int(getenv('T_LARGE', 1000)) nc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME, all_mc, mbuf=mbuf, verbose=nc_verbose, is_redis=False) def setup(): for r in all_mc: r.deploy() r.stop() r.start() nc.deploy() nc.stop() nc.start() def teardown(): for r in all_mc: r.stop() assert(nc._alive()) nc.stop() def getconn(): host_port = '%s:%s' % (nc.host(), nc.port()) return memcache.Client([host_port]) def test_basic(): conn = getconn() conn.set('k', 'v') assert('v' == conn.get('k')) conn.set("key", "1") for i in range(10): conn.incr("key") assert(str(i+2) == conn.get('key')) conn.delete("key") assert(None == conn.get('key')) default_kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(10)} def test_mget_mset(kv=default_kv): conn = getconn() conn.set_multi(kv) keys = sorted(kv.keys()) assert(conn.get_multi(keys) == kv) assert(conn.gets_multi(keys) == kv) #del conn.delete_multi(keys) #mget again vals = conn.get_multi(keys) assert({} == vals) def test_mget_mset_large(): for cnt in range(179, large, 179): #print 'test', cnt kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(cnt)} test_mget_mset(kv) def test_mget_mset_key_not_exists(kv=default_kv): conn = getconn() conn.set_multi(kv) keys = kv.keys() keys2 = ['x-'+k for k in keys] keys = keys + keys2 random.shuffle(keys) for i in range(2): #mget to check vals = conn.get_multi(keys) for i, k in enumerate(keys): if k in kv: assert(kv[k] == vals[k]) else: assert(k not in vals) #del conn.delete_multi(keys) #mget again vals = conn.get_multi(keys) assert({} == vals) nutcracker-0.4.1+dfsg/tests/.gitignore0000664000000000000000000000002212542173515016425 0ustar rootroot*.pyc *.out *.log nutcracker-0.4.1+dfsg/travis.sh0000664000000000000000000000132112542173515015142 0ustar rootroot#!/bin/bash #file : travis.sh #author : ning #date : 2014-05-10 16:54:43 #install deps if we are in travis if [ -n "$TRAVIS" ]; then sudo apt-get install socat #python libs sudo pip install redis sudo pip install nose sudo pip install git+https://github.com/andymccurdy/redis-py.git@2.9.0 sudo pip install git+https://github.com/idning/python-memcached.git#egg=memcache fi #build twemproxy CFLAGS="-ggdb3 -O0" autoreconf -fvi && ./configure --enable-debug=log && make ln -s `pwd`/src/nutcracker tests/_binaries/ cp `which redis-server` tests/_binaries/ cp `which redis-cli` tests/_binaries/ cp `which memcached` tests/_binaries/ #run test cd tests/ && nosetests --nologcapture -x -v nutcracker-0.4.1+dfsg/NOTICE0000664000000000000000000001424412542173515014212 0ustar rootroottwemproxy is a fast and lightweight proxy for memcached protocol Copyright (C) 2012 Twitter, Inc. Portions of twemproxy were inspired from nginx: http://nginx.org/ The implementation of generic array (nc_array.[ch]) and red black tree (nc_rbtree.[ch]) also comes from nginx-0.8.55. /* * Copyright (C) 2002-2010 Igor Sysoev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ The generic queue implementation comes from BSD /* * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ The implementation of consistent hashing and individual hash algorithms were borrowed from libmemcached. Copyright (c) 2011, Data Differential (http://datadifferential.com/) Copyright (c) 2007-2010, TangentOrg (Brian Aker) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of TangentOrg nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The source also includes libyaml (yaml-0.1.4) in contrib/ directory Copyright (c) 2006 Kirill Simonov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nutcracker-0.4.1+dfsg/contrib/0000775000000000000000000000000012556067103014741 5ustar rootrootnutcracker-0.4.1+dfsg/contrib/Makefile.am0000664000000000000000000000006512542173515016776 0ustar rootrootSUBDIRS = yaml-0.1.4 EXTRA_DIST = yaml-0.1.4.tar.gz nutcracker-0.4.1+dfsg/ChangeLog0000664000000000000000000001303212542173515015052 0ustar rootroot 2015-22-06 Manju Rajashekhar * twemproxy: version 0.4.1 release redis_auth is only valid for a redis pool getaddrinfo returns non-zero +ve value on error fix-hang-when-command-only (charsyam) fix bug crash when get command without key and whitespace (charsyam) mark server as failed on protocol level transiet failures like -OOM, -LOADING, etc implemented support for parsing fine grained redis error response remove redundant conditional judgement in rbtree deletion (leo ma) fix bug mset has invalid pair (charsyam) fix bug mset has invalid pair (charsyam) temp fix a core on kqueue (idning) support "touch" command for memcached (panmiaocai) fix redis parse rsp bug (charsyam) SORT command can take multiple arguments. So it should be part of redis_argn() and not redis_arg0() remove incorrect assert because client could send data after sending a quit request which must be discarded allow file permissions to be set for UNIX domain listening socket (ori liveneh) return error if formatted is greater than mbuf size by using nc_vsnprintf() in msg_prepend_format() fix req_make_reply on msg_get, mark it as response (idning) redis database select upon connect (arne claus) redis_auth (charsyam) allow null key(empty key) (idning) fix core on invalid mset like "mset a a a" (idning) 2014-18-10 idning * twemproxy: version 0.4.0 release mget improve (idning) many new commands supported: LEX, PFADD, PFMERGE, SORT, PING, QUIT, SCAN... (mattrobenolt, areina, idning) handle max open file limit(allenlz) add notice-log and use ms time in log(idning) fix bug in string_compare (andyqzb) fix deadlock in sighandler (idning) 2013-20-12 Manju Rajashekhar * twemproxy: version 0.3.0 release SRANDMEMBER support for the optional count argument (mkhq) Handle case where server responds while the request is still being sent (jdi-tagged) event ports (solaris/smartos) support add timestamp when the server was ejected support for set ex/px/nx/xx for redis 2.6.12 and up (ypocat) kqueue (bsd) support (ferenyx) fix parsing redis response to accept integer reply (charsyam) 2013-23-04 Manju Rajashekhar * twemproxy: version 0.2.4 release redis keys must be less than mbuf_data_size() in length (fifsky) Adds support for DUMP/RESTORE commands in Redis (remotezygote) Use of the weight value in the modula distribution (mezzatto) Add support to unix socket connections to servers (mezzatto) only check for duplicate server name and not 'host:port:weight' when 'name' is configured crc16 hash support added (mezzatto) 2013-31-01 Manju Rajashekhar * twemproxy: version 0.2.3 release RPOPLPUSH, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION, SUNIONSTORE, ZINTERSTORE, and ZUNIONSTORE support (dcartoon) EVAL and EVALSHA support (ferenyx) exit 1 if configuration file is invalid (cofyc) return non-zero exit status when nutcracker cannot start for some reason use server names in stats (charsyam) Fix failure to resolve long FQDN name resolve (conmame) add support for hash tags 2012-18-10 Manju Rajashekhar * twemproxy: version 0.2.2 release fix the off-by-one error when calculating redis key length 2012-12-10 Manju Rajashekhar * twemproxy: version 0.2.1 release don't use buf in conf_add_server allow an optional instance name for consistent hashing (charsyam) add --stats-addr=S option add stats-bind-any -a option (charsyam) 2012-12-03 Manju Rajashekhar * twemproxy: version 0.2.0 release add -D or --describe-stats command-line argument to print stats description redis support in twemproxy setup pre/post splitcopy and pre/post coalesce handlers in msg struct memcache pre_splitcopy, post_splitcopy, pre_coalesce and post_coalesce handlers every fragment of a msg vector keeps track of the first/last fragment, number of fragments and fragment owner set up msg parser handler for memcache connections refactor parsing code and create header file nc_proto.h stats_listen should use st->addr as the listening address string delete stats tracking memcache requests and responses; stats module no longer tracks protocol related stats 2012-10-27 Manju Rajashekhar * twemproxy: version 0.1.20 release on msg_repair, msg->pos should point to nbuf->pos and not nbuf->last refactor memcache parsing code into proto directory add redis option to configuration file fix macro definition strXcmp error for big endian fix log_hexdump and loga_hexdump 2012-07-31 Manju Rajashekhar * twemproxy: version 0.1.19 release close server connection on a stray response (yashh, bmatheny) 2012-06-19 Manju Rajashekhar * twemproxy: version 0.1.18 release command line option to set mbuf chunk size 2012-05-09 Manju Rajashekhar * twemproxy: version 0.1.17 release use _exit(0) instead of exit(0) when daemonizing use loga instead of log_stderr in nc_stacktrace 2012-02-09 Manju Rajashekhar * twemproxy: version 0.1.16 release twemproxy (aka nutcracker) is a fast and lightweight proxy for memcached protocol. nutcracker-0.4.1+dfsg/Makefile.am0000664000000000000000000000036212542173515015336 0ustar rootrootMAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure config.h.in config.h.in~ stamp-h.in ACLOCAL_AMFLAGS = -I m4 SUBDIRS = contrib src dist_man_MANS = man/nutcracker.8 EXTRA_DIST = README.md NOTICE LICENSE ChangeLog conf scripts notes nutcracker-0.4.1+dfsg/.gitignore0000664000000000000000000000122112542173515015265 0ustar rootroot# pyc *.pyc # Compiled Object files *.lo *.o # Compiled Dynamic libraries *.so # Compiled Static libraries *.la *.a # Compiled misc *.dep *.gcda *.gcno *.gcov # Packages *.tar.gz *.tar.bz2 # Logs *.log # Temporary *.swp *.~ *.project *.cproject # Core and executable core* nutcracker # extracted yaml !/contrib/yaml-0.1.4.tar.gz # Autotools .deps .libs /aclocal.m4 /autom4te.cache /stamp-h1 /autoscan.log /libtool /config/config.guess /config/config.sub /config/depcomp /config/install-sh /config/ltmain.sh /config/missing /config /config.h /config.h.in /config.h.in~ /config.log /config.status /configure.scan /configure Makefile Makefile.in