CAS is broken
Tomash Brechko
tomash.brechko at gmail.com
Tue Nov 20 08:38:29 UTC 2007
On Mon, Nov 19, 2007 at 23:26:13 -0800, dormando wrote:
> I fixed this with an extensively ugly patch in r646. I didn't commit
> your tests with it though; if you think they cover things enough I'll
> happily apply.
My test case doesn't cove the whole case, so a better test should be
added, because the problem is not a simple one.
I looked through your patch, here are my comments:
> diff --git a/trunk/server/memcached.c b/trunk/server/memcached.c
> index dbbb154..74bf06c 100644
> --- a/trunk/server/memcached.c
> +++ b/trunk/server/memcached.c
> @@ -527,6 +541,66 @@ static void conn_set_state(conn *c, int state) {
> }
> }
>
> +/*
> + * Free list management for suffix buffers.
> + */
> +
> +static char **freesuffix;
> +static int freesuffixtotal;
> +static int freesuffixcurr;
> +
> +static void suffix_init(void) {
> + freesuffixtotal = 500;
> + freesuffixcurr = 0;
> +
> + freesuffix = (char **)malloc( sizeof(char *) *
> + 21 * freesuffixtotal );
freesuffix is the array of pointers, '21' is not required here.
> + if (freesuffix == NULL) {
> + perror("malloc()");
> + }
> + return;
> +}
> +
> +/*
> + * Returns a suffix buffer from the freelist, if any. Should call this using
> + * suffix_from_freelist() for thread safety.
> + */
> +char *do_suffix_from_freelist() {
> + char *s;
> +
> + if (freesuffixcurr > 0) {
> + s = freesuffix[--freesuffixcurr];
> + } else {
> + /* FIXME: global define? */
> + /* If malloc fails, let the logic fall through without spamming
> + * STDERR on the server. */
> + s = malloc( sizeof(char *) * SUFFIX_SIZE );
> + }
[*] (see below).
> +
> + return s;
> +}
> +
> +/*
> + * Adds a connection to the freelist. 0 = success. Should call this using
> + * conn_add_to_freelist() for thread safety.
> + */
> +bool do_suffix_add_to_freelist(char *s) {
> + if (freesuffixcurr < freesuffixtotal) {
> + freesuffix[freesuffixcurr++] = s;
> + return false;
> + } else {
> + /* try to enlarge free connections array */
> + char **new_freesuffix = realloc(freesuffix,
> + SUFFIX_SIZE * freesuffixtotal * 2);
Same here, no need to multiply by SUFFIX_SIZE.
> + if (new_freesuffix) {
> + freesuffixtotal *= 2;
> + freesuffix = new_freesuffix;
> + freesuffix[freesuffixcurr++] = s;
> + return false;
> + }
> + }
> + return true;
> +}
You never check the result of suffix_add_to_freelist() later. Since
you allocate memory in suffix_from_freelist() (see [*] mark above), it
would be natural to free it here, if failed to put it to the free
list.
> @@ -1158,12 +1232,33 @@ static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens,
> * " " + flags + " " + data length + "\r\n" + data (with \r\n)
> */
>
> - if(return_key_ptr == true)
> + if(return_cas == true)
> {
> - sprintf(suffix," %d %d %llu\r\n", atoi(ITEM_suffix(it) + 1),
> - it->nbytes - 2, it->cas_id);
> + /* Goofy mid-flight realloc. */
> + if (i >= c->suffixsize) {
> + char **new_suffix_list = realloc(c->suffixlist,
> + sizeof(char *) * c->suffixsize * 2);
> + if (new_suffix_list) {
> + c->suffixsize *= 2;
> + c->suffixlist = new_suffix_list;
> + }
What if new_suffix_list is NULL? You have to abort on that too.
> + }
> +
> + suffix = suffix_from_freelist();
> + if (suffix == NULL) {
> + STATS_LOCK();
> + stats.get_cmds += stats_get_cmds;
> + stats.get_hits += stats_get_hits;
> + stats.get_misses += stats_get_misses;
> + STATS_UNLOCK();
> + out_string(c, "SERVER_ERROR out of memory");
> + return;
> + }
> + *(c->suffixlist + i) = suffix;
> + sprintf(suffix, " %llu\r\n", it->cas_id);
suffix is 21 chars long, but you need ' ' + 20 + '\r' + '\n' + '\0' ==
24 (sprintf() will copy that '\0' from the format, even if you don't
need it).
> @@ -1223,6 +1322,10 @@ static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens,
> || (c->udp && build_udp_headers(c) != 0)) {
> out_string(c, "SERVER_ERROR out of memory");
> }
> + else if (return_cas) {
> + conn_set_state(c, conn_caswrite);
> + c->msgcurr = 0;
> + }
> else {
> conn_set_state(c, conn_mwrite);
> c->msgcurr = 0;
> @@ -2102,9 +2205,10 @@ static void drive_machine(conn *c) {
> /* fall through... */
>
> case conn_mwrite:
> + case conn_caswrite:
> switch (transmit(c)) {
> case TRANSMIT_COMPLETE:
> - if (c->state == conn_mwrite) {
> + if (c->state == conn_mwrite || c->state == conn_caswrite) {
> while (c->ileft > 0) {
> item *it = *(c->icurr);
> assert((it->it_flags & ITEM_SLABBED) == 0);
> @@ -2112,6 +2216,14 @@ static void drive_machine(conn *c) {
> c->icurr++;
> c->ileft--;
> }
> + if (c->state == conn_caswrite) {
> + while (c->suffixleft > 0) {
> + char *suffix = *(c->suffixcurr);
> + suffix_add_to_freelist(suffix);
> + c->suffixcurr++;
> + c->suffixleft--;
> + }
> + }
You don't need a separate conn_caswrite state, just output suffixes
whenever c->suffixleft > 0, and reset the counter afterwards.
But if we are going to use an ugly solution, there's a simpler one,
not involving global free list and mutexes: let conn have a pointer to
a single chunk of memory, and put suffixes there one after another (in
the format " %llu\r\n" as above).
Of course you can't save pointers to the suffixes in this chunk into
IOV, because on the realloc() the chunk it may move. But you can
temporarily place suffix _offsets_ (cast to pointers) into IOV.
Before leaving process_get_command() you just add another pass and
convert all offsets stored in IOV to pointers (you know where they
are, because the loop has the same number of calls to add_iov(), so
every nth entry is the suffix offset).
--
Tomash Brechko
More information about the memcached
mailing list