Skip to content

Commit 93b5a26

Browse files
committed
MB-21320: Notify memcached to visit readyQ and get any ready items
We should notify memcached to visit readyQ and get any items that were pushed there during stream creation. Also, we must notify the memcached about cursor dropping so that it can visit ep-engine and stream any pending items and do a subsequent stream state change. This is not a functional fix. It improves performance however. It is not absolutely necessary to notify immediately as conn manager will notify eventually. Change-Id: Id06fc450a20f6d0258fa7c687520dff5f4899a28 Reviewed-on: http://review.couchbase.org/69171 Reviewed-by: Dave Rigby <[email protected]> Reviewed-by: Jim Walker <[email protected]> Tested-by: buildbot <[email protected]>
1 parent 7cbf6a5 commit 93b5a26

File tree

6 files changed

+188
-8
lines changed

6 files changed

+188
-8
lines changed

configuration.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,17 @@
309309
"descr": "True if we want to keep the closed checkpoints for each vbucket unless the memory usage is above high water mark",
310310
"type": "bool"
311311
},
312+
"connection_manager_interval": {
313+
"default": "2",
314+
"descr": "How often connection manager task should be run (in seconds).",
315+
"type": "size_t",
316+
"dynamic": false,
317+
"validator": {
318+
"range": {
319+
"min": 2
320+
}
321+
}
322+
},
312323
"max_checkpoints": {
313324
"default": "2",
314325
"type": "size_t"

src/connmap.cc

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,15 @@ bool ConnNotifier::notifyConnections() {
140140
class ConnManager : public GlobalTask {
141141
public:
142142
ConnManager(EventuallyPersistentEngine *e, ConnMap *cmap)
143-
: GlobalTask(e, TaskId::ConnManager, MIN_SLEEP_TIME, true),
144-
engine(e), connmap(cmap) { }
143+
: GlobalTask(e, TaskId::ConnManager,
144+
e->getConfiguration().getConnectionManagerInterval(),
145+
true),
146+
engine(e), connmap(cmap),
147+
snoozeTime(e->getConfiguration().getConnectionManagerInterval()) { }
145148

146149
bool run(void) {
147150
connmap->manageConnections();
148-
snooze(MIN_SLEEP_TIME);
151+
snooze(snoozeTime);
149152
return !engine->getEpStats().isShutdown ||
150153
!connmap->isAllEmpty() ||
151154
!connmap->isDeadConnectionsEmpty();
@@ -158,6 +161,7 @@ class ConnManager : public GlobalTask {
158161
private:
159162
EventuallyPersistentEngine *engine;
160163
ConnMap *connmap;
164+
size_t snoozeTime;
161165
};
162166

163167
class ConnMapValueChangeListener : public ValueChangedListener {
@@ -229,9 +233,11 @@ void ConnMap::notifyPausedConnection(connection_t conn, bool schedule) {
229233
if (tp && tp->isPaused() && conn->isReserved() &&
230234
tp->setNotificationScheduled(true)) {
231235
pendingNotifications.push(conn);
232-
connNotifier_->notifyMutationEvent(); // Wake up the connection notifier so that
233-
// it can notify the event to a given
234-
// paused connection.
236+
if (connNotifier_) {
237+
// Wake up the connection notifier so that
238+
// it can notify the event to a given paused connection.
239+
connNotifier_->notifyMutationEvent();
240+
}
235241
}
236242
} else {
237243
LockHolder rlh(releaseLock);

src/dcp/producer.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ ENGINE_ERROR_CODE DcpProducer::streamRequest(uint32_t flags,
328328
streams[vbucket] = s;
329329
}
330330

331-
ready.pushUnique(vbucket);
331+
notifyStreamReady(vbucket);
332332

333333
if (add_vb_conn_map) {
334334
connection_t conn(this);

src/dcp/stream.cc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,23 @@ void ActiveStream::scheduleBackfill_UNLOCKED(bool reschedule) {
10231023
} else {
10241024
transitionState(STREAM_IN_MEMORY);
10251025
}
1026-
itemsReady.store(true);
1026+
if (reschedule) {
1027+
/* Cursor was dropped, but we will not do backfill.
1028+
This may happen in a corner case where, the memory
1029+
usage is high due to other vbuckets and persistence cursor moves
1030+
ahead of replication cursor to new checkpoint open but does not
1031+
persist items yet.
1032+
Note: (1) We must not notify when we schedule backfill for the
1033+
first time because the stream is not yet in producer
1034+
conn list of streams
1035+
(2) It is not absolutely necessary to notify immediately
1036+
as conn manager or an incoming items will cause a
1037+
notification eventually, but wouldn't hurt to do so */
1038+
bool inverse = false;
1039+
if (itemsReady.compare_exchange_strong(inverse, true)) {
1040+
producer->notifyStreamReady(vb_);
1041+
}
1042+
}
10271043
}
10281044
}
10291045

@@ -1234,6 +1250,10 @@ void ActiveStream::dropCheckpointCursor_UNLOCKED()
12341250
RCPtr<VBucket> vbucket = engine->getVBucket(vb_);
12351251
if (!vbucket) {
12361252
endStream(END_STREAM_STATE);
1253+
bool inverse = false;
1254+
if (itemsReady.compare_exchange_strong(inverse, true)) {
1255+
producer->notifyStreamReady(vb_);
1256+
}
12371257
}
12381258
/* Drop the existing cursor */
12391259
vbucket->checkpointManager.removeCursor(name_);

tests/ep_testsuite.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6153,6 +6153,7 @@ static enum test_result test_mb19687_fixed(ENGINE_HANDLE* h,
61536153
"ep_compaction_write_queue_cap",
61546154
"ep_config_file",
61556155
"ep_conflict_resolution_type",
6156+
"ep_connection_manager_interval",
61566157
"ep_couch_bucket",
61576158
"ep_cursor_dropping_lower_mark",
61586159
"ep_cursor_dropping_upper_mark",
@@ -6333,6 +6334,7 @@ static enum test_result test_mb19687_fixed(ENGINE_HANDLE* h,
63336334
"ep_compaction_write_queue_cap",
63346335
"ep_config_file",
63356336
"ep_conflict_resolution_type",
6337+
"ep_connection_manager_interval",
63366338
"ep_couch_bucket",
63376339
"ep_cursor_dropping_lower_mark",
63386340
"ep_cursor_dropping_lower_threshold",

tests/ep_testsuite_dcp.cc

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ void dcp_step(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const void* cookie) {
4848
(This approach seems safer than calling pthread_cancel()) */
4949
static std::atomic<bool> stop_continuous_dcp_thread(false);
5050

51+
static bool wait_started(false);
52+
5153
struct SeqnoRange {
5254
uint64_t start;
5355
uint64_t end;
@@ -898,6 +900,85 @@ extern "C" {
898900
}
899901
}
900902

903+
/* DCP step thread that keeps running till it reads upto 'exp_mutations'.
904+
Note: the exp_mutations is cumulative across all streams in the DCP
905+
connection */
906+
static void dcp_waiting_step(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
907+
const void *cookie, uint32_t opaque,
908+
uint64_t exp_mutations)
909+
{
910+
bool done = false;
911+
size_t bytes_read = 0;
912+
bool pending_marker_ack = false;
913+
uint64_t marker_end = 0;
914+
uint64_t num_mutations = 0;
915+
std::unique_ptr<dcp_message_producers> producers(get_dcp_producers(h, h1));
916+
917+
do {
918+
if (bytes_read > 512) {
919+
checkeq(ENGINE_SUCCESS,
920+
h1->dcp.buffer_acknowledgement(h, cookie, ++opaque, 0,
921+
bytes_read),
922+
"Failed to get dcp buffer ack");
923+
bytes_read = 0;
924+
}
925+
ENGINE_ERROR_CODE err = h1->dcp.step(h, cookie, producers.get());
926+
if (err == ENGINE_DISCONNECT) {
927+
done = true;
928+
} else {
929+
switch (dcp_last_op) {
930+
case PROTOCOL_BINARY_CMD_DCP_MUTATION:
931+
bytes_read += dcp_last_packet_size;
932+
if (pending_marker_ack && dcp_last_byseqno == marker_end) {
933+
sendDcpAck(h, h1, cookie,
934+
PROTOCOL_BINARY_CMD_DCP_SNAPSHOT_MARKER,
935+
PROTOCOL_BINARY_RESPONSE_SUCCESS,
936+
dcp_last_opaque);
937+
}
938+
++num_mutations;
939+
break;
940+
case PROTOCOL_BINARY_CMD_DCP_STREAM_END:
941+
done = true;
942+
bytes_read += dcp_last_packet_size;
943+
break;
944+
case PROTOCOL_BINARY_CMD_DCP_SNAPSHOT_MARKER:
945+
if (dcp_last_flags & 8) {
946+
pending_marker_ack = true;
947+
marker_end = dcp_last_snap_end_seqno;
948+
}
949+
bytes_read += dcp_last_packet_size;
950+
break;
951+
case 0:
952+
/* No messages were ready on the last step call, so we
953+
* wait till the conn is notified of new item.
954+
* Note that we check for 0 because we clear the
955+
* dcp_last_op value below.
956+
*/
957+
testHarness.lock_cookie(cookie);
958+
/* waitfor_cookie() waits on a condition variable. But
959+
the api expects the cookie to be locked before
960+
calling it */
961+
wait_started = true;
962+
testHarness.waitfor_cookie(cookie);
963+
testHarness.unlock_cookie(cookie);
964+
break;
965+
default:
966+
// Aborting ...
967+
std::string err_string("Unexpected DCP operation: " +
968+
std::to_string(dcp_last_op));
969+
check(false, err_string.c_str());
970+
}
971+
if (num_mutations >= exp_mutations) {
972+
done = true;
973+
}
974+
dcp_last_op = 0;
975+
}
976+
} while (!done);
977+
978+
/* Do buffer ack of the outstanding bytes */
979+
h1->dcp.buffer_acknowledgement(h, cookie, ++opaque, 0, bytes_read);
980+
}
981+
901982
// Testcases //////////////////////////////////////////////////////////////////
902983

903984
static enum test_result test_dcp_vbtakeover_no_stream(ENGINE_HANDLE *h,
@@ -1531,6 +1612,55 @@ static enum test_result test_dcp_consumer_noop(ENGINE_HANDLE *h,
15311612
return SUCCESS;
15321613
}
15331614

1615+
static enum test_result test_dcp_producer_stream_req_open(ENGINE_HANDLE *h,
1616+
ENGINE_HANDLE_V1 *h1)
1617+
{
1618+
const void *cookie = testHarness.create_cookie();
1619+
const int num_items = 3;
1620+
1621+
DcpStreamCtx ctx;
1622+
ctx.vb_uuid = get_ull_stat(h, h1, "vb_0:0:id", "failovers");
1623+
ctx.seqno = {0, static_cast<uint64_t>(-1)};
1624+
1625+
std::string name("unittest");
1626+
TestDcpConsumer tdc(name.c_str(), cookie);
1627+
tdc.addStreamCtx(ctx);
1628+
1629+
tdc.openConnection(h, h1);
1630+
1631+
/* Create a separate thread that does tries to get any DCP items */
1632+
std::thread dcp_step_thread(dcp_waiting_step, h, h1, cookie, 0, num_items);
1633+
1634+
/* We need to wait till the 'dcp_waiting_step' thread begins its wait */
1635+
while (1) {
1636+
/* Busy wait is ok here. To do a non busy wait we must use
1637+
another condition variable which is an overkill here */
1638+
testHarness.lock_cookie(cookie);
1639+
if (wait_started) {
1640+
testHarness.unlock_cookie(cookie);
1641+
break;
1642+
}
1643+
testHarness.unlock_cookie(cookie);
1644+
}
1645+
1646+
/* Now create a stream */
1647+
tdc.openStreams(h, h1);
1648+
1649+
/* Write items */
1650+
write_items(h, h1, num_items, 0);
1651+
wait_for_flusher_to_settle(h, h1);
1652+
verify_curr_items(h, h1, num_items, "Wrong amount of items");
1653+
1654+
/* If the notification (to 'dcp_waiting_step' thread upon writing an item)
1655+
mechanism is efficient, we must see the 'dcp_waiting_step' finish before
1656+
test time out */
1657+
dcp_step_thread.join();
1658+
1659+
testHarness.destroy_cookie(cookie);
1660+
1661+
return SUCCESS;
1662+
}
1663+
15341664
static enum test_result test_dcp_producer_stream_req_partial(ENGINE_HANDLE *h,
15351665
ENGINE_HANDLE_V1 *h1) {
15361666

@@ -5460,6 +5590,7 @@ static enum test_result test_set_dcp_param(ENGINE_HANDLE *h,
54605590
const char *default_dbname = "./ep_testsuite_dcp";
54615591

54625592
BaseTestCase testsuite_testcases[] = {
5593+
54635594
TestCase("test dcp vbtakeover stat no stream",
54645595
test_dcp_vbtakeover_no_stream, test_setup, teardown, nullptr,
54655596
prepare, cleanup),
@@ -5507,6 +5638,16 @@ BaseTestCase testsuite_testcases[] = {
55075638
TestCase("test dcp replica stream all", test_dcp_replica_stream_all,
55085639
test_setup, teardown, "chk_remover_stime=1;max_checkpoints=2",
55095640
prepare, cleanup),
5641+
TestCase("test dcp producer stream open",
5642+
test_dcp_producer_stream_req_open, test_setup, teardown,
5643+
/* Expecting the connection manager background thread to notify
5644+
the connection at its default time interval is not very
5645+
efficent when we have items to be sent in a DCP stream.
5646+
Hence increase the default time to very high value, so that
5647+
the test fails if we are not doing a notification correctly
5648+
*/
5649+
"connection_manager_interval=200000000",
5650+
prepare, cleanup),
55105651
TestCase("test producer stream request (partial)",
55115652
test_dcp_producer_stream_req_partial, test_setup, teardown,
55125653
/* set chk_period to essentially infinity so it won't run

0 commit comments

Comments
 (0)