fix borrowable_ptr - #16005
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to fix a thread-safety/race issue in borrowable_ptr by changing how borrowed_ptr instances are created (via an immutable std::weak_ptr), and updates the unit test to exercise the reset/borrow interaction in a multi-threaded scenario.
Changes:
- Construct
borrowed_ptrviam_weakPtr.lock()and makeborrow()const. - Change
reset()behavior to avoid unconditional mutex operations when empty. - Update
BorrowableTest.TwoThreadsto reset the borrowable and add additional borrowing after a forced scheduling point.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
src/util/borrowable_ptr.h |
Adjusts borrowing/reset implementation to address thread-safety concerns by using a weak_ptr for borrowing and changing reset semantics. |
src/test/borrowabletest.cpp |
Updates the concurrency test scenario to validate reset behavior and guard against deadlocks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ~borrowable_ptr() { | ||
| reset(); | ||
| m_mutex.unlock(); | ||
| } | ||
|
|
||
| // @brief Assign a new raw pointer to the `borrowable_ptr` but not owning | ||
| // @param p Raw pointer to the new object. | ||
| // @return Reference to this instance. | ||
| borrowable_ptr& operator=(Tp* p) { | ||
| reset(); | ||
| m_mutex.unlock(); | ||
| m_sharedPtr = std::shared_ptr<Tp>(p, borrowable_deleter(&m_mutex)); | ||
| return *this; | ||
| // m_mutex.unlock(); | ||
| } |
There was a problem hiding this comment.
The destructor leaves a commented-out m_mutex.unlock() behind. Please remove this dead code (and, if unlocking is no longer needed with the updated reset(), consider documenting that in a short comment instead).
There was a problem hiding this comment.
I have removed the dead code as suggested.
| std::shared_ptr<Tp> m_sharedPtr; ///< Non-owning private shared pointer to the managed object. | ||
| // must not be changed after construction, because changing shared pointers is not thread safe. | ||
| const std::weak_ptr<Tp> m_weakPtr; ///< Non-owning shared pointer for sharing, |
There was a problem hiding this comment.
The member comment for m_weakPtr is misleading/incomplete: it’s a std::weak_ptr (not a shared pointer), and the trailing comma makes the sentence incomplete. Please update the comment to clearly describe why this weak_ptr exists and what thread-safety guarantee it provides.
| std::shared_ptr<Tp> m_sharedPtr; ///< Non-owning private shared pointer to the managed object. | |
| // must not be changed after construction, because changing shared pointers is not thread safe. | |
| const std::weak_ptr<Tp> m_weakPtr; ///< Non-owning shared pointer for sharing, | |
| std::shared_ptr<Tp> m_sharedPtr; ///< Private shared pointer to the managed object, reset only by `borrowable_ptr`. | |
| // Must not be changed after construction, because changing shared pointers is not thread safe. | |
| const std::weak_ptr<Tp> m_weakPtr; ///< Const weak reference to `m_sharedPtr` used by `borrow()` so that threads can | |
| ///< safely obtain temporary strong references via `lock()` without mutating `m_sharedPtr`. |
ywwg
left a comment
There was a problem hiding this comment.
this is looking good, as far as I can understand how this works :).
| ~borrowable_ptr() { | ||
| reset(); | ||
| m_mutex.unlock(); | ||
| } | ||
|
|
||
| // @brief Assign a new raw pointer to the `borrowable_ptr` but not owning | ||
| // @param p Raw pointer to the new object. | ||
| // @return Reference to this instance. | ||
| borrowable_ptr& operator=(Tp* p) { | ||
| reset(); | ||
| m_mutex.unlock(); | ||
| m_sharedPtr = std::shared_ptr<Tp>(p, borrowable_deleter(&m_mutex)); | ||
| return *this; | ||
| // m_mutex.unlock(); | ||
| } |
d78d648 to
d3fba27
Compare
to avoid a race condition with reset(). Also remove copy constructor, because writing the shared_ptr itself is not thread safe.
d3fba27 to
91f2a0f
Compare
| int* p2 = borrowed1.get(); | ||
| qDebug() << "future2" << (p1 ? *p1 : 0) << (p2 ? *p2 : 0); | ||
| int* p2 = borrowed2.get(); | ||
| qDebug() << "future2 a" << (p1 ? *p1 : 0) << (p2 ? *p2 : 0); |
|
Ups, already merged via ma private repository |
This is a copy of #14914 for review purpose.