diff options
-rw-r--r-- | proto_text.c | 166 | ||||
-rw-r--r-- | t/metaget.t | 44 |
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"); |