Skip to content

Commit 081c8b3

Browse files
author
Gerrit Code Review
committed
Merge "Merge branch 'couchbase/alice' into 'couchbase/master'"
2 parents fc1dfdb + a89c626 commit 081c8b3

File tree

9 files changed

+82
-65
lines changed

9 files changed

+82
-65
lines changed

daemon/doc_pre_expiry.cc

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323

2424
#include <gsl/gsl>
2525

26-
bool document_pre_expiry(item_info& itm_info) {
26+
std::string document_pre_expiry(const item_info& itm_info) {
2727
if (!mcbp::datatype::is_xattr(itm_info.datatype)) {
2828
// The object does not contain any XATTRs so we should remove
2929
// the entire content
30-
return false;
30+
return {};
3131
}
3232

3333
cb::char_buffer payload{static_cast<char*>(itm_info.value[0].iov_base),
@@ -36,32 +36,11 @@ bool document_pre_expiry(item_info& itm_info) {
3636
cb::xattr::Blob blob(payload, mcbp::datatype::is_snappy(itm_info.datatype));
3737
blob.prune_user_keys();
3838
auto pruned = blob.finalize();
39-
if (pruned.len == 0) {
39+
if (pruned.size() == 0) {
4040
// The old payload only contained user xattrs and
4141
// we removed everything
42-
return false;
42+
return {};
4343
}
4444

45-
// Pruning the user keys should just repack the data internally
46-
// without any allocations, but to be on the safe side we should
47-
// just check to verify, and if it happened to have done any
48-
// reallocations we could copy the data into our buffer if it fits.
49-
// (or we could throw an exception here, but I'd hate to get that
50-
// 2AM call if we can avoid that with a simple fallback check)
51-
if (pruned.buf != payload.buf) {
52-
if (pruned.len > payload.len) {
53-
throw std::logic_error("pre_expiry_document: the pruned object "
54-
"won't fit!");
55-
}
56-
57-
std::copy(pruned.buf, pruned.buf + pruned.len, payload.buf);
58-
}
59-
60-
// Update the length field of the item
61-
itm_info.nbytes = gsl::narrow<uint32_t>(pruned.len);
62-
itm_info.value[0].iov_len = pruned.len;
63-
// Clear all other datatype flags (we've stripped off everything)
64-
itm_info.datatype = PROTOCOL_BINARY_DATATYPE_XATTR;
65-
66-
return true;
45+
return {pruned.data(), pruned.size()};
6746
}

daemon/doc_pre_expiry.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,4 @@
2323
* Implementation of the pre_expiry hook in the `SERVER_DOCUMENT_API`. Check
2424
* the server API documentation for more information.
2525
*/
26-
bool document_pre_expiry(item_info& itm_info);
26+
std::string document_pre_expiry(const item_info& itm_info);

daemon/memcached.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1505,7 +1505,7 @@ struct ServerDocumentApi : public ServerDocumentIface {
15051505
return ENGINE_SUCCESS;
15061506
}
15071507

1508-
bool pre_expiry(item_info& itm_info) override {
1508+
std::string pre_expiry(const item_info& itm_info) override {
15091509
return document_pre_expiry(itm_info);
15101510
}
15111511
};

engines/ep/src/kv_bucket.cc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -554,11 +554,12 @@ void KVBucket::runPreExpiryHook(VBucket& vb, Item& it) {
554554
it.decompressValue(); // A no-op for already decompressed items
555555
auto info =
556556
it.toItemInfo(vb.failovers->getLatestUUID(), vb.getHLCEpochSeqno());
557-
if (engine.getServerApi()->document->pre_expiry(info)) {
558-
// The payload is modified and contains data we should use
559-
it.replaceValue(Blob::New(static_cast<char*>(info.value[0].iov_base),
560-
info.value[0].iov_len));
561-
it.setDataType(info.datatype);
557+
auto result = engine.getServerApi()->document->pre_expiry(info);
558+
if (!result.empty()) {
559+
// A modified value was returned, use it
560+
it.replaceValue(Blob::New(result.data(), result.size()));
561+
// The API states only uncompressed xattr values are returned
562+
it.setDataType(PROTOCOL_BINARY_DATATYPE_XATTR);
562563
} else {
563564
// Make the document empty and raw
564565
it.replaceValue(Blob::New(0));

engines/ep/src/vbucket.cc

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -767,21 +767,19 @@ void VBucket::handlePreExpiry(const HashTable::HashBucketLock& hbl,
767767
EventuallyPersistentEngine* engine = ObjectRegistry::getCurrentEngine();
768768
itm_info =
769769
itm->toItemInfo(failovers->getLatestUUID(), getHLCEpochSeqno());
770-
value_t new_val(Blob::Copy(*value));
771-
itm->replaceValue(new_val.get());
772-
itm->setDataType(v.getDatatype());
773770

774771
SERVER_HANDLE_V1* sapi = engine->getServerApi();
775772
/* TODO: In order to minimize allocations, the callback needs to
776773
* allocate an item whose value size will be exactly the size of the
777774
* value after pre-expiry is performed.
778775
*/
779-
if (sapi->document->pre_expiry(itm_info)) {
776+
auto result = sapi->document->pre_expiry(itm_info);
777+
if (!result.empty()) {
780778
Item new_item(v.getKey(),
781779
v.getFlags(),
782780
v.getExptime(),
783-
itm_info.value[0].iov_base,
784-
itm_info.value[0].iov_len,
781+
result.data(),
782+
result.size(),
785783
itm_info.datatype,
786784
v.getCas(),
787785
v.getBySeqno(),

engines/ep/tests/module_tests/kv_bucket_test.cc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,6 +1381,50 @@ TEST_P(KVBucketParamTest, FailoverEntryNotAddedActiveToActive) {
13811381
EXPECT_EQ(1, vb->failovers->getNumEntries());
13821382
}
13831383

1384+
// Test that expiring a compressed xattr doesn't trigger any errors
1385+
TEST_P(KVBucketParamTest, MB_34346) {
1386+
// Create an XTTR value with only a large system xattr, and compress the lot
1387+
// Note the large xattr should be highly compressible to make it easier to
1388+
// trigger the MB.
1389+
cb::xattr::Blob blob;
1390+
blob.set("_sync",
1391+
R"({"fffff":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"})");
1392+
auto xattr = blob.finalize();
1393+
cb::compression::Buffer output;
1394+
cb::compression::deflate(cb::compression::Algorithm::Snappy,
1395+
{xattr.data(), xattr.size()},
1396+
output);
1397+
EXPECT_LT(output.size(), xattr.size())
1398+
<< "Expected the compressed buffer to be smaller than the input";
1399+
1400+
auto key = makeStoredDocKey("key_1");
1401+
store_item(
1402+
vbid,
1403+
key,
1404+
{output.data(), output.size()},
1405+
ep_abs_time(ep_current_time() + 10),
1406+
{cb::engine_errc::success},
1407+
PROTOCOL_BINARY_DATATYPE_XATTR | PROTOCOL_BINARY_DATATYPE_SNAPPY);
1408+
1409+
flushVBucketToDiskIfPersistent(vbid, 1);
1410+
1411+
EXPECT_EQ(1, engine->getVBucket(vbid)->getNumItems())
1412+
<< "Should have 1 item after calling store()";
1413+
1414+
TimeTraveller docBrown(15);
1415+
1416+
get_options_t options = static_cast<get_options_t>(
1417+
QUEUE_BG_FETCH | HONOR_STATES | TRACK_REFERENCE | DELETE_TEMP |
1418+
HIDE_LOCKED_CAS | TRACK_STATISTICS | GET_DELETED_VALUE);
1419+
GetValue gv2 = store->get(key, vbid, cookie, options);
1420+
EXPECT_TRUE(gv2.item->isDeleted());
1421+
1422+
flushVBucketToDiskIfPersistent(vbid, 1);
1423+
1424+
EXPECT_EQ(0, engine->getVBucket(vbid)->getNumItems())
1425+
<< "Should still have 0 items after time-travelling/expiry";
1426+
}
1427+
13841428
class StoreIfTest : public KVBucketTest {
13851429
public:
13861430
void SetUp() override {

include/memcached/server_document_iface.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ struct ServerDocumentIface {
5454

5555
/**
5656
* This callback is called from the underlying engine right before
57-
* a particular document expires. The callback is responsible for
58-
* modifying the contents of the itm_info passed in. The updated
59-
* total size is available in itm_info.nbytes.
57+
* a particular document expires. The callback is responsible examining
58+
* the value and possibly returning a new and modified value.
6059
*
6160
* @param itm_info info pertaining to the item that is to be expired.
62-
* @return true indicating that the info has been modified in itm_info.
63-
* false indicating that there is no data available in itm_info.
61+
* @return std::string empty if the value required no modification, not
62+
* empty then the string contains the modified value. When not empty
63+
* the datatype of the new value is datatype xattr only.
64+
*
6465
* @throws std::bad_alloc in case of memory allocation failure
65-
* @throws std::logic_error if the data has grown
6666
*/
67-
virtual bool pre_expiry(item_info& itm_info) = 0;
67+
virtual std::string pre_expiry(const item_info& itm_info) = 0;
6868
};

programs/engine_testapp/mock_server.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ struct MockServerDocumentApi : public ServerDocumentIface {
218218
return ENGINE_SUCCESS;
219219
}
220220

221-
bool pre_expiry(item_info& itm_info) override {
221+
std::string pre_expiry(const item_info& itm_info) override {
222222
return document_pre_expiry(itm_info);
223223
}
224224
};

tests/doc_server_api/doc_pre_expiry_test.cc

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
TEST(PreExpiry, EmptyDocument) {
2626
item_info info{};
27-
EXPECT_FALSE(document_pre_expiry(info));
27+
EXPECT_TRUE(document_pre_expiry(info).empty());
2828
}
2929

3030
TEST(PreExpiry, DocumentWithoutXattr) {
@@ -33,8 +33,8 @@ TEST(PreExpiry, DocumentWithoutXattr) {
3333
info.value[0].iov_base = static_cast<void*>(blob);
3434
info.value[0].iov_len = sizeof(blob);
3535
info.nbytes = sizeof(blob);
36-
EXPECT_FALSE(document_pre_expiry(info));
37-
EXPECT_EQ(PROTOCOL_BINARY_RAW_BYTES, info.datatype);
36+
auto rv = document_pre_expiry(info);
37+
EXPECT_TRUE(rv.empty());
3838
}
3939

4040
TEST(PreExpiry, DocumentWithUserXAttrOnly) {
@@ -46,8 +46,8 @@ TEST(PreExpiry, DocumentWithUserXAttrOnly) {
4646
info.value[0].iov_len = body.size();
4747
info.nbytes = gsl::narrow<uint32_t>(body.size());
4848
info.datatype = PROTOCOL_BINARY_DATATYPE_XATTR;
49-
EXPECT_FALSE(document_pre_expiry(info));
50-
EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, info.datatype);
49+
auto rv = document_pre_expiry(info);
50+
EXPECT_TRUE(rv.empty());
5151
}
5252

5353
TEST(PreExpiry, DocumentWithSystemXattrOnly) {
@@ -59,8 +59,8 @@ TEST(PreExpiry, DocumentWithSystemXattrOnly) {
5959
info.value[0].iov_len = body.size();
6060
info.nbytes = gsl::narrow<uint32_t>(body.size());
6161
info.datatype = PROTOCOL_BINARY_DATATYPE_XATTR;
62-
EXPECT_TRUE(document_pre_expiry(info));
63-
EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, info.datatype);
62+
auto rv = document_pre_expiry(info);
63+
EXPECT_FALSE(rv.empty());
6464
}
6565

6666
TEST(PreExpiry, DocumentWithUserAndSystemXattr) {
@@ -73,11 +73,9 @@ TEST(PreExpiry, DocumentWithUserAndSystemXattr) {
7373
info.value[0].iov_len = body.size();
7474
info.nbytes = gsl::narrow<uint32_t>(body.size());
7575
info.datatype = PROTOCOL_BINARY_DATATYPE_XATTR;
76-
EXPECT_TRUE(document_pre_expiry(info));
77-
EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, info.datatype);
78-
79-
// The body should have been modified (shrinked by stripping off user attr)
80-
EXPECT_GT(body.size(), info.datatype);
76+
auto rv = document_pre_expiry(info);
77+
EXPECT_FALSE(rv.empty());
78+
EXPECT_LT(rv.size(), body.size());
8179
}
8280

8381
TEST(PreExpiry, DocumentWithJsonBodyAndXattrs) {
@@ -95,10 +93,7 @@ TEST(PreExpiry, DocumentWithJsonBodyAndXattrs) {
9593
info.nbytes = gsl::narrow<uint32_t>(body.size());
9694
info.datatype =
9795
PROTOCOL_BINARY_DATATYPE_XATTR | PROTOCOL_BINARY_DATATYPE_JSON;
98-
EXPECT_TRUE(document_pre_expiry(info));
99-
// The JSON datatype should be stripped off
100-
EXPECT_EQ(PROTOCOL_BINARY_DATATYPE_XATTR, info.datatype);
101-
102-
// The body should have been modified (shrinked by stripping off user attr)
103-
EXPECT_GT(body.size(), info.datatype);
96+
auto rv = document_pre_expiry(info);
97+
EXPECT_FALSE(rv.empty());
98+
EXPECT_LT(rv.size(), body.size());
10499
}

0 commit comments

Comments
 (0)