summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--proto_text.c166
-rw-r--r--t/metaget.t44
2 files changed, 127 insertions, 83 deletions
diff --git a/proto_text.c b/proto_text.c
index 67930cc..8cbd216 100644
--- a/proto_text.c
+++ b/proto_text.c
@@ -14,6 +14,34 @@
#include <string.h>
#include <stdlib.h>
+#define META_SPACE(p) { \
+ *p = ' '; \
+ p++; \
+}
+
+#define META_CHAR(p, c) { \
+ *p = ' '; \
+ *(p+1) = c; \
+ p += 2; \
+}
+
+// NOTE: being a little casual with the write buffer.
+// the buffer needs to be sized that the longest possible meta response will
+// fit. Here we allow the key to fill up to half the write buffer, in case
+// something terrible has gone wrong.
+#define META_KEY(p, key, nkey, bin) { \
+ META_CHAR(p, 'k'); \
+ if (!bin) { \
+ memcpy(p, key, nkey); \
+ p += nkey; \
+ } else { \
+ p += base64_encode((unsigned char *) key, nkey, (unsigned char *)p, WRITE_BUFFER_SIZE / 2); \
+ *p = ' '; \
+ *(p+1) = 'b'; \
+ p += 2; \
+ } \
+}
+
static void process_command(conn *c, char *command);
typedef struct token_s {
@@ -21,6 +49,72 @@ typedef struct token_s {
size_t length;
} token_t;
+static void _finalize_mset(conn *c, enum store_item_type ret) {
+ mc_resp *resp = c->resp;
+ item *it = c->item;
+ conn_set_state(c, conn_new_cmd);
+
+ // information about the response line has been stashed in wbuf.
+ char *p = resp->wbuf + resp->wbytes;
+ char *end = p; // end of the stashed data portion.
+
+ switch (ret) {
+ case STORED:
+ memcpy(p, "OK", 2);
+ // Only place noreply is used for meta cmds is a nominal response.
+ if (c->noreply) {
+ resp->skip = true;
+ }
+ break;
+ case EXISTS:
+ memcpy(p, "EX", 2);
+ break;
+ case NOT_FOUND:
+ memcpy(p, "NF", 2);
+ break;
+ case NOT_STORED:
+ memcpy(p, "NS", 2);
+ break;
+ default:
+ c->noreply = false;
+ out_string(c, "SERVER_ERROR Unhandled storage type.");
+ return;
+ }
+ p += 2;
+
+ for (char *fp = resp->wbuf; fp < end; fp++) {
+ switch (*fp) {
+ case 'O':
+ // Copy stashed opaque.
+ META_SPACE(p);
+ while (fp < end && *fp != ' ') {
+ *p = *fp;
+ p++;
+ fp++;
+ }
+ break;
+ case 'k':
+ // Encode the key here instead of earlier to minimize copying.
+ META_KEY(p, ITEM_key(it), it->nkey, (it->it_flags & ITEM_KEY_BINARY));
+ break;
+ case 'c':
+ // We don't have the CAS until this point, which is why we
+ // generate this line so late.
+ META_CHAR(p, 'c');
+ p = itoa_u64(ITEM_get_cas(it), p);
+ break;
+ default:
+ break;
+ }
+ }
+
+ memcpy(p, "\r\n", 2);
+ p += 2;
+ // we're offset into wbuf, but good convention to track wbytes.
+ resp->wbytes = p - resp->wbuf;
+ resp_add_iov(resp, end, p - end);
+}
+
/*
* we get here after reading the value in set/add/replace commands. The command
* has been stored in c->cmd, and the item is ready in c->item.
@@ -105,31 +199,7 @@ void complete_nread_ascii(conn *c) {
#endif
if (c->mset_res) {
- // Replace the status code in the response.
- // Rest was prepared during mset parsing.
- mc_resp *resp = c->resp;
- conn_set_state(c, conn_new_cmd);
- switch (ret) {
- case STORED:
- memcpy(resp->wbuf, "OK ", 3);
- // Only place noreply is used for meta cmds is a nominal response.
- if (c->noreply) {
- resp->skip = true;
- }
- break;
- case EXISTS:
- memcpy(resp->wbuf, "EX ", 3);
- break;
- case NOT_FOUND:
- memcpy(resp->wbuf, "NF ", 3);
- break;
- case NOT_STORED:
- memcpy(resp->wbuf, "NS ", 3);
- break;
- default:
- c->noreply = false;
- out_string(c, "SERVER_ERROR Unhandled storage type.");
- }
+ _finalize_mset(c, ret);
} else {
switch (ret) {
case STORED:
@@ -982,34 +1052,6 @@ static int _meta_flag_preparse(token_t *tokens, const size_t ntokens,
return of->has_error ? -1 : 0;
}
-#define META_SPACE(p) { \
- *p = ' '; \
- p++; \
-}
-
-#define META_CHAR(p, c) { \
- *p = ' '; \
- *(p+1) = c; \
- p += 2; \
-}
-
-// NOTE: being a little casual with the write buffer.
-// the buffer needs to be sized that the longest possible meta response will
-// fit. Here we allow the key to fill up to half the write buffer, in case
-// something terrible has gone wrong.
-#define META_KEY(p, key, nkey, bin) { \
- META_CHAR(p, 'k'); \
- if (!bin) { \
- memcpy(p, key, nkey); \
- p += nkey; \
- } else { \
- p += base64_encode((unsigned char *) key, nkey, (unsigned char *)p, WRITE_BUFFER_SIZE / 2); \
- *p = ' '; \
- *(p+1) = 'b'; \
- p += 2; \
- } \
-}
-
static void process_mget_command(conn *c, token_t *tokens, const size_t ntokens) {
char *key;
size_t nkey;
@@ -1339,9 +1381,9 @@ static void process_mset_command(conn *c, token_t *tokens, const size_t ntokens)
return;
}
- // leave space for the status code.
- // FIXME: two spaces after the OK line :(
- p = resp->wbuf + 3;
+ // We note tokens into the front of the write buffer, so we can create the
+ // final buffer in complete_nread_ascii.
+ p = resp->wbuf;
// We need to at least try to get the size to properly slurp bad bytes
// after an error.
@@ -1370,7 +1412,11 @@ static void process_mset_command(conn *c, token_t *tokens, const size_t ntokens)
p += tokens[i].length;
break;
case 'k':
- META_KEY(p, key, nkey, of.key_binary);
+ META_CHAR(p, 'k');
+ break;
+ case 'c':
+ // need to set the cas value post-assignment.
+ META_CHAR(p, 'c');
break;
}
}
@@ -1466,10 +1512,8 @@ static void process_mset_command(conn *c, token_t *tokens, const size_t ntokens)
c->set_stale = true;
}
resp->wbytes = p - resp->wbuf;
- memcpy(resp->wbuf + resp->wbytes, "\r\n", 2);
- resp->wbytes += 2;
- // We've written the status line into wbuf, use wbytes to finalize later.
- resp_add_iov(resp, resp->wbuf, resp->wbytes);
+ // we don't set up the iov here, instead after complete_nread_ascii when
+ // we have the full status code and item data.
c->mset_res = true;
conn_set_state(c, conn_nread);
return;
diff --git a/t/metaget.t b/t/metaget.t
index 962dd3e..a9c3dfe 100644
--- a/t/metaget.t
+++ b/t/metaget.t
@@ -153,7 +153,7 @@ my $sock = $server->sock;
# 44OG44K544OI is "tesuto" in katakana
my $tesuto = "44OG44K544OI";
print $sock "ms $tesuto S2 b\r\npo\r\n";
- like(scalar <$sock>, qr/^OK /, "set with encoded key");
+ like(scalar <$sock>, qr/^OK/, "set with encoded key");
my $res = mget($sock, $tesuto, 'v');
ok(! exists $res->{val}, "encoded key doesn't exist");
@@ -236,7 +236,7 @@ my $sock = $server->sock;
{
diag "mset mode switch";
print $sock "ms modedefault S2 T120\r\naa\r\n";
- like(scalar <$sock>, qr/^OK /, "default set mode");
+ like(scalar <$sock>, qr/^OK/, "default set mode");
mget_is({ sock => $sock,
flags => 's v',
eflags => 's2' },
@@ -244,10 +244,10 @@ my $sock = $server->sock;
# Fail an add
print $sock "ms modedefault S2 T120 ME\r\naa\r\n";
- like(scalar <$sock>, qr/^NS /, "add mode gets NOT_STORED");
+ like(scalar <$sock>, qr/^NS/, "add mode gets NOT_STORED");
# Win an add
print $sock "ms modetest S2 T120 ME\r\nbb\r\n";
- like(scalar <$sock>, qr/^OK /, "add mode");
+ like(scalar <$sock>, qr/^OK/, "add mode");
mget_is({ sock => $sock,
flags => 's v',
eflags => 's2' },
@@ -255,14 +255,14 @@ my $sock = $server->sock;
# append
print $sock "ms modetest S2 T120 MA\r\ncc\r\n";
- like(scalar <$sock>, qr/^OK /, "append mode");
+ like(scalar <$sock>, qr/^OK/, "append mode");
mget_is({ sock => $sock,
flags => 's v',
eflags => 's4' },
'modetest', 'bbcc', "retrieved test value");
# prepend
print $sock "ms modetest S2 T120 MP\r\naa\r\n";
- like(scalar <$sock>, qr/^OK /, "append mode");
+ like(scalar <$sock>, qr/^OK/, "append mode");
mget_is({ sock => $sock,
flags => 's v',
eflags => 's6' },
@@ -270,9 +270,9 @@ my $sock = $server->sock;
# replace
print $sock "ms modereplace S2 T120 MR\r\nzz\r\n";
- like(scalar <$sock>, qr/^NS /, "fail replace mode");
+ like(scalar <$sock>, qr/^NS/, "fail replace mode");
print $sock "ms modetest S2 T120 MR\r\nxx\r\n";
- like(scalar <$sock>, qr/^OK /, "replace mode");
+ like(scalar <$sock>, qr/^OK/, "replace mode");
mget_is({ sock => $sock,
flags => 's v',
eflags => 's2' },
@@ -280,7 +280,7 @@ my $sock = $server->sock;
# explicit set
print $sock "ms modetest S2 T120 MS\r\nyy\r\n";
- like(scalar <$sock>, qr/^OK /, "force set mode");
+ like(scalar <$sock>, qr/^OK/, "force set mode");
# invalid mode
print $sock "ms modetest S2 T120 MZ\r\ntt\r\n";
@@ -316,13 +316,13 @@ my $sock = $server->sock;
# set back with the wrong CAS
print $sock "ms needwin C5000 S2 T120\r\nnu\r\n";
- like(scalar <$sock>, qr/^EX /, "failed to SET: CAS didn't match");
+ like(scalar <$sock>, qr/^EX/, "failed to SET: CAS didn't match");
# again, but succeed.
# TODO: the actual CAS command should work here too?
my $cas = get_flag($res, 'c');
print $sock "ms needwin C$cas S2 T120\r\nmu\r\n";
- like(scalar <$sock>, qr/^OK /, "SET: CAS matched");
+ like(scalar <$sock>, qr/^OK/, "SET: CAS matched");
# now we repeat the original mget, but the data should be different.
$res = mget($sock, 'needwin', 's k t c v N30');
@@ -354,7 +354,7 @@ my $sock = $server->sock;
# again, but succeed.
$cas = get_flag($res, 'c');
print $sock "ms needwin C$cas S4 T300\r\nzuuu\r\n";
- like(scalar <$sock>, qr/^OK /, "SET: CAS matched");
+ like(scalar <$sock>, qr/^OK/, "SET: CAS matched");
# now we repeat the original mget, but the data should be different.
$res = mget($sock, 'needwin', 's t c v N30');
@@ -372,7 +372,7 @@ my $sock = $server->sock;
{
# Set key with lower initial TTL.
print $sock "ms gatkey S4 T100\r\nooom\r\n";
- like(scalar <$sock>, qr/^OK /, "set gatkey");
+ like(scalar <$sock>, qr/^OK/, "set gatkey");
# Coolish side feature and/or bringer of bugs: 't' before 'T' gives TTL
# before adjustment. 'T' before 't' gives TTL after adjustment.
@@ -388,7 +388,7 @@ my $sock = $server->sock;
{
# Set key with lower initial TTL.
print $sock "ms hidevalue S4 T100\r\nhide\r\n";
- like(scalar <$sock>, qr/^OK /, "set hidevalue");
+ like(scalar <$sock>, qr/^OK/, "set hidevalue");
my $res = mget($sock, 'hidevalue', 's t');
ok(keys %$res, "not a miss");
@@ -402,7 +402,7 @@ my $sock = $server->sock;
# test hit-before? flag
{
print $sock "ms hitflag S3 T100\r\nhit\r\n";
- like(scalar <$sock>, qr/^OK /, "set hitflag");
+ like(scalar <$sock>, qr/^OK/, "set hitflag");
my $res = mget($sock, 'hitflag', 's t h');
ok(keys %$res, "not a miss");
@@ -416,7 +416,7 @@ my $sock = $server->sock;
# test no-update flag
{
print $sock "ms noupdate S3 T100\r\nhit\r\n";
- like(scalar <$sock>, qr/^OK /, "set noupdate");
+ like(scalar <$sock>, qr/^OK/, "set noupdate");
my $res = mget($sock, 'noupdate', 's t u h');
ok(keys %$res, "not a miss");
@@ -434,7 +434,7 @@ my $sock = $server->sock;
# test last-access time
{
print $sock "ms la_test S2 T100\r\nla\r\n";
- like(scalar <$sock>, qr/^OK /, "set la_test");
+ like(scalar <$sock>, qr/^OK/, "set la_test");
sleep 2;
my $res = mget($sock, 'la_test', 's t l');
@@ -483,10 +483,10 @@ my $sock = $server->sock;
diag "trying to fail then stale set via mset";
print $sock "ms toinv S1 T90 C0\r\nf\r\n";
- like(scalar <$sock>, qr/^EX /, "failed to SET: low CAS didn't match");
+ like(scalar <$sock>, qr/^EX/, "failed to SET: low CAS didn't match");
print $sock "ms toinv S1 I T90 C1\r\nf\r\n";
- like(scalar <$sock>, qr/^OK /, "SET an invalid/stale item");
+ like(scalar <$sock>, qr/^OK/, "SET an invalid/stale item");
diag "confirm item still stale, and TTL wasn't raised.";
$res = mget($sock, 'toinv', 's t c v');
@@ -501,7 +501,7 @@ my $sock = $server->sock;
diag "do valid mset";
$cas = get_flag($res, 'c');
print $sock "ms toinv S1 T90 C$cas\r\ng\r\n";
- like(scalar <$sock>, qr/^OK /, "SET over the stale item");
+ like(scalar <$sock>, qr/^OK/, "SET over the stale item");
$res = mget($sock, 'toinv', 's t c v');
ok(keys %$res, "not a miss");
@@ -546,7 +546,7 @@ my $sock = $server->sock;
my $k = 'otest';
diag "testing mget opaque";
print $sock "ms $k S2 T100\r\nra\r\n";
- like(scalar <$sock>, qr/^OK /, "set $k");
+ like(scalar <$sock>, qr/^OK/, "set $k");
my $res = mget($sock, $k, 't v Oopaque');
is(get_flag($res, 'O'), 'opaque', "O flag returned opaque");
@@ -561,7 +561,7 @@ my $sock = $server->sock;
{
diag "pipeline test";
print $sock "ms foo S2 T100\r\nna\r\n";
- like(scalar <$sock>, qr/^OK /, "set foo");
+ like(scalar <$sock>, qr/^OK/, "set foo");
print $sock "mg foo s\r\nmg foo s\r\nquit\r\nmg foo s\r\n";
like(scalar <$sock>, qr/^OK /, "got resp");
like(scalar <$sock>, qr/^OK /, "got resp");