# HG changeset patch # User Maxim Dounin # Date 1191111297 -14400 # Node ID e28ab6bd21fa406dbfaa5ee771cbbf83e0f3e6a3 # Parent 7ba3a424d50977a0c0f9400df03f1e1c66ce35ab Append/prepend commands. Introduce new storage commands "append" and "prepend" for atomic value modification. They follow generic storage commands interface as described in doc/protocol.txt: \r\n \r\n Current implementation involves memcpy() twice to combine values on writing (i.e. full resulting value are copied), but truly atomic (and even threadsafe). diff --git a/memcached.c b/memcached.c --- a/memcached.c +++ b/memcached.c @@ -717,14 +717,55 @@ int do_store_item(item *it, int comm) { item *old_it = do_item_get_notedeleted(key, it->nkey, &delete_locked); int stored = 0; + item *new_it = NULL; + int flags; + if (old_it != NULL && comm == NREAD_ADD) { /* add only adds a nonexistent item, but promote to head of LRU */ do_item_update(old_it); - } else if (!old_it && comm == NREAD_REPLACE) { + } else if (!old_it && (comm == NREAD_REPLACE + || comm == NREAD_APPEND || comm == NREAD_PREPEND)) + { /* replace only replaces an existing value; don't store */ - } else if (delete_locked && (comm == NREAD_REPLACE || comm == NREAD_ADD)) { + } else if (delete_locked && (comm == NREAD_REPLACE || comm == NREAD_ADD + || comm == NREAD_APPEND || comm == NREAD_PREPEND)) + { /* replace and add can't override delete locks; don't store */ } else { + + /* + * Append - combine new and old record into single one. Here it's + * atomic and thread-safe. + */ + + if (comm == NREAD_APPEND || comm == NREAD_PREPEND) { + + /* we have it and old_it here - alloc memory to hold both */ + /* flags was already lost - so recover them from ITEM_suffix(it) */ + + flags = (int) strtol(ITEM_suffix(it), (char **) NULL, 10); + + new_it = do_item_alloc(key, it->nkey, flags, it->exptime, it->nbytes + old_it->nbytes - 2 /* CRLF */); + + if (new_it == NULL) { + /* SERVER_ERROR out of memory */ + return 0; + } + + /* copy data from it and old_it to new_it */ + + if (comm == NREAD_APPEND) { + memcpy(ITEM_data(new_it), ITEM_data(old_it), old_it->nbytes); + memcpy(ITEM_data(new_it) + old_it->nbytes - 2 /* CRLF */, ITEM_data(it), it->nbytes); + } else { + /* NREAD_PREPEND */ + memcpy(ITEM_data(new_it), ITEM_data(it), it->nbytes); + memcpy(ITEM_data(new_it) + it->nbytes - 2 /* CRLF */, ITEM_data(old_it), old_it->nbytes); + } + + it = new_it; + } + /* "set" commands can override the delete lock window... in which case we have to find the old hidden item that's in the namespace/LRU but wasn't returned by @@ -742,6 +783,9 @@ int do_store_item(item *it, int comm) { if (old_it) do_item_remove(old_it); /* release our reference */ + if (new_it) + do_item_remove(new_it); + return stored; } @@ -1456,6 +1500,8 @@ static void process_command(conn *c, cha } else if (ntokens == 6 && ((strcmp(tokens[COMMAND_TOKEN].value, "add") == 0 && (comm = NREAD_ADD)) || (strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET)) || + (strcmp(tokens[COMMAND_TOKEN].value, "append") == 0 && (comm = NREAD_APPEND)) || + (strcmp(tokens[COMMAND_TOKEN].value, "prepend") == 0 && (comm = NREAD_PREPEND)) || (strcmp(tokens[COMMAND_TOKEN].value, "replace") == 0 && (comm = NREAD_REPLACE)))) { process_update_command(c, tokens, ntokens, comm); diff --git a/memcached.h b/memcached.h --- a/memcached.h +++ b/memcached.h @@ -134,6 +134,8 @@ enum conn_states { #define NREAD_ADD 1 #define NREAD_SET 2 #define NREAD_REPLACE 3 +#define NREAD_APPEND 4 +#define NREAD_PREPEND 5 typedef struct { int sfd;