Skip to content

Commit 966a1a4

Browse files
authored
feat: support memcache meta responses (#4366)
Fixes #4348 and #3071 Signed-off-by: Roman Gershman <roman@dragonflydb.io>
1 parent c88c707 commit 966a1a4

File tree

9 files changed

+107
-29
lines changed

9 files changed

+107
-29
lines changed

src/facade/dragonfly_connection.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,8 @@ struct Connection::AsyncOperations {
421421
}
422422

423423
void operator()(const PubMessage& msg);
424-
void operator()(Connection::PipelineMessage& msg);
425-
void operator()(const Connection::MCPipelineMessage& msg);
424+
void operator()(PipelineMessage& msg);
425+
void operator()(const MCPipelineMessage& msg);
426426
void operator()(const MonitorMessage& msg);
427427
void operator()(const AclUpdateMessage& msg);
428428
void operator()(const MigrationRequestMessage& msg);
@@ -479,7 +479,7 @@ void Connection::AsyncOperations::operator()(Connection::PipelineMessage& msg) {
479479
self->skip_next_squashing_ = false;
480480
}
481481

482-
void Connection::AsyncOperations::operator()(const Connection::MCPipelineMessage& msg) {
482+
void Connection::AsyncOperations::operator()(const MCPipelineMessage& msg) {
483483
self->service_->DispatchMC(msg.cmd, msg.value,
484484
static_cast<MCReplyBuilder*>(self->reply_builder_.get()),
485485
self->cc_.get());

src/facade/memcache_parser.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ MP::Result ParseMeta(ArgSlice tokens, MP::Command* res) {
274274
case 'h':
275275
res->return_hit = true;
276276
break;
277+
case 'c':
278+
res->return_version = true;
279+
break;
277280
default:
278281
LOG(WARNING) << "unknown meta flag: " << token; // not yet implemented
279282
return MP::PARSE_ERROR;
@@ -291,7 +294,7 @@ auto MP::Parse(string_view str, uint32_t* consumed, Command* cmd) -> Result {
291294
*consumed = 0;
292295
if (pos == string_view::npos) {
293296
// We need more data to parse the command. For get/gets commands this line can be very long.
294-
// we limit maxmimum buffer capacity in the higher levels using max_client_iobuf_len.
297+
// we limit maximum buffer capacity in the higher levels using max_client_iobuf_len.
295298
return INPUT_PENDING;
296299
}
297300

src/facade/memcache_parser.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class MemcacheParser {
7575
bool return_ttl = false; // t
7676
bool return_access_time = false; // l
7777
bool return_hit = false; // h
78+
bool return_version = false; // c
79+
7880
// Used internally by meta parsing.
7981
std::string blob;
8082
};

src/facade/reply_builder.cc

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -204,35 +204,48 @@ void SinkReplyBuilder::NextVec(std::string_view str) {
204204
vecs_.push_back(iovec{const_cast<char*>(str.data()), str.size()});
205205
}
206206

207-
MCReplyBuilder::MCReplyBuilder(::io::Sink* sink) : SinkReplyBuilder(sink), noreply_(false) {
207+
MCReplyBuilder::MCReplyBuilder(::io::Sink* sink) : SinkReplyBuilder(sink), all_(0) {
208208
}
209209

210210
void MCReplyBuilder::SendValue(std::string_view key, std::string_view value, uint64_t mc_ver,
211211
uint32_t mc_flag) {
212212
ReplyScope scope(this);
213-
WritePieces("VALUE ", key, " ", mc_flag, " ", value.size());
214-
if (mc_ver)
215-
WritePieces(" ", mc_ver);
216-
217-
if (value.size() <= kMaxInlineSize) {
218-
WritePieces(kCRLF, value, kCRLF);
213+
if (flag_.meta) {
214+
string flags;
215+
if (flag_.return_mcflag)
216+
absl::StrAppend(&flags, " f", mc_flag);
217+
if (flag_.return_version)
218+
absl::StrAppend(&flags, " c", mc_ver);
219+
if (flag_.return_value) {
220+
WritePieces("VA ", value.size(), flags, kCRLF, value, kCRLF);
221+
} else {
222+
WritePieces("HD ", flags, kCRLF);
223+
}
219224
} else {
220-
WritePieces(kCRLF);
221-
WriteRef(value);
222-
WritePieces(kCRLF);
225+
WritePieces("VALUE ", key, " ", mc_flag, " ", value.size());
226+
if (mc_ver)
227+
WritePieces(" ", mc_ver);
228+
229+
if (value.size() <= kMaxInlineSize) {
230+
WritePieces(kCRLF, value, kCRLF);
231+
} else {
232+
WritePieces(kCRLF);
233+
WriteRef(value);
234+
WritePieces(kCRLF);
235+
}
223236
}
224237
}
225238

226239
void MCReplyBuilder::SendSimpleString(std::string_view str) {
227-
if (noreply_)
240+
if (flag_.noreply)
228241
return;
229242

230243
ReplyScope scope(this);
231244
WritePieces(str, kCRLF);
232245
}
233246

234247
void MCReplyBuilder::SendStored() {
235-
SendSimpleString("STORED");
248+
SendSimpleString(flag_.meta ? "HD" : "STORED");
236249
}
237250

238251
void MCReplyBuilder::SendLong(long val) {
@@ -253,11 +266,21 @@ void MCReplyBuilder::SendClientError(string_view str) {
253266
}
254267

255268
void MCReplyBuilder::SendSetSkipped() {
256-
SendSimpleString("NOT_STORED");
269+
SendSimpleString(flag_.meta ? "NS" : "NOT_STORED");
257270
}
258271

259272
void MCReplyBuilder::SendNotFound() {
260-
SendSimpleString("NOT_FOUND");
273+
SendSimpleString(flag_.meta ? "NF" : "NOT_FOUND");
274+
}
275+
276+
void MCReplyBuilder::SendGetEnd() {
277+
if (!flag_.meta)
278+
SendSimpleString("END");
279+
}
280+
281+
void MCReplyBuilder::SendMiss() {
282+
if (flag_.meta)
283+
SendSimpleString("EN");
261284
}
262285

263286
void MCReplyBuilder::SendRaw(std::string_view str) {

src/facade/reply_builder.h

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ class MCReplyBuilder : public SinkReplyBuilder {
171171

172172
void SendClientError(std::string_view str);
173173
void SendNotFound();
174+
void SendMiss();
175+
void SendGetEnd();
174176

175177
void SendValue(std::string_view key, std::string_view value, uint64_t mc_ver, uint32_t mc_flag);
176178
void SendSimpleString(std::string_view str) final;
@@ -179,15 +181,45 @@ class MCReplyBuilder : public SinkReplyBuilder {
179181
void SendRaw(std::string_view str);
180182

181183
void SetNoreply(bool noreply) {
182-
noreply_ = noreply;
184+
flag_.noreply = noreply;
183185
}
184186

185187
bool NoReply() const {
186-
return noreply_;
188+
return flag_.noreply;
189+
}
190+
191+
void SetMeta(bool meta) {
192+
flag_.meta = meta;
193+
}
194+
195+
void SetBase64(bool base64) {
196+
flag_.base64 = base64;
197+
}
198+
199+
void SetReturnMCFlag(bool val) {
200+
flag_.return_mcflag = val;
201+
}
202+
203+
void SetReturnValue(bool val) {
204+
flag_.return_value = val;
205+
}
206+
207+
void SetReturnVersion(bool val) {
208+
flag_.return_version = val;
187209
}
188210

189211
private:
190-
bool noreply_ = false;
212+
union {
213+
struct {
214+
uint8_t noreply : 1;
215+
uint8_t meta : 1;
216+
uint8_t base64 : 1;
217+
uint8_t return_value : 1;
218+
uint8_t return_mcflag : 1;
219+
uint8_t return_version : 1;
220+
} flag_;
221+
uint8_t all_;
222+
};
191223
};
192224

193225
// Redis reply builder interface for sending RESP data.

src/server/main_service.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1490,6 +1490,13 @@ void Service::DispatchMC(const MemcacheParser::Command& cmd, std::string_view va
14901490
char ttl_op[] = "EXAT";
14911491

14921492
mc_builder->SetNoreply(cmd.no_reply);
1493+
mc_builder->SetMeta(cmd.meta);
1494+
if (cmd.meta) {
1495+
mc_builder->SetBase64(cmd.base64);
1496+
mc_builder->SetReturnMCFlag(cmd.return_flags);
1497+
mc_builder->SetReturnValue(cmd.return_value);
1498+
mc_builder->SetReturnVersion(cmd.return_version);
1499+
}
14931500

14941501
switch (cmd.type) {
14951502
case MemcacheParser::REPLACE:
@@ -1533,7 +1540,7 @@ void Service::DispatchMC(const MemcacheParser::Command& cmd, std::string_view va
15331540
server_family_.StatsMC(cmd.key, mc_builder);
15341541
return;
15351542
case MemcacheParser::VERSION:
1536-
mc_builder->SendSimpleString("VERSION 1.5.0 DF");
1543+
mc_builder->SendSimpleString("VERSION 1.6.0 DF");
15371544
return;
15381545
default:
15391546
mc_builder->SendClientError("bad command line format");

src/server/string_family.cc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,11 +1360,13 @@ void StringFamily::MGet(CmdArgList args, const CommandContext& cmnd_cntx) {
13601360
auto* rb = static_cast<MCReplyBuilder*>(builder);
13611361
DCHECK(dynamic_cast<CapturingReplyBuilder*>(builder) == nullptr);
13621362
for (const auto& entry : res) {
1363-
if (!entry)
1364-
continue;
1365-
rb->SendValue(entry->key, entry->value, entry->mc_ver, entry->mc_flag);
1363+
if (entry) {
1364+
rb->SendValue(entry->key, entry->value, entry->mc_ver, entry->mc_flag);
1365+
} else {
1366+
rb->SendMiss();
1367+
}
13661368
}
1367-
rb->SendSimpleString("END");
1369+
rb->SendGetEnd();
13681370
} else {
13691371
auto* rb = static_cast<RedisReplyBuilder*>(builder);
13701372
rb->StartArray(res.size());

tests/dragonfly/memcache_meta.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
CacheClient,
77
connection_pool_factory_builder,
88
)
9+
from meta_memcache.protocol import RequestFlags, Miss, Value, Success
910

1011
DEFAULT_ARGS = {"memcached_port": 11211, "proactor_threads": 4}
1112

@@ -16,6 +17,14 @@ def test_basic(df_server: DflyInstance):
1617
servers=[
1718
ServerAddress(host="localhost", port=DEFAULT_ARGS.get("memcached_port")),
1819
],
19-
connection_pool_factory_fn=connection_pool_factory_builder(),
20+
connection_pool_factory_fn=connection_pool_factory_builder(recv_timeout=5),
2021
)
21-
# TODO: to add integration tests
22+
23+
assert pool.set("key1", "value1", 100)
24+
assert pool.set("key1", "value2", 0)
25+
assert pool.get("key1") == "value2"
26+
27+
request_flags = RequestFlags(return_value=False)
28+
response = pool.meta_get(Key("key1"), flags=request_flags)
29+
assert isinstance(response, Success)
30+
assert pool.get("key2") is None

tests/dragonfly/pymemcached_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def test_version(memcached_client: MCClient):
137137
Our real version is being returned in the stats command.
138138
Also verified manually that php client parses correctly the version string that ends with "DF".
139139
"""
140-
assert b"1.5.0 DF" == memcached_client.version()
140+
assert b"1.6.0 DF" == memcached_client.version()
141141
stats = memcached_client.stats()
142142
version = stats[b"version"].decode("utf-8")
143143
assert version.startswith("v") or version == "dev"

0 commit comments

Comments
 (0)