Skip to content

Commit

Permalink
adds get and touch command for ascii protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
Theo Najim authored and dormando committed Nov 4, 2017
1 parent 69a4414 commit 7f4e024
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 11 deletions.
39 changes: 39 additions & 0 deletions doc/protocol.txt
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,45 @@ The response line to this command can be one of:
- "NOT_FOUND\r\n" to indicate that the item with this key was not
found.

Get And Touch
-----

The "gat" and "gats" commands are used to fetch items and update the
expiration time of an existing items.

gat <exptime> <key>*\r\n
gats <exptime> <key>*\r\n

- <exptime> is expiration time.

- <key>* means one or more key strings separated by whitespace.

After this command, the client expects zero or more items, each of
which is received as a text line followed by a data block. After all
the items have been transmitted, the server sends the string

"END\r\n"

to indicate the end of response.

Each item sent by the server looks like this:

VALUE <key> <flags> <bytes> [<cas unique>]\r\n
<data block>\r\n

- <key> is the key for the item being sent

- <flags> is the flags value set by the storage command

- <bytes> is the length of the data block to follow, *not* including
its delimiting \r\n

- <cas unique> is a unique 64-bit integer that uniquely identifies
this specific item.

- <data block> is the data for this item.


Slabs Reassign
--------------

Expand Down
57 changes: 46 additions & 11 deletions memcached.c
Original file line number Diff line number Diff line change
Expand Up @@ -3278,8 +3278,13 @@ static inline int make_ascii_get_suffix(char *suffix, item *it, bool return_cas,
}

