Skip to content

Commit c770d79

Browse files
[yugabyte#23747] MetaCache: Callback should not be called while holding the lock
Summary: 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: hsunder Subscribers: svc_phabricator, ybase Differential Revision: https://phorge.dev.yugabyte.com/D37706
1 parent 9889df7 commit c770d79

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
@@ -112,6 +112,8 @@ DEFINE_test_flag(bool, force_master_lookup_all_tablets, false,
112112
"If set, force the client to go to the master for all tablet lookup "
113113
"instead of reading from cache.");
114114

115+
DEFINE_test_flag(int32, sleep_before_metacache_lookup_ms, 0,
116+
"If set, will sleep in LookupTabletByKey for a random amount up to this value.");
115117
DEFINE_test_flag(double, simulate_lookup_timeout_probability, 0,
116118
"If set, mark an RPC as failed and force retry on the first attempt.");
117119
DEFINE_test_flag(double, simulate_lookup_partition_list_mismatch_probability, 0,
@@ -2058,9 +2060,12 @@ bool MetaCache::DoLookupTabletByKey(
20582060
LookupTabletCallback* callback, PartitionGroupStartKeyPtr* partition_group_start) {
20592061
DCHECK_ONLY_NOTNULL(partition_group_start);
20602062
RemoteTabletPtr tablet;
2061-
auto scope_exit = ScopeExit([callback, &tablet] {
2063+
Status status = Status::OK();
2064+
auto scope_exit = ScopeExit([callback, &tablet, &status] {
20622065
if (tablet) {
20632066
(*callback)(tablet);
2067+
} else if (!status.ok()) {
2068+
(*callback)(status);
20642069
}
20652070
});
20662071
int64_t request_no;
@@ -2087,13 +2092,13 @@ bool MetaCache::DoLookupTabletByKey(
20872092
(PREDICT_FALSE(RandomActWithProbability(
20882093
FLAGS_TEST_simulate_lookup_partition_list_mismatch_probability)) &&
20892094
table->table_type() != YBTableType::TRANSACTION_STATUS_TABLE_TYPE)) {
2090-
(*callback)(STATUS(
2095+
status = STATUS(
20912096
TryAgain,
20922097
Format(
20932098
"MetaCache's table $0 partitions version does not match, cached: $1, got: $2, "
20942099
"refresh required",
20952100
table->ToString(), table_data->partition_list->version, partitions->version),
2096-
ClientError(ClientErrorCode::kTablePartitionListIsStale)));
2101+
ClientError(ClientErrorCode::kTablePartitionListIsStale));
20972102
return true;
20982103
}
20992104

@@ -2200,6 +2205,12 @@ void MetaCache::LookupTabletByKey(const std::shared_ptr<YBTable>& table,
22002205
return;
22012206
}
22022207

2208+
if (FLAGS_TEST_sleep_before_metacache_lookup_ms > 0) {
2209+
MonoDelta sleep_time = MonoDelta::FromMilliseconds(1) *
2210+
RandomUniformInt(1, FLAGS_TEST_sleep_before_metacache_lookup_ms);
2211+
SleepFor(sleep_time);
2212+
VLOG_WITH_FUNC(2) << "Slept for " << sleep_time;
2213+
}
22032214
if (table->ArePartitionsStale()) {
22042215
RefreshTablePartitions(
22052216
table,
@@ -2293,9 +2304,12 @@ bool MetaCache::DoLookupTabletById(
22932304
UseCache use_cache,
22942305
LookupTabletCallback* callback) {
22952306
std::optional<RemoteTabletPtr> tablet = std::nullopt;
2296-
auto scope_exit = ScopeExit([callback, &tablet] {
2307+
Status status = Status::OK();
2308+
auto scope_exit = ScopeExit([callback, &tablet, &status] {
22972309
if (tablet) {
22982310
(*callback)(*tablet);
2311+
} else if (!status.ok()) {
2312+
(*callback)(status);
22992313
}
23002314
});
23012315
int64_t request_no;
@@ -2311,7 +2325,7 @@ bool MetaCache::DoLookupTabletById(
23112325
if (use_cache) {
23122326
if (!include_deleted) {
23132327
tablet = std::nullopt;
2314-
(*callback)(STATUS(NotFound, "Tablet deleted"));
2328+
status = STATUS(NotFound, "Tablet deleted");
23152329
}
23162330
return true;
23172331
}

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);
@@ -438,6 +440,12 @@ TEST_F(QLStressTest, RetryWritesWithRestarts) {
438440
TestRetryWrites(true /* restarts */);
439441
}
440442

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

0 commit comments

Comments
 (0)