Mercurial > hg > memcached
changeset 2:e28ab6bd21fa
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:
<command name> <key> <flags> <exptime> <bytes>\r\n
<data block>\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).
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Sun, 30 Sep 2007 04:14:57 +0400 |
parents | 7ba3a424d509 |
children | 0b6ac95a09bb |
files | memcached.c memcached.h |
diffstat | 2 files changed, 50 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- 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);