Skip to content

Commit 59b5eab

Browse files
jameseh96daverigby
authored andcommitted
MB-35979: Ensure SyncWrites check expiry of committed item
Writes with cas respond differently if the existing item is logically non-existent (e.g., expired) vs exists with different cas. Prior to this patch, processSetInner checked the expiry of the stored value which is about to be modified. However, in an ephemeral bucket this stored value may be a completed prepare - the expiry of the prepare is not necessarily related to the expiry of the committed item. This patch ensures it is always the expiry time of the committed item which is checked. Change-Id: I4a3200cbc363e42f2fc9e5a8009b1a31fd91fdc8 Reviewed-on: http://review.couchbase.org/114825 Tested-by: Build Bot <[email protected]> Reviewed-by: Dave Rigby <[email protected]>
1 parent ee83c61 commit 59b5eab

File tree

2 files changed

+51
-7
lines changed

2 files changed

+51
-7
lines changed

engines/ep/src/vbucket.cc

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3173,20 +3173,21 @@ VBucket::processSetInner(HashTable::FindCommitResult& htRes,
31733173
* a cas operation for a key that doesn't exist is not a very cool
31743174
* thing to do. See MB 3252
31753175
*/
3176-
if (v && v->isExpired(ep_real_time()) && !hasMetaData && !itm.isDeleted()) {
3177-
if (v->isLocked(ep_current_time())) {
3178-
v->unlock();
3176+
// need to test cas and locking against the committed value
3177+
// explicitly, as v may be a completed prepare (to be modified)
3178+
// with a cas, deleted status, expiry etc. different from the committed
3179+
auto* committed = htRes.committed;
3180+
if (committed && committed->isExpired(ep_real_time()) && !hasMetaData &&
3181+
!itm.isDeleted()) {
3182+
if (committed->isLocked(ep_current_time())) {
3183+
committed->unlock();
31793184
}
31803185
if (cas) {
31813186
/* item has expired and cas value provided. Deny ! */
31823187
return {MutationStatus::NotFound, {}};
31833188
}
31843189
}
31853190

3186-
// need to test cas and locking against the committed value
3187-
// explicitly, as v may be a completed prepare to be modified
3188-
// containing an unrelated cas, deleted status etc.
3189-
auto* committed = htRes.committed;
31903191
if (committed) {
31913192
if (!allowExisting && !committed->isTempItem() &&
31923193
!committed->isDeleted()) {

engines/ep/tests/module_tests/evp_store_durability_test.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,49 @@ TEST_P(DurabilityBucketTest, SyncWriteComparesToCorrectCas) {
843843
ASSERT_EQ(ENGINE_SYNC_WRITE_PENDING, store->set(*pending, cookie));
844844
}
845845

846+
TEST_P(DurabilityEphemeralBucketTest, SyncWriteChecksCorrectExpiry) {
847+
setVBucketStateAndRunPersistTask(
848+
vbid,
849+
vbucket_state_active,
850+
{{"topology", nlohmann::json::array({{"active", "replica"}})}});
851+
852+
auto& vb = *store->getVBucket(vbid);
853+
854+
// prepare SyncWrite and commit.
855+
auto key = makeStoredDocKey("key");
856+
auto pending = makePendingItem(key, "value");
857+
858+
ASSERT_EQ(ENGINE_SYNC_WRITE_PENDING, store->set(*pending, cookie));
859+
ASSERT_EQ(ENGINE_SUCCESS,
860+
vb.commit(key,
861+
pending->getBySeqno(),
862+
{} /*commitSeqno*/,
863+
vb.lockCollections(key)));
864+
865+
vb.processResolvedSyncWrites();
866+
867+
// Non-durable write with expiry
868+
auto committed = makeCommittedItem(key, "some_other_value");
869+
870+
using namespace std::chrono;
871+
auto expiry = system_clock::now() + seconds(1);
872+
committed->setExpTime(system_clock::to_time_t(expiry));
873+
874+
ASSERT_EQ(ENGINE_SUCCESS, store->set(*committed, cookie));
875+
876+
// get cas
877+
uint64_t cas = store->get(key, vbid, cookie, {}).item->getCas();
878+
879+
// time travel to when the item has definitely expired
880+
TimeTraveller cooper(10);
881+
882+
// now do a SyncWrite with a cas - the item has expired so it should
883+
// return not found, not invalid cas
884+
pending = makePendingItem(key, "new_value");
885+
pending->setCas(cas);
886+
ASSERT_EQ(ENGINE_KEY_ENOENT, store->set(*pending, cookie));
887+
}
888+
846889
void DurabilityEPBucketTest::verifyOnDiskItemCount(VBucket& vb,
847890
uint64_t expectedValue) {
848891
// skip for rocksdb as it treats every mutation as an insertion

0 commit comments

Comments
 (0)