MediaMetrics:LockWrap: Use recursive mutex.
Because one might reference the object twice when
the end of the full expression goes to ;
Test: atest mediametrics_tests
Bug: 138583596
Change-Id: I80ced3af615578d63310acaf70142be900855902
diff --git a/services/mediametrics/Wrap.h b/services/mediametrics/Wrap.h
index 99963fb..3584e08 100644
--- a/services/mediametrics/Wrap.h
+++ b/services/mediametrics/Wrap.h
@@ -150,11 +150,17 @@
*/
class LockedPointer {
friend LockWrap;
- LockedPointer(T *t, std::mutex *lock)
- : mT(t), mLock(*lock) {}
+ LockedPointer(T *t, std::recursive_mutex *lock, std::atomic<size_t> *recursionDepth)
+ : mT(t), mLock(*lock), mRecursionDepth(recursionDepth) { ++*mRecursionDepth; }
+
T* const mT;
- std::lock_guard<std::mutex> mLock;
+ std::lock_guard<std::recursive_mutex> mLock;
+ std::atomic<size_t>* mRecursionDepth;
public:
+ ~LockedPointer() {
+ --*mRecursionDepth; // Used for testing, we do not check underflow.
+ }
+
const T* operator->() const {
return mT;
}
@@ -163,27 +169,36 @@
}
};
- mutable std::mutex mLock;
+ // We must use a recursive mutex because the end of the full expression may
+ // involve another reference to T->.
+ //
+ // A recursive mutex allows the same thread to recursively acquire,
+ // but different thread would block.
+ //
+ // Example which fails with a normal mutex:
+ //
+ // android::mediametrics::LockWrap<std::vector<int>> v{std::initializer_list<int>{1, 2}};
+ // const int sum = v->operator[](0) + v->operator[](1);
+ //
+ mutable std::recursive_mutex mLock;
mutable T mT;
+ mutable std::atomic<size_t> mRecursionDepth{}; // Used for testing.
public:
template <typename... Args>
explicit LockWrap(Args&&... args) : mT(std::forward<Args>(args)...) {}
const LockedPointer operator->() const {
- return LockedPointer(&mT, &mLock);
+ return LockedPointer(&mT, &mLock, &mRecursionDepth);
}
LockedPointer operator->() {
- return LockedPointer(&mT, &mLock);
+ return LockedPointer(&mT, &mLock, &mRecursionDepth);
}
+ // Returns the lock depth of the recursive mutex.
// @TestApi
- bool isLocked() const {
- if (mLock.try_lock()) {
- mLock.unlock();
- return false; // we were able to get the lock.
- }
- return true; // we were NOT able to get the lock.
+ size_t getRecursionDepth() const {
+ return mRecursionDepth;
}
};
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index dea625c..27b72eb 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -112,6 +112,27 @@
s3->operator=("abc");
ASSERT_EQ('b', s3->operator[](1)); // s2[1] = 'b';
+ // Check that we can recursively hold lock.
+ android::mediametrics::LockWrap<std::vector<int>> v{std::initializer_list<int>{1, 2}};
+ v->push_back(3);
+ v->push_back(4);
+ ASSERT_EQ(1, v->operator[](0));
+ ASSERT_EQ(2, v->operator[](1));
+ ASSERT_EQ(3, v->operator[](2));
+ ASSERT_EQ(4, v->operator[](3));
+ // The end of the full expression here requires recursive depth of 4.
+ ASSERT_EQ(10, v->operator[](0) + v->operator[](1) + v->operator[](2) + v->operator[](3));
+
+ // Mikhail's note: a non-recursive lock implementation could be used if one obtains
+ // the LockedPointer helper object like this and directly hold the lock through RAII,
+ // though it is trickier in use.
+ //
+ // We include an example here for completeness.
+ {
+ auto l = v.operator->();
+ ASSERT_EQ(10, l->operator[](0) + l->operator[](1) + l->operator[](2) + l->operator[](3));
+ }
+
// Use Thunk to check whether we have the lock when calling a method through LockWrap.
class Thunk {
@@ -123,11 +144,13 @@
};
android::mediametrics::LockWrap<Thunk> s4([&]{
- ASSERT_EQ(true, s4.isLocked()); // we must be locked when thunk() is called.
+ ASSERT_EQ((size_t)1, s4.getRecursionDepth()); // we must be locked when thunk() is called.
});
+ ASSERT_EQ((size_t)0, s4.getRecursionDepth());
// This will fail if we are not locked during method access.
s4->thunk();
+ ASSERT_EQ((size_t)0, s4.getRecursionDepth());
}
TEST(mediametrics_tests, lock_wrap_multithread) {