#define IT_REFCOUNT_LIMIT 60000
static inline item* limited_get(char *key, size_t nkey, conn *c) {
item *it = item_get(key, nkey, c, DO_UPDATE);
static inline item* limited_get(char *key, size_t nkey, conn *c, uint32_t exptime, bool should_touch) {
item *it;
if (should_touch) {
it = item_touch(key, nkey, exptime, c);
} else {
it = item_get(key, nkey, c, DO_UPDATE);
}
if (it && it->refcount > IT_REFCOUNT_LIMIT) {
item_remove(it);
it = NULL;
Expand Down Expand Up @@ -3334,16 +3339,28 @@ static inline char *_ascii_get_suffix_buf(conn *c, int i) {
// FIXME: the 'breaks' around memory malloc's should break all the way down,
// fill ileft/suffixleft, then run conn_releaseitems()
/* ntokens is overwritten here... shrug.. */
static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, bool return_cas) {
static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens, bool return_cas, bool should_touch) {
char *key;
size_t nkey;
int i = 0;
int si = 0;
item *it;
token_t *key_token = &tokens[KEY_TOKEN];
char *suffix;
int32_t exptime_int = 0;
rel_time_t exptime = 0;
assert(c != NULL);

if (should_touch) {
// For get and touch commands, use first token as exptime
if (!safe_strtol(tokens[1].value, &exptime_int)) {
out_string(c, "CLIENT_ERROR invalid exptime argument");
return;
}
key_token++;
exptime = realtime(exptime_int);
}

do {
while(key_token->length != 0) {

Expand All @@ -3361,7 +3378,7 @@ static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens,
return;
}

it = limited_get(key, nkey, c);
it = limited_get(key, nkey, c, exptime, should_touch);
if (settings.detail_enabled) {
stats_prefix_record_get(key, nkey, NULL != it);
}
Expand Down Expand Up @@ -3443,18 +3460,28 @@ static inline void process_get_command(conn *c, token_t *tokens, size_t ntokens,

/* item_get() has incremented it->refcount for us */
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.lru_hits[it->slabs_clsid]++;
c->thread->stats.get_cmds++;
if (should_touch) {
c->thread->stats.touch_cmds++;
c->thread->stats.slab_stats[ITEM_clsid(it)].touch_hits++;
} else {
c->thread->stats.lru_hits[it->slabs_clsid]++;
c->thread->stats.get_cmds++;
}
pthread_mutex_unlock(&c->thread->stats.mutex);
*(c->ilist + i) = it;
i++;

} else {
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.get_misses++;
c->thread->stats.get_cmds++;
pthread_mutex_unlock(&c->thread->stats.mutex);
if (should_touch) {
c->thread->stats.touch_cmds++;
c->thread->stats.touch_misses++;
} else {
c->thread->stats.get_misses++;
c->thread->stats.get_cmds++;
}
MEMCACHED_COMMAND_GET(c->sfd, key, nkey, -1, 0);
pthread_mutex_unlock(&c->thread->stats.mutex);
}

key_token++;
Expand Down Expand Up @@ -4077,7 +4104,7 @@ static void process_command(conn *c, char *command) {
((strcmp(tokens[COMMAND_TOKEN].value, "get") == 0) ||
(strcmp(tokens[COMMAND_TOKEN].value, "bget") == 0))) {

process_get_command(c, tokens, ntokens, false);
process_get_command(c, tokens, ntokens, false, false);

} else if ((ntokens == 6 || ntokens == 7) &&
((strcmp(tokens[COMMAND_TOKEN].value, "add") == 0 && (comm = NREAD_ADD)) ||
Expand All @@ -4098,7 +4125,7 @@ static void process_command(conn *c, char *command) {

} else if (ntokens >= 3 && (strcmp(tokens[COMMAND_TOKEN].value, "gets") == 0)) {

process_get_command(c, tokens, ntokens, true);
process_get_command(c, tokens, ntokens, true, false);

} else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0)) {

Expand All @@ -4112,6 +4139,14 @@ static void process_command(conn *c, char *command) {

process_touch_command(c, tokens, ntokens);

} else if (ntokens >= 4 && (strcmp(tokens[COMMAND_TOKEN].value, "gat") == 0)) {

process_get_command(c, tokens, ntokens, false, true);

} else if (ntokens >= 4 && (strcmp(tokens[COMMAND_TOKEN].value, "gats") == 0)) {

process_get_command(c, tokens, ntokens, true, true);

} else if (ntokens >= 2 && (strcmp(tokens[COMMAND_TOKEN].value, "stats") == 0)) {

process_stat(c, tokens, ntokens);
Expand Down
43 changes: 43 additions & 0 deletions t/getandtouch.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/perl

use strict;
use warnings;
use Test::More tests => 15;
use FindBin qw($Bin);
use lib "$Bin/lib";
use MemcachedTest;


my $server = new_memcached();
my $sock = $server->sock;

# cache miss
print $sock "gat 10 foo1\r\n";
is(scalar <$sock>, "END\r\n", "cache miss");

# set foo1 and foo2 (and should get it)
print $sock "set foo1 0 2 7\r\nfooval1\r\n";
is(scalar <$sock>, "STORED\r\n", "stored foo");

print $sock "set foo2 0 2 7\r\nfooval2\r\n";
is(scalar <$sock>, "STORED\r\n", "stored foo2");

# get and touch it with cas
print $sock "gats 10 foo1 foo2\r\n";
ok(scalar <$sock> =~ /VALUE foo1 0 7 (\d+)\r\n/, "get and touch foo1 with cas regexp success");
is(scalar <$sock>, "fooval1\r\n","value");
ok(scalar <$sock> =~ /VALUE foo2 0 7 (\d+)\r\n/, "get and touch foo2 with cas regexp success");
is(scalar <$sock>, "fooval2\r\n","value");
is(scalar <$sock>, "END\r\n", "end");

# get and touch it without cas
print $sock "gat 10 foo1 foo2\r\n";
ok(scalar <$sock> =~ /VALUE foo1 0 7\r\n/, "get and touch foo1 without cas regexp success");
is(scalar <$sock>, "fooval1\r\n","value");
ok(scalar <$sock> =~ /VALUE foo2 0 7\r\n/, "get and touch foo2 without cas regexp success");
is(scalar <$sock>, "fooval2\r\n","value");
is(scalar <$sock>, "END\r\n", "end");

sleep 2;
mem_get_is($sock, "foo1", "fooval1");
mem_get_is($sock, "foo2", "fooval2");

0 comments on commit 7f4e024

Please sign in to comment.