-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DRAFT] Add ReadWrite locking mechanism on top of Blockchain. #9173
[DRAFT] Add ReadWrite locking mechanism on top of Blockchain. #9173
Conversation
73e87fe
to
27f98a7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some Initial thoughts. I'm so glad someone is working on this! It is sorely needed, especially for production nodes which have a lot of concurrent readers
src/cryptonote_core/blockchain.cpp
Outdated
m_blockchain_transaction.start_write(); | ||
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = | ||
epee::misc_utils::create_scope_leave_handler([&](){ | ||
m_blockchain_transaction.end_write(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this pattern is a good candidate for an RAII class. This class could also let you lock and unlock more finely within this scope depending on the operations required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree here. Eventually, we want to have a fine-grained locking mechanism. RAII here would just make this a lot more cumbersome to use.
Consider this as prototype/experimental PR, which does have bugs, that is why I am using auto_scope_leave_caller
here. Eventually, we want to only lock where we interact with write/reads. Not whole functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can still have fine-grained locking with RAII. Consider std::unique_lock
.
src/cryptonote_core/blockchain.cpp
Outdated
@@ -1102,7 +1136,11 @@ size_t Blockchain::recalculate_difficulties(boost::optional<uint64_t> start_heig | |||
//------------------------------------------------------------------ | |||
std::vector<time_t> Blockchain::get_last_block_timestamps(unsigned int blocks) const | |||
{ | |||
CRITICAL_REGION_LOCAL(m_blockchain_lock); | |||
m_blockchain_transaction.start_write(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are all these start_write()
even for functions which only read data?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider this as prototype/experimental PR, which does have bugs.
This is a symptom of a bigger problem we have. Imagine this scenario: Functions calls: A
->B
->C
->D
.
A
is a write transaction. But B
, C
and D
are read transactions. We have to acquire a write lock for B
, C
, and D
since read and write transactions in this implementation are exclusive. (I am fixing it).
Piggybacking on the previous comment, if we do implement more fine-grained locking we would not have this problem.
src/cryptonote_core/blockchain.h
Outdated
boost::condition_variable_any r_condition; | ||
|
||
boost::recursive_mutex w_mutex; | ||
std::atomic<unsigned int> w_locks; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would there allowed to be more than 1 write lock at a time?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recursive function calls.
E.g. Imagine this function calls: A
->B
->C
->D
.
A
, B
, and C
are a write transaction. But D
is read transactions.
(this will be fixed too).
One other approach would just check boost::thread::id
and if you are the owner then don't lock again. That's one approach. I am evaluating which way we should go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How much would the code have to be refactored so that we don't use the locks recursively in the first place? (probably a lot)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is one of the cleaner approaches and end result of that approach would be much much cleaner. But the problem with that is a lot of APIs should change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
take a look at this line:
This is much cleaner. But at the moment, it is not possible since we have to change a lot of APIs.
Eventually, we want that. A transaction-based blockchain.cpp. Transaction objects will be trivial to create and you can create ReadTransaction
and WriteTransaction
. For now, this is an intermediary patch to improve.
@jeffro256 thanks for your comment. I left some comments too. |
2ebdee1
to
cd40269
Compare
Leaving this here just to keep in mind. This deadlock should be fixed: Thread 1, waiting while holding the lock:
Thread 2, locking while other thread has the lock:
|
7bbb3ed
to
a52f339
Compare
This will fix existing locking issues, and provide a foundation to implement more fine-grained locking mechanisms in future works.
a52f339
to
70ce45a
Compare
This will fix existing locking issues, and provide a foundation to implement more fine-grained locking mechanisms in future works.