summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog3
-rw-r--r--doc/protocol.txt14
-rw-r--r--memcached.c75
-rw-r--r--memcached.h1
4 files changed, 60 insertions, 33 deletions
diff --git a/ChangeLog b/ChangeLog
index 860ee7a..767650f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -14,7 +14,8 @@
from Evan Miller and Dustin Sallings.
* Add append command support written by Filipe Laborde.
- Tests/protocol doc updates by Paul Lindner.
+ Thread safe version plus prepend command from Maxim Dounin
+ <mdounin@mdounin.ru>
* The memcached-tool script can now display stats. Patch
provided by Dan Christian <dchristian@google.com>
diff --git a/doc/protocol.txt b/doc/protocol.txt
index 2543737..41cc9a7 100644
--- a/doc/protocol.txt
+++ b/doc/protocol.txt
@@ -53,8 +53,8 @@ Commands
There are three types of commands.
-Storage commands (there are five: "set", "add", "replace", "append"
-and "cas") ask the server to store some data identified by a key. The
+Storage commands (there are six: "set", "add", "replace", "append"
+"prepend" and "cas") ask the server to store some data identified by a key. The
client sends a command line, and then a data block; after that the
client expects one line of response, which will indicate success or
faulure.
@@ -126,9 +126,9 @@ Storage commands
First, the client sends a command line which looks like this:
-<command name> <key> <flags> <exptime> <bytes> [<unqiue>]\r\n
+<command name> <key> <flags> <exptime> <bytes> [<cas unqiue>]\r\n
-- <command name> is "set", "add", "replace", "append", or "cas"
+- <command name> is "set", "add", "replace", "append", "prepend", or "cas"
"set" means "store this data".
@@ -138,7 +138,9 @@ First, the client sends a command line which looks like this:
"replace" means "store this data, but only if the server *does*
already hold data for this key".
- "append" means "add this data to an existing key".
+ "append" means "add this data to an existing key after existing data".
+
+ "prepend" means "add this data to an existing key before existing data".
"cas" is a check and set operation which means "store this data but
only if no one else has updated since I last fetched it."
@@ -165,6 +167,8 @@ First, the client sends a command line which looks like this:
it's followed by an empty data block).
- <cas unique> is a unique 64-bit value of an existing entry.
+ Clients should use the value returned from the "gets" command
+ when issuing "cas" updates.
After this line, the client sends the data block:
diff --git a/memcached.c b/memcached.c
index d8e717d..2cb9a7f 100644
--- a/memcached.c
+++ b/memcached.c
@@ -719,14 +719,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,8 +783,11 @@ int do_store_item(item *it, int comm) {
stored = 1;
}
- if (old_it)
+ if (old_it != NULL)
do_item_remove(old_it); /* release our reference */
+ if (new_it != NULL)
+ do_item_remove(new_it);
+
return stored;
}
@@ -1208,18 +1252,6 @@ static void process_update_command(conn *c, token_t *tokens, const size_t ntoken
}
}
- /* Check if append -- if yes, search for previous entry, and allocate memory for both */
- if( comm == NREAD_APPEND ){
- old_it = assoc_find(key,nkey);
-
- if( old_it && (old_it->nbytes)>2 ){ // previous must be more than \r\n
- old_vlen = old_it->nbytes - 2;
- vlen += old_vlen; // append the length of old data
- } else {
- comm = NREAD_REPLACE; // no old entry: treat as replace
- }
- }
-
it = item_alloc(key, nkey, flags, realtime(exptime), vlen+2);
/* HANDLE_CAS VALIDATION */
@@ -1268,18 +1300,6 @@ static void process_update_command(conn *c, token_t *tokens, const size_t ntoken
c->item = it;
c->ritem = ITEM_data(it);
c->rlbytes = it->nbytes;
-
-
- /* If append, prepend old data before new - adjust item, rlbytes variables too
- * Now that data has been merged, treat simply as a replace command
- */
- if (comm == NREAD_APPEND ){
- memcpy( c->ritem, ITEM_data(old_it), old_vlen );
- c->ritem += old_vlen;
- c->rlbytes -= old_vlen;
- comm = NREAD_REPLACE;
- }
-
c->item_comm = comm;
conn_set_state(c, conn_nread);
}
@@ -1514,6 +1534,7 @@ static void process_command(conn *c, char *command) {
((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, "replace") == 0 && (comm = NREAD_REPLACE)) ||
+ (strcmp(tokens[COMMAND_TOKEN].value, "prepend") == 0 && (comm = NREAD_PREPEND)) ||
(strcmp(tokens[COMMAND_TOKEN].value, "append") == 0 && (comm = NREAD_APPEND)) )) {
process_update_command(c, tokens, ntokens, comm, false);
diff --git a/memcached.h b/memcached.h
index 4a58db1..b5411c9 100644
--- a/memcached.h
+++ b/memcached.h
@@ -138,6 +138,7 @@ enum conn_states {
#define NREAD_SET 2
#define NREAD_REPLACE 3
#define NREAD_APPEND 4
+#define NREAD_PREPEND 5
typedef struct {
int sfd;