Changeset 615

Show
Ignore:
Timestamp:
10/03/07 19:59:11 (1 year ago)
Author:
plindner
Message:

Incorporate "cas" operation developed by Dustin Sallings
<dustin@spy.net> and implemented by Chris Goffinet
<goffinet@yahoo-inc.com>. This change allows you to do
atomic changes to an existing key.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/server/ChangeLog

    r608 r615  
     12007-10-03 Paul Lindner <lindner@inuus.com> 
     2        * Incorporate "cas" operation developed by Dustin 
     3          Sallings <dustin@spy.net> and implemented by 
     4          Chris Goffinet <goffinet@yahoo-inc.com>.  This 
     5          change allows you to do atomic changes to an 
     6          existing key. 
     7 
    182007-08-21 Paul Lindner <lindner@inuus.com> 
    29        * Incorporate incrememnt patch from Evan Miller  
  • trunk/server/doc/protocol.txt

    r609 r615  
    5454There are three types of commands.  
    5555 
    56 Storage commands (there are three: "set", "add" and "replace") ask the 
    57 server to store some data identified by a key. The client sends a 
    58 command line, and then a data block; after that the client expects one 
    59 line of response, which will indicate success or faulure. 
    60  
    61 Retrieval commands (there is only one: "get") ask the server to 
     56Storage commands (there are four: "set", "add", "replace", and "cas") 
     57ask the server to store some data identified by a key. The client 
     58sends a command line, and then a data block; after that the client 
     59expects one line of response, which will indicate success or faulure. 
     60 
     61Retrieval commands (there are two: "get" and "gets") ask the server to 
    6262retrieve data corresponding to a set of keys (one or more keys in one 
    6363request). The client sends a command line, which includes all the 
     
    126126First, the client sends a command line which looks like this: 
    127127 
    128 <command name> <key> <flags> <exptime> <bytes>\r\n 
    129  
    130 - <command name> is "set", "add" or "replace
     128<command name> <key> <flags> <exptime> <bytes> [<unqiue>]\r\n 
     129 
     130- <command name> is "set", "add", "replace", or "cas
    131131 
    132132  "set" means "store this data".   
     
    137137  "replace" means "store this data, but only if the server *does* 
    138138  already hold data for this key". 
     139 
     140  "cas" is a check and set operation which means "store this data but 
     141  only if no one else has updated since I last fetched it." 
    139142 
    140143- <key> is the key under which the client asks to store the data 
     
    159162  it's followed by an empty data block). 
    160163 
     164- <cas unique> is a unique 64-bit value of an existing entry. 
     165 
    161166After this line, the client sends the data block: 
    162167 
     
    176181item is in a delete queue (see the "delete" command below). 
    177182 
     183- "EXISTS\r\n" to indicate that the item you are trying to store with 
     184a "cas" command has been modified since you last fetched it. 
    178185 
    179186Retrieval command: 
    180187------------------ 
    181188 
    182 The retrieval command looks like this: 
     189The retrieval commands "get" and "gets" operates like this: 
    183190 
    184191get <key>*\r\n 
     192gets <key>*\r\n 
    185193 
    186194- <key>* means one or more key strings separated by whitespace. 
     
    196204Each item sent by the server looks like this: 
    197205 
    198 VALUE <key> <flags> <bytes>\r\n 
     206VALUE <key> <flags> <bytes> [<cas unique>]\r\n 
    199207<data block>\r\n 
    200208 
     
    205213- <bytes> is the length of the data block to follow, *not* including 
    206214  its delimiting \r\n 
     215 
     216- <cas unique> is a unique 64-bit integer that uniquely identifies 
     217  this specific item. 
    207218 
    208219- <data block> is the data for this item. 
     
    213224but deleted to make space for more items, or expired, or explicitly 
    214225deleted by a client). 
    215  
    216226 
    217227 
  • trunk/server/memcached.c

    r608 r615  
    10261026 
    10271027/* ntokens is overwritten here... shrug.. */ 
    1028 static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens) { 
     1028static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, bool return_key_ptr) { 
    10291029    char *key; 
    10301030    size_t nkey; 
     
    10321032    item *it; 
    10331033    token_t *key_token = &tokens[KEY_TOKEN]; 
    1034  
     1034    char suffix[255]; 
     1035    uint32_t in_memory_ptr; 
    10351036    assert(c != NULL); 
    10361037 
     
    10821083                 *   " " + flags + " " + data length + "\r\n" + data (with \r\n) 
    10831084                 */ 
    1084                 if (add_iov(c, "VALUE ", 6) != 0 || 
    1085                     add_iov(c, ITEM_key(it), it->nkey) != 0 || 
    1086                     add_iov(c, ITEM_suffix(it), it->nsuffix + it->nbytes) != 0) 
    1087                     { 
    1088                         break; 
    1089                     } 
     1085 
     1086                if(return_key_ptr == true) 
     1087                { 
     1088                  in_memory_ptr = (uint32_t)item_get(key, nkey); 
     1089                  sprintf(suffix," %d %d %lu\r\n", atoi(ITEM_suffix(it) + 1), it->nbytes - 2, in_memory_ptr); 
     1090                  if (add_iov(c, "VALUE ", 6) != 0 || 
     1091                      add_iov(c, ITEM_key(it), it->nkey) != 0 || 
     1092                      add_iov(c, suffix, strlen(suffix)) != 0 || 
     1093                      add_iov(c, ITEM_data(it), it->nbytes) != 0) 
     1094                      { 
     1095                          break; 
     1096                      } 
     1097                } 
     1098                else 
     1099                { 
     1100                  if (add_iov(c, "VALUE ", 6) != 0 || 
     1101                      add_iov(c, ITEM_key(it), it->nkey) != 0 || 
     1102                      add_iov(c, ITEM_suffix(it), it->nsuffix + it->nbytes) != 0) 
     1103                      { 
     1104                          break; 
     1105                      } 
     1106                } 
     1107 
     1108 
    10901109                if (settings.verbose > 1) 
    10911110                    fprintf(stderr, ">%d sending key %s\n", c->sfd, ITEM_key(it)); 
     
    11361155} 
    11371156 
    1138 static void process_update_command(conn *c, token_t *tokens, const size_t ntokens, int comm) { 
     1157static void process_update_command(conn *c, token_t *tokens, const size_t ntokens, int comm, bool handle_cas) { 
    11391158    char *key; 
    11401159    size_t nkey; 
     
    11421161    time_t exptime; 
    11431162    int vlen; 
     1163    uint32_t req_memory_ptr, in_memory_ptr; 
     1164 
    11441165    item *it; 
    11451166 
     
    11571178    exptime = strtol(tokens[3].value, NULL, 10); 
    11581179    vlen = strtol(tokens[4].value, NULL, 10); 
     1180 
     1181    // does cas value exist? 
     1182    if(tokens[5].value) 
     1183    { 
     1184      req_memory_ptr = strtoull(tokens[5].value, NULL, 10); 
     1185    } 
    11591186 
    11601187    if(errno == ERANGE || ((flags == 0 || exptime == 0) && errno == EINVAL)) { 
     
    11811208 
    11821209    it = item_alloc(key, nkey, flags, realtime(exptime), vlen+2); 
     1210 
     1211    /* HANDLE_CAS VALIDATION */ 
     1212    if(handle_cas == true) 
     1213    { 
     1214      item *itmp=item_get(key, it->nkey); 
     1215      /* Release the reference */ 
     1216      if(itmp) { 
     1217        item_remove(itmp); 
     1218      } 
     1219      in_memory_ptr = (uint32_t)itmp; 
     1220      if(in_memory_ptr == req_memory_ptr) 
     1221      { 
     1222        // validates allow the set 
     1223      } 
     1224      else if(in_memory_ptr) 
     1225      { 
     1226        out_string(c, "EXISTS"); 
     1227 
     1228        /* swallow the data line */ 
     1229        c->write_and_go = conn_swallow; 
     1230        c->sbytes = vlen + 2; 
     1231        return; 
     1232      } 
     1233      else 
     1234      { 
     1235        out_string(c, "NOT FOUND"); 
     1236        /* swallow the data line */ 
     1237        c->write_and_go = conn_swallow; 
     1238        c->sbytes = vlen + 2; 
     1239        return; 
     1240      } 
     1241    } 
    11831242 
    11841243    if (it == 0) { 
     
    14201479 
    14211480    ntokens = tokenize_command(command, tokens, MAX_TOKENS); 
    1422  
    14231481    if (ntokens >= 3 && 
    14241482        ((strcmp(tokens[COMMAND_TOKEN].value, "get") == 0) || 
    14251483         (strcmp(tokens[COMMAND_TOKEN].value, "bget") == 0))) { 
    14261484 
    1427         process_get_command(c, tokens, ntokens); 
     1485        process_get_command(c, tokens, ntokens, false); 
    14281486 
    14291487    } else if (ntokens == 6 && 
     
    14321490                (strcmp(tokens[COMMAND_TOKEN].value, "replace") == 0 && (comm = NREAD_REPLACE)))) { 
    14331491 
    1434         process_update_command(c, tokens, ntokens, comm); 
     1492        process_update_command(c, tokens, ntokens, comm, false); 
     1493 
     1494    } else if (ntokens == 6 && (strcmp(tokens[COMMAND_TOKEN].value, "cas") == 0)) { 
     1495 
     1496        process_update_command(c, tokens, ntokens, comm, true); 
    14351497 
    14361498    } else if (ntokens == 4 && (strcmp(tokens[COMMAND_TOKEN].value, "incr") == 0)) { 
    14371499 
    14381500        process_arithmetic_command(c, tokens, ntokens, 1); 
     1501 
     1502    } else if (ntokens >= 3 && (strcmp(tokens[COMMAND_TOKEN].value, "gets") == 0)) { 
     1503 
     1504        process_get_command(c, tokens, ntokens, true); 
    14391505 
    14401506    } else if (ntokens == 4 && (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0)) { 
  • trunk/server/t/getset.t

    r472 r615  
    22 
    33use strict; 
    4 use Test::More tests => 528
     4use Test::More tests => 535
    55use FindBin qw($Bin); 
    66use lib "$Bin/lib"; 
     
    4343is(scalar <$sock>, "NOT_FOUND\r\n", "deleted foo, but not found"); 
    4444 
     45# add moo 
     46# 
     47print $sock "add moo 0 0 6\r\nmooval\r\n"; 
     48is(scalar <$sock>, "STORED\r\n", "stored barval"); 
     49mem_get_is($sock, "moo", "mooval"); 
     50 
     51# check-and-set (cas) failure case, try to set value with incorrect cas unique val 
     52print $sock "cas moo 0 0 6 0\r\nMOOVAL\r\n"; 
     53is(scalar <$sock>, "EXISTS\r\n", "check and set with invalid id"); 
     54 
     55# test "gets", grab unique ID 
     56print $sock "gets moo\r\n"; 
     57# VALUE moo 0 6 3084947704 
     58#  
     59my @retvals = split(/ /, scalar <$sock>); 
     60my $data = scalar <$sock>; # grab data 
     61my $dot  = scalar <$sock>; # grab dot on line by itself 
     62is($retvals[0], "VALUE", "get value using 'gets'"); 
     63my $unique_id = $retvals[4]; 
     64# clean off \r\n 
     65$unique_id =~ s/\r\n$//; 
     66ok($unique_id =~ /^\d+$/, "unique ID '$unique_id' is an integer"); 
     67# now test that we can store moo with the correct unique id 
     68print $sock "cas moo 0 0 6 $unique_id\r\nMOOVAL\r\n"; 
     69is(scalar <$sock>, "STORED\r\n"); 
     70mem_get_is($sock, "moo", "MOOVAL"); 
     71 
    4572# pipeling is okay 
    4673print $sock "set foo 0 0 6\r\nfooval\r\ndelete foo\r\nset foo 0 0 6\r\nfooval\r\ndelete foo\r\n";