Skip to content

Commit 495e00a

Browse files
jimwwalkerchiyoung
authored andcommitted
MB-16357: Interlock expiry and vbucket state changes
Expiry should only occur whilst the vbucket is active. Background tasks performing expiry deletion must stop driving deletions when the vb changes status to !active. Using a reader/writer lock the core deleteExpiredItem function which is used by both compaction driven expiry and the item pager are now interlocked with vbucket::setState() Change-Id: I19d30c3d7855778613ccb4534a042c0daf627b8c Reviewed-on: http://review.couchbase.org/55646 Reviewed-by: Chiyoung Seo <chiyoung@couchbase.com> Tested-by: buildbot <build@couchbase.com>
1 parent a80db34 commit 495e00a

6 files changed

Lines changed: 259 additions & 28 deletions

File tree

src/ep.cc

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -484,34 +484,39 @@ EventuallyPersistentStore::deleteExpiredItem(uint16_t vbid, std::string &key,
484484
uint64_t revSeqno) {
485485
RCPtr<VBucket> vb = getVBucket(vbid);
486486
if (vb) {
487-
int bucket_num(0);
488-
incExpirationStat(vb);
489-
LockHolder lh = vb->ht.getLockedBucket(key, &bucket_num);
490-
StoredValue *v = vb->ht.unlocked_find(key, bucket_num, true, false);
491-
if (v) {
492-
if (v->isTempNonExistentItem() || v->isTempDeletedItem()) {
493-
// This is a temporary item whose background fetch for metadata
494-
// has completed.
495-
bool deleted = vb->ht.unlocked_del(key, bucket_num);
496-
cb_assert(deleted);
497-
} else if (v->isExpired(startTime) && !v->isDeleted()) {
498-
vb->ht.unlocked_softDelete(v, 0, getItemEvictionPolicy());
499-
queueDirty(vb, v, &lh, false);
500-
}
501-
} else {
502-
if (eviction_policy == FULL_EVICTION) {
503-
// Create a temp item and delete and push it
504-
// into the checkpoint queue.
505-
add_type_t rv = vb->ht.unlocked_addTempItem(bucket_num, key,
506-
eviction_policy);
507-
if (rv == ADD_NOMEM) {
508-
return;
487+
// Obtain reader access to the VB state change lock so that
488+
// the VB can't switch state whilst we're processing
489+
ReaderLockHolder(vb->getStateLock());
490+
if (vb->getState() == vbucket_state_active) {
491+
int bucket_num(0);
492+
incExpirationStat(vb);
493+
LockHolder lh = vb->ht.getLockedBucket(key, &bucket_num);
494+
StoredValue *v = vb->ht.unlocked_find(key, bucket_num, true, false);
495+
if (v) {
496+
if (v->isTempNonExistentItem() || v->isTempDeletedItem()) {
497+
// This is a temporary item whose background fetch for metadata
498+
// has completed.
499+
bool deleted = vb->ht.unlocked_del(key, bucket_num);
500+
cb_assert(deleted);
501+
} else if (v->isExpired(startTime) && !v->isDeleted()) {
502+
vb->ht.unlocked_softDelete(v, 0, getItemEvictionPolicy());
503+
queueDirty(vb, v, &lh, false);
504+
}
505+
} else {
506+
if (eviction_policy == FULL_EVICTION) {
507+
// Create a temp item and delete and push it
508+
// into the checkpoint queue.
509+
add_type_t rv = vb->ht.unlocked_addTempItem(bucket_num, key,
510+
eviction_policy);
511+
if (rv == ADD_NOMEM) {
512+
return;
513+
}
514+
v = vb->ht.unlocked_find(key, bucket_num, true, false);
515+
v->setStoredValueState(StoredValue::state_deleted_key);
516+
v->setRevSeqno(revSeqno);
517+
vb->ht.unlocked_softDelete(v, 0, eviction_policy);
518+
queueDirty(vb, v, &lh, false);
509519
}
510-
v = vb->ht.unlocked_find(key, bucket_num, true, false);
511-
v->setStoredValueState(StoredValue::state_deleted_key);
512-
v->setRevSeqno(revSeqno);
513-
vb->ht.unlocked_softDelete(v, 0, eviction_policy);
514-
queueDirty(vb, v, &lh, false);
515520
}
516521
}
517522
}

src/locks.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "common.h"
2929
#include "mutex.h"
3030
#include "syncobject.h"
31+
#include "rwlock.h"
3132

3233
/**
3334
* RAII lock holder to guarantee release of the lock.
@@ -158,4 +159,64 @@ class MultiLockHolder {
158159
DISALLOW_COPY_AND_ASSIGN(MultiLockHolder);
159160
};
160161

162+
// RAII Reader lock
163+
class ReaderLockHolder {
164+
public:
165+
ReaderLockHolder(RWLock& lock)
166+
: rwLock(lock) {
167+
int locked = rwLock.readerLock();
168+
if (locked != 0) {
169+
char exceptionMsg[64];
170+
snprintf(exceptionMsg, sizeof(exceptionMsg),
171+
"%d returned by readerLock()", locked);
172+
throw std::runtime_error(exceptionMsg);
173+
}
174+
}
175+
176+
~ReaderLockHolder() {
177+
int unlocked = rwLock.readerUnlock();
178+
if (unlocked != 0) {
179+
char exceptionMsg[64];
180+
snprintf(exceptionMsg, sizeof(exceptionMsg),
181+
"%d returned by readerUnlock()", unlocked);
182+
throw std::runtime_error(exceptionMsg);
183+
}
184+
}
185+
186+
private:
187+
RWLock& rwLock;
188+
189+
DISALLOW_COPY_AND_ASSIGN(ReaderLockHolder);
190+
};
191+
192+
// RAII Writer lock
193+
class WriterLockHolder {
194+
public:
195+
WriterLockHolder(RWLock& lock)
196+
: rwLock(lock) {
197+
int locked = rwLock.writerLock();
198+
if (locked != 0) {
199+
char exceptionMsg[64];
200+
snprintf(exceptionMsg, sizeof(exceptionMsg),
201+
"%d returned by writerLock()", locked);
202+
throw std::runtime_error(exceptionMsg);
203+
}
204+
}
205+
206+
~WriterLockHolder() {
207+
int unlocked = rwLock.writerUnlock();
208+
if (unlocked != 0) {
209+
char exceptionMsg[64];
210+
snprintf(exceptionMsg, sizeof(exceptionMsg),
211+
"%d returned by writerUnlock()", unlocked);
212+
throw std::runtime_error(exceptionMsg);
213+
}
214+
}
215+
216+
private:
217+
RWLock& rwLock;
218+
219+
DISALLOW_COPY_AND_ASSIGN(WriterLockHolder);
220+
};
221+
161222
#endif // SRC_LOCKS_H_

src/rwlock.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2+
/*
3+
* Copyright 2015 Couchbase, Inc
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
#pragma once
18+
19+
#include "platform/platform.h"
20+
#include "common.h"
21+
22+
/**
23+
* Reader/Write lock abstraction for platform provided cb_rw_lock
24+
*
25+
* The lock allows many readers but mutual exclusion with a writer.
26+
*/
27+
class RWLock {
28+
public:
29+
RWLock() {
30+
cb_rw_lock_initialize(&lock);
31+
}
32+
33+
~RWLock() {
34+
cb_rw_lock_destroy(&lock);
35+
}
36+
37+
int readerLock() {
38+
return cb_rw_reader_enter(&lock);
39+
}
40+
41+
int readerUnlock() {
42+
return cb_rw_reader_exit(&lock);
43+
}
44+
45+
int writerLock() {
46+
return cb_rw_writer_enter(&lock);
47+
}
48+
49+
int writerUnlock() {
50+
return cb_rw_writer_exit(&lock);
51+
}
52+
53+
private:
54+
cb_rwlock_t lock;
55+
56+
DISALLOW_COPY_AND_ASSIGN(RWLock);
57+
};

src/vbucket.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ void VBucket::fireAllOps(EventuallyPersistentEngine &engine) {
183183

184184
void VBucket::setState(vbucket_state_t to, SERVER_HANDLE_V1 *sapi) {
185185
cb_assert(sapi);
186+
WriterLockHolder wlh(stateLock);
186187
vbucket_state_t oldstate(state);
187188

188189
if (to == vbucket_state_active &&

src/vbucket.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ class VBucket : public RCValue {
242242
int getId(void) const { return id; }
243243
vbucket_state_t getState(void) const { return state; }
244244
void setState(vbucket_state_t to, SERVER_HANDLE_V1 *sapi);
245+
RWLock& getStateLock() {return stateLock;}
245246

246247
vbucket_state_t getInitialState(void) { return initialState; }
247248
void setInitialState(vbucket_state_t initState) {
@@ -436,6 +437,7 @@ class VBucket : public RCValue {
436437

437438
int id;
438439
AtomicValue<vbucket_state_t> state;
440+
RWLock stateLock;
439441
vbucket_state_t initialState;
440442
Mutex pendingOpLock;
441443
std::vector<const void*> pendingOps;

tests/ep_testsuite.cc

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11326,6 +11326,109 @@ static enum test_result test_failover_log_dcp(ENGINE_HANDLE *h,
1132611326
return SUCCESS;
1132711327
}
1132811328

11329+
struct mb16357_ctx {
11330+
mb16357_ctx(ENGINE_HANDLE *_h, ENGINE_HANDLE_V1 *_h1, int _items) :
11331+
h(_h), h1(_h1), items(_items) { }
11332+
11333+
ENGINE_HANDLE *h;
11334+
ENGINE_HANDLE_V1 *h1;
11335+
int items;
11336+
};
11337+
11338+
extern "C" {
11339+
static void dcp_thread_func(void *args) {
11340+
struct mb16357_ctx *ctx = static_cast<mb16357_ctx *>(args);
11341+
11342+
const void *cookie = testHarness.create_cookie();
11343+
uint32_t opaque = 0xFFFF0000;
11344+
uint32_t flags = 0;
11345+
std::string name = "unittest";
11346+
11347+
while (get_int_stat(ctx->h, ctx->h1, "ep_pending_compactions") == 0);
11348+
11349+
// Switch to replica
11350+
check(set_vbucket_state(ctx->h, ctx->h1, 0, vbucket_state_replica),
11351+
"Failed to set vbucket state.");
11352+
11353+
// Open consumer connection
11354+
checkeq(ctx->h1->dcp.open(ctx->h, cookie, opaque, 0, flags,
11355+
(void*)name.c_str(), name.length()),
11356+
ENGINE_SUCCESS,
11357+
"Failed dcp Consumer open connection.");
11358+
11359+
add_stream_for_consumer(ctx->h, ctx->h1, cookie, opaque++, 0, 0,
11360+
PROTOCOL_BINARY_RESPONSE_SUCCESS);
11361+
11362+
11363+
uint32_t stream_opaque = get_int_stat(ctx->h, ctx->h1,
11364+
"eq_dcpq:unittest:stream_0_opaque",
11365+
"dcp");
11366+
11367+
11368+
for (int i = 1; i <= ctx->items; i++) {
11369+
std::stringstream ss;
11370+
ss << "kamakeey-" << i;
11371+
11372+
// send mutations in single mutation snapshots to race more with compaction
11373+
checkeq(ctx->h1->dcp.snapshot_marker(ctx->h, cookie,
11374+
stream_opaque, 0/*vbid*/,
11375+
ctx->items, ctx->items + i, 2),
11376+
ENGINE_SUCCESS,
11377+
"Failed to send snapshot marker");
11378+
checkeq(ctx->h1->dcp.mutation(ctx->h, cookie, stream_opaque,
11379+
ss.str().c_str(), ss.str().length(),
11380+
"value", 5, i * 3, 0, 0, 0,
11381+
i + ctx->items, i + ctx->items,
11382+
0, 0, "", 0, INITIAL_NRU_VALUE),
11383+
ENGINE_SUCCESS,
11384+
"Failed to send dcp mutation");
11385+
}
11386+
11387+
testHarness.destroy_cookie(cookie);
11388+
}
11389+
11390+
static void compact_thread_func(void *args) {
11391+
struct mb16357_ctx *ctx = static_cast<mb16357_ctx *>(args);
11392+
compact_db(ctx->h, ctx->h1, 0, 99, ctx->items, 1);
11393+
}
11394+
}
11395+
11396+
static enum test_result test_mb16357(ENGINE_HANDLE *h,
11397+
ENGINE_HANDLE_V1 *h1) {
11398+
11399+
// Load up vb0 with n items, expire in 1 second
11400+
int num_items = 1000;
11401+
11402+
for (int j = 0; j < num_items; ++j) {
11403+
item *i = NULL;
11404+
std::stringstream ss;
11405+
ss << "key-" << j;
11406+
check(store(h, h1, NULL, OPERATION_SET,
11407+
ss.str().c_str(), "data", &i, 0, 0, 1/*expire*/, 0)
11408+
== ENGINE_SUCCESS, "Failed to store a value"); //expire in 1 second
11409+
11410+
h1->release(h, NULL, i);
11411+
}
11412+
11413+
wait_for_flusher_to_settle(h, h1);
11414+
testHarness.time_travel(3617); // force expiry pushing time forward.
11415+
11416+
struct mb16357_ctx ctx(h, h1, num_items);
11417+
cb_thread_t cp_thread, dcp_thread;
11418+
11419+
cb_assert(cb_create_thread(&cp_thread,
11420+
compact_thread_func,
11421+
&ctx, 0) == 0);
11422+
cb_assert(cb_create_thread(&dcp_thread,
11423+
dcp_thread_func,
11424+
&ctx, 0) == 0);
11425+
11426+
cb_assert(cb_join_thread(cp_thread) == 0);
11427+
cb_assert(cb_join_thread(dcp_thread) == 0);
11428+
11429+
return SUCCESS;
11430+
}
11431+
1132911432
static enum test_result prepare(engine_test_t *test) {
1133011433
#ifdef __sun
1133111434
// Some of the tests doesn't work on Solaris.. Don't know why yet..
@@ -12251,7 +12354,9 @@ engine_test_t* get_tests(void) {
1225112354

1225212355
TestCase("test failover log behavior", test_failover_log_behavior,
1225312356
test_setup, teardown, NULL, prepare, cleanup),
12254-
12357+
TestCase("test MB-16357", test_mb16357,
12358+
test_setup, teardown, "compaction_exp_mem_threshold=85",
12359+
prepare, cleanup),
1225512360
TestCase(NULL, NULL, NULL, NULL, NULL, prepare, cleanup)
1225612361
};
1225712362

0 commit comments

Comments
 (0)