Skip to content

Commit 3aa62bb

Browse files
committed
MB-58350: Wait for clients to disconnect before deleting bucket
As part of the fix for MB-56067, we converted the blocking bucket deletion code into a state machine. The state machine should only make progress if the bucket has no clients (connections) associated with it. However, there appears to be a bug in the state machine code, where we incorrectly move the state machine to complete bucket deletion, before all clients have disconnected. This will happen 2 minutes after the DELETE_BUCKET started executing, due to a missing check for the current number of clients in that code path. As a consequence of this, we crash in Connection::propagateDisconnect(). Change-Id: I1aaffb2481107221020974ba7729224e4b764f4b Reviewed-on: https://review.couchbase.org/c/kv_engine/+/201110 Well-Formed: Restriction Checker Reviewed-by: Dave Rigby <[email protected]> Tested-by: Vesko Karaganev <[email protected]>
1 parent a5eef6e commit 3aa62bb

File tree

4 files changed

+74
-1
lines changed

4 files changed

+74
-1
lines changed

daemon/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ INSTALL(TARGETS memcached
267267
#
268268
cb_add_test_executable(memcached_unit_tests
269269
bucket_validator_test.cc
270+
buckets_test.cc
270271
client_cert_config_test.cc
271272
connection_unit_tests.cc
272273
datatype_filter_test.cc

daemon/bucket_destroyer.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,10 @@ cb::engine_errc BucketDestroyer::waitForConnections() {
115115
bucket.clients,
116116
currConns.dump());
117117

118-
guard.lock();
118+
// we need to wait more
119+
return cb::engine_errc::would_block;
119120
}
121+
120122
state = State::WaitingForItemsInTransit;
121123
return cb::engine_errc::success;
122124
}

daemon/bucket_destroyer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class BucketDestroyer {
5252
*/
5353
cb::engine_errc drive();
5454

55+
protected:
56+
// Added for testing of the connection timer.
57+
friend class BucketDestroyerIntrospector;
58+
5559
private:
5660
/**
5761
* Start shutting down the engine.

daemon/buckets_test.cc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2022-Present Couchbase, Inc.
3+
*
4+
* Use of this software is governed by the Business Source License included
5+
* in the file licenses/BSL-Couchbase.txt. As of the Change Date specified
6+
* in that file, in accordance with the Business Source License, use of this
7+
* software will be governed by the Apache License, Version 2.0, included in
8+
* the file licenses/APL2.txt.
9+
*/
10+
11+
#include "bucket_destroyer.h"
12+
#include "buckets.h"
13+
#include "memcached.h"
14+
#include "memcached/engine.h"
15+
#include "engines/nobucket/nobucket_public.h"
16+
#include "stats.h"
17+
#include <folly/portability/GTest.h>
18+
19+
class BucketManagerTest : public ::testing::Test, public BucketManager {
20+
};
21+
22+
class BucketDestroyerIntrospector {
23+
public:
24+
static void setNextLogConnections(
25+
BucketDestroyer& destroyer,
26+
std::chrono::steady_clock::time_point tp) {
27+
destroyer.nextLogConnections = tp;
28+
}
29+
};
30+
31+
// Destroy bucket with no clients.
32+
TEST_F(BucketManagerTest, DestroyBucketTest) {
33+
auto& bucket = all_buckets.back();
34+
bucket.setEngine(create_no_bucket_instance());
35+
BucketDestroyer destroyer(bucket, "", false);
36+
EXPECT_EQ(cb::engine_errc::success, destroyer.drive());
37+
}
38+
39+
// We shouldn't destroy a bucket with clients.
40+
TEST_F(BucketManagerTest, DestroyBucketWaitsForClients) {
41+
auto& bucket = all_buckets.back();
42+
bucket.setEngine(create_no_bucket_instance());
43+
// Add a client and verify that we wait for it.
44+
bucket.clients++;
45+
46+
using std::chrono::steady_clock;
47+
using namespace std::chrono_literals;
48+
BucketDestroyer destroyer(bucket, "", false);
49+
// Cannot complete the bucket delete as we've got a client. Run twice to
50+
// make sure the result is the same.
51+
EXPECT_EQ(cb::engine_errc::would_block, destroyer.drive());
52+
EXPECT_EQ(cb::engine_errc::would_block, destroyer.drive());
53+
54+
// "Move" the connection log time, such that on the next run it has elapsed.
55+
BucketDestroyerIntrospector::setNextLogConnections(
56+
destroyer, steady_clock::now() - 1s);
57+
58+
// We should still be unable to delete the bucket. Run twice to make sure
59+
// the result is the same.
60+
EXPECT_EQ(cb::engine_errc::would_block, destroyer.drive());
61+
EXPECT_EQ(cb::engine_errc::would_block, destroyer.drive());
62+
63+
// We should succeed in deleting now that clients == 0.
64+
bucket.clients--;
65+
EXPECT_EQ(cb::engine_errc::success, destroyer.drive());
66+
}

0 commit comments

Comments
 (0)