Skip to content
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

Conversation

0xFFFC0000
Copy link
Collaborator

This will fix existing locking issues, and provide a foundation to implement more fine-grained locking mechanisms in future works.

Copy link
Contributor

@jeffro256 jeffro256 left a 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

Comment on lines 1352 to 1416
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();
});
Copy link
Contributor

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.

Copy link
Collaborator Author

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.

Copy link
Contributor

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.

@@ -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();
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

boost::condition_variable_any r_condition;

boost::recursive_mutex w_mutex;
std::atomic<unsigned int> w_locks;
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

Copy link
Contributor

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)

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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:

https://github.com/0xFFFC0000/monero/blob/0574c09993f32c3ec532b9511b57fccb0818f2bc/src/cryptonote_core/blockchain.cpp#L119

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.

@0xFFFC0000
Copy link
Collaborator Author

@jeffro256 thanks for your comment. I left some comments too.

@0xFFFC0000 0xFFFC0000 force-pushed the dev/0xfffc/rw-lock-blockchain branch 6 times, most recently from 2ebdee1 to cd40269 Compare February 16, 2024 08:12
@0xFFFC0000
Copy link
Collaborator Author

Leaving this here just to keep in mind. This deadlock should be fixed:

Thread 1, waiting while holding the lock:

libc.so.6!__futex_abstimed_wait_common (Unknown Source:0)
libc.so.6!pthread_cond_wait@@GLIBC_2.3.2 (Unknown Source:0)
boost::condition_variable::wait(boost::unique_lock<boost::mutex> & m, boost::condition_variable * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/thread/pthread/condition_variable.hpp:76)
tools::threadpool::waiter::wait(tools::threadpool::waiter * const this, tools::threadpool::waiter * const this@entry) (/home/0xfffc/developments/monero/src/common/threadpool.cpp:135)
cryptonote::Blockchain::prepare_handle_incoming_blocks(cryptonote::Blockchain * const this, const std::vector<cryptonote::block_complete_entry, std::allocator<cryptonote::block_complete_entry> > & blocks_entry, std::vector<cryptonote::block, std::allocator<cryptonote::block> > & blocks) (/home/0xfffc/developments/monero/src/cryptonote_core/blockchain.cpp:5366)
cryptonote::core::prepare_handle_incoming_blocks(std::vector<cryptonote::block, std::allocator<cryptonote::block> > & blocks, const std::vector<cryptonote::block_complete_entry, std::allocator<cryptonote::block_complete_entry> > & blocks_entry, cryptonote::core * const this) (/home/0xfffc/developments/monero/src/cryptonote_core/cryptonote_core.cpp:1620)
cryptonote::core::handle_block_found(cryptonote::core * const this, cryptonote::block & b, cryptonote::block_verification_context & bvc) (/home/0xfffc/developments/monero/src/cryptonote_core/cryptonote_core.cpp:1555)
cryptonote::core_rpc_server::on_submitblock(cryptonote::core_rpc_server * const this, const cryptonote::COMMAND_RPC_SUBMITBLOCK::request & req, cryptonote::COMMAND_RPC_SUBMITBLOCK::response & res, epee::json_rpc::error & error_resp, const cryptonote::core_rpc_server::connection_context * ctx) (/home/0xfffc/developments/monero/src/rpc/core_rpc_server.cpp:2255)
cryptonote::core_rpc_server::on_generateblocks(cryptonote::core_rpc_server * const this, const cryptonote::COMMAND_RPC_GENERATEBLOCKS::request & req, cryptonote::COMMAND_RPC_GENERATEBLOCKS::response & res, epee::json_rpc::error & error_resp, const cryptonote::core_rpc_server::connection_context * ctx) (/home/0xfffc/developments/monero/src/rpc/core_rpc_server.cpp:2327)
cryptonote::core_rpc_server::handle_http_request_map<epee::net_utils::connection_context_base>(cryptonote::core_rpc_server * const this, cryptonote::core_rpc_server * const this@entry, const epee::net_utils::http::http_request_info & query_info, epee::net_utils::http::http_response_info & response_info, epee::net_utils::connection_context_base & m_conn_context) (/home/0xfffc/developments/monero/src/rpc/core_rpc_server.h:152)
cryptonote::core_rpc_server::handle_http_request(cryptonote::core_rpc_server * const this, const epee::net_utils::http::http_request_info & query_info, epee::net_utils::http::http_response_info & response, cryptonote::core_rpc_server::connection_context & m_conn_context) (/home/0xfffc/developments/monero/src/rpc/core_rpc_server.h:95)
epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base>::handle_request(epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> * const this, const epee::net_utils::http::http_request_info & query_info, epee::net_utils::http::http_response_info & response) (/home/0xfffc/developments/monero/contrib/epee/include/net/http_protocol_handler.h:201)
epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base>::handle_request_and_send_response(epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base> * const this, const epee::net_utils::http::http_request_info & query_info) (/home/0xfffc/developments/monero/contrib/epee/include/net/http_protocol_handler.inl:589)
epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base>::handle_query_measure(epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base> * const this) (/home/0xfffc/developments/monero/contrib/epee/include/net/http_protocol_handler.inl:503)
epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base>::handle_query_measure(epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base> * const this) (/home/0xfffc/developments/monero/contrib/epee/include/net/http_protocol_handler.inl:486)
epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base>::handle_retriving_query_body(epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base> * const this) (/home/0xfffc/developments/monero/contrib/epee/include/net/http_protocol_handler.inl:471)
epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base>::handle_recv(epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base> * const this, epee::net_utils::http::simple_http_connection_handler<epee::net_utils::connection_context_base> * const this@entry, const void * ptr, const void * ptr@entry, size_t cb, size_t cb@entry) (/usr/include/c++/13/bits/new_allocator.h:104)
epee::net_utils::connection<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::start_read()::{lambda(boost::system::error_code const&, unsigned long)#1}::operator()(boost::system::error_code const&, unsigned long) const::{lambda()#1}::operator()() const(const struct {...} * const __closure) (/home/0xfffc/developments/monero/contrib/epee/include/net/abstract_tcp_server2.inl:406)
boost::asio::asio_handler_invoke<epee::net_utils::connection<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::start_read()::{lambda(boost::system::error_code const&, unsigned long)#1}::operator()(boost::system::error_code const&, unsigned long) const::{lambda()#1}>(epee::net_utils::connection<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::start_read()::{lambda(boost::system::error_code const&, unsigned long)#1}::operator()(boost::system::error_code const&, unsigned long) const::{lambda()#1}&, ...)(struct {...} & function) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/handler_invoke_hook.hpp:69)
boost_asio_handler_invoke_helpers::invoke<epee::net_utils::connection<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::start_read()::{lambda(boost::system::error_code const&, unsigned long)#1}::operator()(boost::system::error_code const&, unsigned long) const::{lambda()#1}, epee::net_utils::connection<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::start_read()::{lambda(boost::system::error_code const&, unsigned long)#1}::operator()(boost::system::error_code const&, unsigned long) const::{lambda()#1}>(epee::net_utils::connection<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::start_read()::{lambda(boost::system::error_code const&, unsigned long)#1}::operator()(boost::system::error_code const&, unsigned long) const::{lambda()#1}&, epee::net_utils::connection<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::start_read()::{lambda(boost::system::error_code const&, unsigned long)#1}::operator()(boost::system::error_code const&, unsigned long) const::{lambda()#1}&)(struct {...} & context, struct {...} & function) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/detail/handler_invoke_helpers.hpp:37)
boost::asio::detail::completion_handler<epee::net_utils::connection<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::start_read()::{lambda(boost::system::error_code const&, unsigned long)#1}::operator()(boost::system::error_code const&, unsigned long) const::{lambda()#1}>::do_complete(boost::asio::detail::task_io_service*, boost::asio::detail::task_io_service_operation*, boost::system::error_code const&, unsigned long)(boost::asio::detail::io_service_impl * owner, boost::asio::detail::operation * base) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/detail/completion_handler.hpp:68)
boost::asio::detail::task_io_service_operation::complete(std::size_t bytes_transferred, const boost::system::error_code & ec, boost::asio::detail::task_io_service & owner, boost::asio::detail::task_io_service_operation * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/detail/task_io_service_operation.hpp:38)
boost::asio::detail::strand_service::do_complete(boost::asio::detail::io_service_impl * owner, boost::asio::detail::operation * base, const boost::system::error_code & ec) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/detail/impl/strand_service.ipp:167)
boost::asio::detail::task_io_service_operation::complete(std::size_t bytes_transferred, const boost::system::error_code & ec, boost::asio::detail::task_io_service & owner, boost::asio::detail::task_io_service_operation * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/detail/task_io_service_operation.hpp:38)
boost::asio::detail::task_io_service::do_run_one(const boost::system::error_code & ec, boost::asio::detail::task_io_service::thread_info & this_thread, boost::asio::detail::posix_mutex::scoped_lock & lock, boost::asio::detail::task_io_service * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/detail/impl/task_io_service.ipp:372)
boost::asio::detail::task_io_service::run(boost::system::error_code & ec, boost::asio::detail::task_io_service * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/detail/impl/task_io_service.ipp:149)
boost::asio::io_service::run(boost::asio::io_service * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/asio/impl/io_service.ipp:59)
epee::net_utils::boosted_tcp_server<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> >::worker_thread(epee::net_utils::boosted_tcp_server<epee::net_utils::http::http_custom_handler<epee::net_utils::connection_context_base> > * const this) (/home/0xfffc/developments/monero/contrib/epee/include/net/abstract_tcp_server2.inl:1317)
boost::(anonymous namespace)::thread_proxy(void * param) (/home/0xfffc/developments/monero/contrib/depends/work/build/x86_64-pc-linux-gnu/boost/1_64_0-41f40726f35/libs/thread/src/pthread/thread.cpp:171)
libc.so.6!start_thread (Unknown Source:0)
libc.so.6!clone3 (Unknown Source:0)

Thread 2, locking while other thread has the lock:

libc.so.6!__futex_abstimed_wait_common (Unknown Source:0)
libc.so.6!pthread_cond_wait@@GLIBC_2.3.2 (Unknown Source:0)
boost::condition_variable::wait(boost::unique_lock<boost::mutex> & m, boost::condition_variable * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/thread/pthread/condition_variable.hpp:76)
boost::shared_mutex::lock_shared(boost::shared_mutex * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/thread/pthread/shared_mutex.hpp:191)
boost::shared_lock<boost::shared_mutex>::lock(boost::shared_lock<boost::shared_mutex> * const this) (/home/0xfffc/developments/monero/contrib/depends/x86_64-pc-linux-gnu/include/boost/thread/lock_types.hpp:645)
cryptonote::Blockchain::blockchain_transaction::start_read(cryptonote::Blockchain::blockchain_transaction * const this) (/home/0xfffc/developments/monero/src/cryptonote_core/blockchain.h:142)
cryptonote::Blockchain::get_block_id_by_height(const cryptonote::Blockchain * const this, uint64_t height) (/home/0xfffc/developments/monero/src/cryptonote_core/blockchain.cpp:793)
cryptonote::Blockchain::get_pending_block_id_by_height(const cryptonote::Blockchain * const this, const cryptonote::Blockchain * const this@entry, uint64_t height) (/home/0xfffc/developments/monero/src/cryptonote_core/blockchain.cpp:822)
cryptonote::get_block_longhash(const cryptonote::Blockchain * pbc, const cryptonote::blobdata & bd, crypto::hash & res, const uint64_t height, const int major_version, const crypto::hash * seed_hash, const int miners) (/home/0xfffc/developments/monero/src/cryptonote_core/cryptonote_tx_utils.cpp:695)
cryptonote::get_block_longhash(const int miners, const crypto::hash * seed_hash, const uint64_t height, crypto::hash & res, const cryptonote::block & b, const cryptonote::Blockchain * pbc) (/home/0xfffc/developments/monero/src/cryptonote_core/cryptonote_tx_utils.cpp:711)
cryptonote::get_block_longhash(const cryptonote::Blockchain * pbc, const cryptonote::Blockchain * pbc@entry, const cryptonote::block & b, const uint64_t height, const uint64_t height@entry, const crypto::hash * seed_hash, const crypto::hash * seed_hash@entry, const int miners, const int miners@entry) (/home/0xfffc/developments/monero/src/cryptonote_core/cryptonote_tx_utils.cpp:717)
cryptonote::Blockchain::block_longhash_worker(const cryptonote::Blockchain * const this, uint64_t height, const epee::span<cryptonote::block const> & blocks, std::unordered_map<crypto::hash, crypto::hash, std::hash<crypto::hash>, std::equal_to<crypto::hash>, std::allocator<std::pair<crypto::hash const, crypto::hash> > > & map) (/home/0xfffc/developments/monero/src/cryptonote_core/blockchain.cpp:4985)
std::function<void ()>::operator()() const(const std::function<void()> * const this) (/usr/include/c++/13/bits/std_function.h:591)
tools::threadpool::run(tools::threadpool * const this, bool flush) (/home/0xfffc/developments/monero/src/common/threadpool.cpp:169)
boost::(anonymous namespace)::thread_proxy(void * param) (/home/0xfffc/developments/monero/contrib/depends/work/build/x86_64-pc-linux-gnu/boost/1_64_0-41f40726f35/libs/thread/src/pthread/thread.cpp:171)
libc.so.6!start_thread (Unknown Source:0)
libc.so.6!clone3 (Unknown Source:0)

@0xFFFC0000 0xFFFC0000 force-pushed the dev/0xfffc/rw-lock-blockchain branch 13 times, most recently from 7bbb3ed to a52f339 Compare February 18, 2024 14:14
This will fix existing locking issues, and provide a foundation to implement
more fine-grained locking mechanisms in future works.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants