Index: /trunk/server/memcached.c
===================================================================
--- /trunk/server/memcached.c (revision 624)
+++ /trunk/server/memcached.c (revision 627)
@@ -720,12 +720,53 @@
     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
@@ -743,6 +784,9 @@
     }
 
-    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;
 }
@@ -1209,16 +1253,4 @@
     }
 
-    /* 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);
 
@@ -1269,16 +1301,4 @@
     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);
@@ -1515,4 +1535,5 @@
                 (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)) )) {
 
Index: /trunk/server/ChangeLog
===================================================================
--- /trunk/server/ChangeLog (revision 624)
+++ /trunk/server/ChangeLog (revision 627)
@@ -15,5 +15,6 @@
 
 	* 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
Index: /trunk/server/memcached.h
===================================================================
--- /trunk/server/memcached.h (revision 620)
+++ /trunk/server/memcached.h (revision 627)
@@ -139,4 +139,5 @@
 #define NREAD_REPLACE 3
 #define NREAD_APPEND 4
+#define NREAD_PREPEND 5
 
 typedef struct {
Index: /trunk/server/doc/protocol.txt
===================================================================
--- /trunk/server/doc/protocol.txt (revision 620)
+++ /trunk/server/doc/protocol.txt (revision 627)
@@ -54,6 +54,6 @@
 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
@@ -127,7 +127,7 @@
 First, the client sends a command line which looks like this:
 
-<command name> <key> <flags> <exptime> <bytes> [<unqiue>]\r\n
-
-- <command name> is "set", "add", "replace", "append", or "cas"
+<command name> <key> <flags> <exptime> <bytes> [<cas unqiue>]\r\n
+
+- <command name> is "set", "add", "replace", "append", "prepend", or "cas"
 
   "set" means "store this data".  
@@ -139,5 +139,7 @@
   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
@@ -166,4 +168,6 @@
 
 - <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:
