Changeset 615
- Timestamp:
- 10/03/07 19:59:11 (1 year ago)
- Files:
-
- trunk/server/ChangeLog (modified) (1 diff)
- trunk/server/doc/protocol.txt (modified) (8 diffs)
- trunk/server/memcached.c (modified) (9 diffs)
- trunk/server/t/getset.t (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/server/ChangeLog
r608 r615 1 2007-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 1 8 2007-08-21 Paul Lindner <lindner@inuus.com> 2 9 * Incorporate incrememnt patch from Evan Miller trunk/server/doc/protocol.txt
r609 r615 54 54 There are three types of commands. 55 55 56 Storage commands (there are three: "set", "add" and "replace") ask the57 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 to56 Storage commands (there are four: "set", "add", "replace", and "cas") 57 ask the server to store some data identified by a key. The client 58 sends a command line, and then a data block; after that the client 59 expects one line of response, which will indicate success or faulure. 60 61 Retrieval commands (there are two: "get" and "gets") ask the server to 62 62 retrieve data corresponding to a set of keys (one or more keys in one 63 63 request). The client sends a command line, which includes all the … … 126 126 First, the client sends a command line which looks like this: 127 127 128 <command name> <key> <flags> <exptime> <bytes> \r\n129 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" 131 131 132 132 "set" means "store this data". … … 137 137 "replace" means "store this data, but only if the server *does* 138 138 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." 139 142 140 143 - <key> is the key under which the client asks to store the data … … 159 162 it's followed by an empty data block). 160 163 164 - <cas unique> is a unique 64-bit value of an existing entry. 165 161 166 After this line, the client sends the data block: 162 167 … … 176 181 item is in a delete queue (see the "delete" command below). 177 182 183 - "EXISTS\r\n" to indicate that the item you are trying to store with 184 a "cas" command has been modified since you last fetched it. 178 185 179 186 Retrieval command: 180 187 ------------------ 181 188 182 The retrieval command looks like this:189 The retrieval commands "get" and "gets" operates like this: 183 190 184 191 get <key>*\r\n 192 gets <key>*\r\n 185 193 186 194 - <key>* means one or more key strings separated by whitespace. … … 196 204 Each item sent by the server looks like this: 197 205 198 VALUE <key> <flags> <bytes> \r\n206 VALUE <key> <flags> <bytes> [<cas unique>]\r\n 199 207 <data block>\r\n 200 208 … … 205 213 - <bytes> is the length of the data block to follow, *not* including 206 214 its delimiting \r\n 215 216 - <cas unique> is a unique 64-bit integer that uniquely identifies 217 this specific item. 207 218 208 219 - <data block> is the data for this item. … … 213 224 but deleted to make space for more items, or expired, or explicitly 214 225 deleted by a client). 215 216 226 217 227 trunk/server/memcached.c
r608 r615 1026 1026 1027 1027 /* ntokens is overwritten here... shrug.. */ 1028 static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens ) {1028 static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, bool return_key_ptr) { 1029 1029 char *key; 1030 1030 size_t nkey; … … 1032 1032 item *it; 1033 1033 token_t *key_token = &tokens[KEY_TOKEN]; 1034 1034 char suffix[255]; 1035 uint32_t in_memory_ptr; 1035 1036 assert(c != NULL); 1036 1037 … … 1082 1083 * " " + flags + " " + data length + "\r\n" + data (with \r\n) 1083 1084 */ 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 1090 1109 if (settings.verbose > 1) 1091 1110 fprintf(stderr, ">%d sending key %s\n", c->sfd, ITEM_key(it)); … … 1136 1155 } 1137 1156 1138 static void process_update_command(conn *c, token_t *tokens, const size_t ntokens, int comm ) {1157 static void process_update_command(conn *c, token_t *tokens, const size_t ntokens, int comm, bool handle_cas) { 1139 1158 char *key; 1140 1159 size_t nkey; … … 1142 1161 time_t exptime; 1143 1162 int vlen; 1163 uint32_t req_memory_ptr, in_memory_ptr; 1164 1144 1165 item *it; 1145 1166 … … 1157 1178 exptime = strtol(tokens[3].value, NULL, 10); 1158 1179 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 } 1159 1186 1160 1187 if(errno == ERANGE || ((flags == 0 || exptime == 0) && errno == EINVAL)) { … … 1181 1208 1182 1209 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 } 1183 1242 1184 1243 if (it == 0) { … … 1420 1479 1421 1480 ntokens = tokenize_command(command, tokens, MAX_TOKENS); 1422 1423 1481 if (ntokens >= 3 && 1424 1482 ((strcmp(tokens[COMMAND_TOKEN].value, "get") == 0) || 1425 1483 (strcmp(tokens[COMMAND_TOKEN].value, "bget") == 0))) { 1426 1484 1427 process_get_command(c, tokens, ntokens );1485 process_get_command(c, tokens, ntokens, false); 1428 1486 1429 1487 } else if (ntokens == 6 && … … 1432 1490 (strcmp(tokens[COMMAND_TOKEN].value, "replace") == 0 && (comm = NREAD_REPLACE)))) { 1433 1491 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); 1435 1497 1436 1498 } else if (ntokens == 4 && (strcmp(tokens[COMMAND_TOKEN].value, "incr") == 0)) { 1437 1499 1438 1500 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); 1439 1505 1440 1506 } else if (ntokens == 4 && (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0)) { trunk/server/t/getset.t
r472 r615 2 2 3 3 use strict; 4 use Test::More tests => 5 28;4 use Test::More tests => 535; 5 5 use FindBin qw($Bin); 6 6 use lib "$Bin/lib"; … … 43 43 is(scalar <$sock>, "NOT_FOUND\r\n", "deleted foo, but not found"); 44 44 45 # add moo 46 # 47 print $sock "add moo 0 0 6\r\nmooval\r\n"; 48 is(scalar <$sock>, "STORED\r\n", "stored barval"); 49 mem_get_is($sock, "moo", "mooval"); 50 51 # check-and-set (cas) failure case, try to set value with incorrect cas unique val 52 print $sock "cas moo 0 0 6 0\r\nMOOVAL\r\n"; 53 is(scalar <$sock>, "EXISTS\r\n", "check and set with invalid id"); 54 55 # test "gets", grab unique ID 56 print $sock "gets moo\r\n"; 57 # VALUE moo 0 6 3084947704 58 # 59 my @retvals = split(/ /, scalar <$sock>); 60 my $data = scalar <$sock>; # grab data 61 my $dot = scalar <$sock>; # grab dot on line by itself 62 is($retvals[0], "VALUE", "get value using 'gets'"); 63 my $unique_id = $retvals[4]; 64 # clean off \r\n 65 $unique_id =~ s/\r\n$//; 66 ok($unique_id =~ /^\d+$/, "unique ID '$unique_id' is an integer"); 67 # now test that we can store moo with the correct unique id 68 print $sock "cas moo 0 0 6 $unique_id\r\nMOOVAL\r\n"; 69 is(scalar <$sock>, "STORED\r\n"); 70 mem_get_is($sock, "moo", "MOOVAL"); 71 45 72 # pipeling is okay 46 73 print $sock "set foo 0 0 6\r\nfooval\r\ndelete foo\r\nset foo 0 0 6\r\nfooval\r\ndelete foo\r\n";
