Skip to content

Commit ec79d7b

Browse files
[BACKPORT 2024.1][yugabyte#23747] MetaCache: Callback should not be called while holding the lock
Summary: Original commit: c770d79 / D37706 Call callback in ScopeExit block only. Not while holding the lock. Without this fix, it is possible that a thread can get into a deadlock, trying to request a shared_lock on a mutex, while already holding an exclusive lock on the same mutex: This deadlock can be triggered if there are active read/write requests to a Table (from more than 1 thread) right after the table had a tablet-split. If there is only 1 thread, it is unlikely to run into the deadlock, as the thread notices -- as part of the callback -- that the table's partition info is stale. Having a different thread refresh the partition version before the main thread checks if the table version is stale, is likely necessary to trigger the stack trace seen below. e.g: ``` #0 pthread_cond_wait@@GLIBC_2.3.2 () at ../sysdeps/unix/sysv/linux/x86_64/pthread_cond_wait.S:185 #1 0x00005640c3eb441b in std::__1::shared_timed_mutex::lock_shared() () #2 0x00005640c3ffcbff in yb::client::internal::MetaCache::LookupTabletByKey(std::__1::shared_ptr<yb::client::YBTable> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::chrono::time_point<yb::CoarseMonoClock, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l> > >, std::__1::function<void (yb::Result<scoped_refptr<yb::client::internal::RemoteTablet> > const&)>, yb::StronglyTypedBool<yb::client::internal::FailOnPartitionListRefreshed_Tag>) () #3 0x00005640c3f7549a in yb::client::internal::Batcher::LookupTabletFor(yb::client::internal::InFlightOp*) () #4 0x00005640c401855e in yb::client::(anonymous namespace)::FlushBatcherAsync(std::__1::shared_ptr<yb::client::internal::Batcher> const&, boost::function<void (yb::client::FlushStatus*)>, yb::client::YBSession::BatcherConfig, yb::StronglyTypedBool<yb::client::internal::IsWithinTransactionRetry_Tag>) () #5 0x00005640c401aa76 in yb::client::(anonymous namespace)::BatcherFlushDone(std::__1::shared_ptr<yb::client::internal::Batcher> const&, yb::Status const&, boost::function<void (yb::client::FlushStatus*)>, yb::client::YBSession::BatcherConfig) () #6 0x00005640c401b371 in boost::detail::function::void_function_obj_invoker1<std::__1::__bind<void (*)(std::__1::shared_ptr<yb::client::internal::Batcher> const&, yb::Status const&, boost::function<void (yb::client::FlushStatus*)>, yb::client::YBSession::BatcherConfig), std::__1::shared_ptr<yb::client::internal::Batcher> const&, std::__1::placeholders::__ph<1> const&, boost::function<void (yb::client::FlushStatus*)>, yb::client::YBSession::BatcherConfig&>, void, yb::Status const&>::invoke(boost::detail::function::function_buffer&, yb::Status const&) () #7 0x00005640c3f70398 in yb::client::internal::Batcher::Run() () #8 0x00005640c3f72656 in yb::client::internal::Batcher::FlushFinished() () #9 0x00005640c3f74a4d in yb::client::internal::Batcher::TabletLookupFinished(yb::client::internal::InFlightOp*, yb::Result<scoped_refptr<yb::client::internal::RemoteTablet> >) () #10 0x00005640c3f759bc in std::__1::__function::__func<yb::client::internal::Batcher::LookupTabletFor(yb::client::internal::InFlightOp*)::$_0, std::__1::allocator<yb::client::internal::Batcher::LookupTabletFor(yb::client::internal::InFlightOp*)::$_0>, void (yb::Result<scoped_refptr<yb::client::internal::RemoteTablet> > const&)>::operator()(yb::Result<scoped_refptr<yb::client::internal::RemoteTablet> > const&) () #11 0x00005640c3fff05d in yb::client::internal::MetaCache::LookupTabletByKey(std::__1::shared_ptr<yb::client::YBTable> const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::chrono::time_point<yb::CoarseMonoClock, std::__1::chrono::duration<long long, std::__1::ratio<1l, 1000000000l> > >, std::__1::function<void (yb::Result<scoped_refptr<yb::client::internal::RemoteTablet> > const&)>, yb::StronglyTypedBool<yb::client::internal::FailOnPartitionListRefreshed_Tag>) () ** Is holding an exclusive lock in MetaCache::LookupTabletByKey/DoLookupTabletByKey ** yugabyte#12 0x00005640c3f7549a in yb::client::internal::Batcher::LookupTabletFor(yb::client::internal::InFlightOp*) () yugabyte#13 0x00005640c401855e in yb::client::(anonymous namespace)::FlushBatcherAsync(std::__1::shared_ptr<yb::client::internal::Batcher> const&, boost::function<void (yb::client::FlushStatus*)>, yb::client::YBSession::BatcherConfig, yb::StronglyTypedBool<yb::client::internal::IsWithinTransactionRetry_Tag>) () yugabyte#14 0x00005640c4017130 in yb::client::YBSession::FlushAsync(boost::function<void (yb::client::FlushStatus*)>) () yugabyte#15 0x00005640c5225a0c in yb::tserver::PgClientServiceImpl::Perform(yb::tserver::PgPerformRequestPB const*, yb::tserver::PgPerformResponsePB*, yb::rpc::RpcContext) () yugabyte#16 0x00005640c51c4487 in std::__1::__function::__func<yb::tserver::PgClientServiceIf::InitMethods(scoped_refptr<yb::MetricEntity> const&)::$_20, std::__1::allocator<yb::tserver::PgClientServiceIf::InitMethods(scoped_refptr<yb::MetricEntity> const&)::$_20>, void (std::__1::shared_ptr<yb::rpc::InboundCall>)>::operator()(std::__1::shared_ptr<yb::rpc::InboundCall>&&) () yugabyte#17 0x00005640c51d374f in yb::tserver::PgClientServiceIf::Handle(std::__1::shared_ptr<yb::rpc::InboundCall>) () yugabyte#18 0x00005640c4f5f420 in yb::rpc::ServicePoolImpl::Handle(std::__1::shared_ptr<yb::rpc::InboundCall>) () yugabyte#19 0x00005640c4e845af in yb::rpc::InboundCall::InboundCallTask::Run() () yugabyte#20 0x00005640c4f6e243 in yb::rpc::(anonymous namespace)::Worker::Execute() () yugabyte#21 0x00005640c570ecb4 in yb::Thread::SuperviseThread(void*) () yugabyte#22 0x00007f808b7c6694 in start_thread (arg=0x7f76d8caf700) at pthread_create.c:333 yugabyte#23 0x00007f808bac341d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109 ``` Jira: DB-12651 Test Plan: Jenkins yb_build.sh --cxx-test ql-stress-test QLStressTest.ReproMetaCacheDeadlock Reviewers: rthallam, hsunder, qhu, timur Reviewed By: rthallam Subscribers: ybase, svc_phabricator Differential Revision: https://phorge.dev.yugabyte.com/D37788
1 parent 2bf6354 commit ec79d7b

File tree

2 files changed

+27
-5
lines changed

2 files changed

+27
-5
lines changed

src/yb/client/meta_cache.cc

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ DEFINE_test_flag(bool, force_master_lookup_all_tablets, false,
111111
"If set, force the client to go to the master for all tablet lookup "
112112
"instead of reading from cache.");
113113

114+
DEFINE_test_flag(int32, sleep_before_metacache_lookup_ms, 0,
115+
"If set, will sleep in LookupTabletByKey for a random amount up to this value.");
114116
DEFINE_test_flag(double, simulate_lookup_timeout_probability, 0,
115117
"If set, mark an RPC as failed and force retry on the first attempt.");
116118
DEFINE_test_flag(double, simulate_lookup_partition_list_mismatch_probability, 0,
@@ -1921,9 +1923,12 @@ bool MetaCache::DoLookupTabletByKey(
19211923
LookupTabletCallback* callback, PartitionGroupStartKeyPtr* partition_group_start) {
19221924
DCHECK_ONLY_NOTNULL(partition_group_start);
19231925
RemoteTabletPtr tablet;
1924-
auto scope_exit = ScopeExit([callback, &tablet] {
1926+
Status status = Status::OK();
1927+
auto scope_exit = ScopeExit([callback, &tablet, &status] {
19251928
if (tablet) {
19261929
(*callback)(tablet);
1930+
} else if (!status.ok()) {
1931+
(*callback)(status);
19271932
}
19281933
});
19291934
int64_t request_no;
@@ -1950,13 +1955,13 @@ bool MetaCache::DoLookupTabletByKey(
19501955
(PREDICT_FALSE(RandomActWithProbability(
19511956
FLAGS_TEST_simulate_lookup_partition_list_mismatch_probability)) &&
19521957
table->table_type() != YBTableType::TRANSACTION_STATUS_TABLE_TYPE)) {
1953-
(*callback)(STATUS(
1958+
status = STATUS(
19541959
TryAgain,
19551960
Format(
19561961
"MetaCache's table $0 partitions version does not match, cached: $1, got: $2, "
19571962
"refresh required",
19581963
table->ToString(), table_data->partition_list->version, partitions->version),
1959-
ClientError(ClientErrorCode::kTablePartitionListIsStale)));
1964+
ClientError(ClientErrorCode::kTablePartitionListIsStale));
19601965
return true;
19611966
}
19621967

@@ -2063,6 +2068,12 @@ void MetaCache::LookupTabletByKey(const std::shared_ptr<YBTable>& table,
20632068
return;
20642069
}
20652070

2071+
if (FLAGS_TEST_sleep_before_metacache_lookup_ms > 0) {
2072+
MonoDelta sleep_time = MonoDelta::FromMilliseconds(1) *
2073+
RandomUniformInt(1, FLAGS_TEST_sleep_before_metacache_lookup_ms);
2074+
SleepFor(sleep_time);
2075+
VLOG_WITH_FUNC(2) << "Slept for " << sleep_time;
2076+
}
20662077
if (table->ArePartitionsStale()) {
20672078
RefreshTablePartitions(
20682079
table,
@@ -2156,9 +2167,12 @@ bool MetaCache::DoLookupTabletById(
21562167
UseCache use_cache,
21572168
LookupTabletCallback* callback) {
21582169
std::optional<RemoteTabletPtr> tablet = std::nullopt;
2159-
auto scope_exit = ScopeExit([callback, &tablet] {
2170+
Status status = Status::OK();
2171+
auto scope_exit = ScopeExit([callback, &tablet, &status] {
21602172
if (tablet) {
21612173
(*callback)(*tablet);
2174+
} else if (!status.ok()) {
2175+
(*callback)(status);
21622176
}
21632177
});
21642178
int64_t request_no;
@@ -2174,7 +2188,7 @@ bool MetaCache::DoLookupTabletById(
21742188
if (use_cache) {
21752189
if (!include_deleted) {
21762190
tablet = std::nullopt;
2177-
(*callback)(STATUS(NotFound, "Tablet deleted"));
2191+
status = STATUS(NotFound, "Tablet deleted");
21782192
}
21792193
return true;
21802194
}

src/yb/client/ql-stress-test.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,10 @@ DECLARE_bool(detect_duplicates_for_retryable_requests);
7474
DECLARE_bool(enable_ondisk_compression);
7575
DECLARE_bool(ycql_enable_packed_row);
7676
DECLARE_double(TEST_respond_write_failed_probability);
77+
DECLARE_double(TEST_simulate_lookup_partition_list_mismatch_probability);
7778
DECLARE_double(transaction_max_missed_heartbeat_periods);
7879
DECLARE_int32(TEST_max_write_waiters);
80+
DECLARE_int32(TEST_sleep_before_metacache_lookup_ms);
7981
DECLARE_int32(client_read_write_timeout_ms);
8082
DECLARE_int32(log_cache_size_limit_mb);
8183
DECLARE_int32(log_min_seconds_to_retain);
@@ -437,6 +439,12 @@ TEST_F(QLStressTest, RetryWritesWithRestarts) {
437439
TestRetryWrites(true /* restarts */);
438440
}
439441

442+
TEST_F(QLStressTest, ReproMetaCacheDeadlock) {
443+
ANNOTATE_UNPROTECTED_WRITE(FLAGS_TEST_simulate_lookup_partition_list_mismatch_probability) = 0.8;
444+
ANNOTATE_UNPROTECTED_WRITE(FLAGS_TEST_sleep_before_metacache_lookup_ms) = 50;
445+
TestRetryWrites(true /* restarts */);
446+
}
447+
440448
void SetTransactional(YBSchemaBuilder* builder) {
441449
TableProperties table_properties;
442450
table_properties.SetTransactional(true);

0 commit comments

Comments
 (0)