Skip to content

Commit 5d81fd4

Browse files
committed
ydb_topic: do not call lock_shared recursively
ydb-platform#4366
1 parent ed0f418 commit 5d81fd4

File tree

1 file changed

+59
-4
lines changed

1 file changed

+59
-4
lines changed

ydb/public/sdk/cpp/client/ydb_persqueue_core/impl/callback_context.h

+59-4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
#include <util/system/guard.h>
55
#include <util/system/spinlock.h>
66

7+
#include <map>
78
#include <memory>
9+
#include <mutex>
810
#include <shared_mutex>
9-
#include <vector>
11+
#include <thread>
1012

1113
namespace NYdb::NPersQueue {
1214

@@ -17,18 +19,62 @@ template <typename TGuardedObject>
1719
class TCallbackContext {
1820
friend class TContextOwner<TGuardedObject>;
1921

22+
// thread_id -> number of LockShared calls from this thread
23+
using TSharedLockCounter = std::map<std::thread::id, size_t>;
24+
using TSharedLockCounterPtr = std::shared_ptr<TSharedLockCounter>;
25+
using TSpinLockPtr = std::shared_ptr<TSpinLock>;
26+
2027
public:
2128
using TMutexPtr = std::shared_ptr<std::shared_mutex>;
2229

2330
class TBorrowed {
2431
public:
25-
explicit TBorrowed(const TCallbackContext& parent) : Mutex(parent.Mutex) {
26-
Mutex->lock_shared();
32+
explicit TBorrowed(const TCallbackContext& parent)
33+
: Mutex(parent.Mutex)
34+
, SharedLockCounterMutex(parent.SharedLockCounterMutex)
35+
, SharedLockCounter(parent.SharedLockCounter)
36+
{
37+
// "Recursive shared lock".
38+
//
39+
// https://en.cppreference.com/w/cpp/thread/shared_mutex/lock_shared says:
40+
// If lock_shared is called by a thread that already owns the mutex
41+
// in any mode (exclusive or shared), the behavior is UNDEFINED.
42+
//
43+
// So if a thread calls LockShared more than once without releasing the lock,
44+
// we should call lock_shared only on the first call.
45+
46+
bool takeLock = false;
47+
48+
with_lock(*SharedLockCounterMutex) {
49+
auto& counter = SharedLockCounter->emplace(std::this_thread::get_id(), 0).first->second;
50+
++counter;
51+
takeLock = counter == 1;
52+
}
53+
54+
if (takeLock) {
55+
Mutex->lock_shared();
56+
}
57+
2758
Ptr = parent.GuardedObjectPtr.get();
2859
}
2960

3061
~TBorrowed() {
31-
Mutex->unlock_shared();
62+
bool releaseLock = false;
63+
64+
with_lock(*SharedLockCounterMutex) {
65+
auto it = SharedLockCounter->find(std::this_thread::get_id());
66+
Y_ABORT_UNLESS(it != SharedLockCounter->end());
67+
auto& counter = it->second;
68+
--counter;
69+
if (counter == 0) {
70+
releaseLock = true;
71+
SharedLockCounter->erase(it);
72+
}
73+
}
74+
75+
if (releaseLock) {
76+
Mutex->unlock_shared();
77+
}
3278
}
3379

3480
TGuardedObject* operator->() {
@@ -46,12 +92,17 @@ class TCallbackContext {
4692
private:
4793
TMutexPtr Mutex;
4894
TGuardedObject* Ptr = nullptr;
95+
96+
TSpinLockPtr SharedLockCounterMutex;
97+
TSharedLockCounterPtr SharedLockCounter;
4998
};
5099

51100
public:
52101
explicit TCallbackContext(std::shared_ptr<TGuardedObject> ptr)
53102
: Mutex(std::make_shared<std::shared_mutex>())
54103
, GuardedObjectPtr(std::move(ptr))
104+
, SharedLockCounterMutex(std::make_shared<TSpinLock>())
105+
, SharedLockCounter(std::make_shared<TSharedLockCounter>())
55106
{}
56107

57108
TBorrowed LockShared() {
@@ -75,8 +126,12 @@ class TCallbackContext {
75126
}
76127

77128
private:
129+
78130
TMutexPtr Mutex;
79131
std::shared_ptr<TGuardedObject> GuardedObjectPtr;
132+
133+
TSpinLockPtr SharedLockCounterMutex;
134+
TSharedLockCounterPtr SharedLockCounter;
80135
};
81136

82137
template<typename T>

0 commit comments

Comments
 (0)