Add ReadWrite locking mechanism on top of Blockchain.

This will fix existing locking issues, and provide a foundation to implement
more fine-grained locking mechanisms in future works. reader_writer_lock include
wait_queue to prevent writer starvation. In this design, order
between read(s) and write will be preserved.

Co-authored-by: Jeffro <jeffro256@tutanota.com>
pull/9181/head
0xFFFC0000 3 months ago
parent 1bec71279e
commit 84844843ac
No known key found for this signature in database
GPG Key ID: 650F7C2B7BDA3819

@ -15,7 +15,7 @@ env:
REMOVE_BUNDLED_PACKAGES : sudo rm -rf /usr/local
BUILD_DEFAULT_LINUX: |
cmake -S . -B build -D ARCH="default" -D BUILD_TESTS=ON -D CMAKE_BUILD_TYPE=Release && cmake --build build -j3
APT_INSTALL_LINUX: 'sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev libprotobuf-dev protobuf-compiler ccache'
APT_INSTALL_LINUX: 'sudo apt -y install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev libzmq3-dev libsodium-dev libhidapi-dev libnorm-dev libusb-1.0-0-dev libpgm-dev libprotobuf-dev protobuf-compiler ccache libtsan0'
APT_SET_CONF: |
echo "Acquire::Retries \"3\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom
echo "Acquire::http::Timeout \"120\";" | sudo tee -a /etc/apt/apt.conf.d/80-custom

@ -30,12 +30,23 @@
#ifndef __WINH_OBJ_H__
#define __WINH_OBJ_H__
#include <algorithm>
#include <boost/chrono/duration.hpp>
#include <boost/functional/hash/hash.hpp>
#include <boost/thread/condition_variable.hpp>
#include <boost/thread/detail/thread.hpp>
#include <boost/thread/locks.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/recursive_mutex.hpp>
#include <boost/thread/thread.hpp>
#include <cstdint>
#include <queue>
#include <set>
#include <utility>
#include <functional>
#include <vector>
#include "misc_log_ex.h"
#include "misc_language.h"
namespace epee
{
@ -149,7 +160,6 @@ namespace epee
}
};
#define CRITICAL_REGION_LOCAL(x) {} epee::critical_region_t<decltype(x)> critical_region_var(x)
#define CRITICAL_REGION_BEGIN(x) { boost::this_thread::sleep_for(boost::chrono::milliseconds(epee::debug::g_test_dbg_lock_sleep())); epee::critical_region_t<decltype(x)> critical_region_var(x)
#define CRITICAL_REGION_LOCAL1(x) {boost::this_thread::sleep_for(boost::chrono::milliseconds(epee::debug::g_test_dbg_lock_sleep()));} epee::critical_region_t<decltype(x)> critical_region_var1(x)
@ -157,6 +167,20 @@ namespace epee
#define CRITICAL_REGION_END() }
#define RWLOCK(m_lock) \
m_lock.lock(); \
epee::misc_utils::auto_scope_leave_caller scope_exit_handler##m_lock = \
epee::misc_utils::create_scope_leave_handler([&]() { \
m_lock.unlock(); \
});
#define RLOCK(m_lock) \
m_lock.lock_shared(); \
epee::misc_utils::auto_scope_leave_caller scope_exit_handler##m_lock = \
epee::misc_utils::create_scope_leave_handler([&]() { \
m_lock.unlock_shared(); \
});
}
#endif

@ -41,6 +41,7 @@ set(common_sources
password.cpp
perf_timer.cpp
pruning.cpp
recursive_shared_mutex.cpp
spawn.cpp
threadpool.cpp
updates.cpp

@ -0,0 +1,125 @@
// Copyright (c) 2024, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "recursive_shared_mutex.h"
#include <cassert>
namespace tools
{
// Initialize static member shared_slot_counter
std::atomic<recursive_shared_mutex::slot_t> recursive_shared_mutex::shared_slot_counter{0};
// Initialize static member tlocal_access_per_mutex
thread_local
std::unordered_map<recursive_shared_mutex::slot_t, recursive_shared_mutex::access_counter_t>
recursive_shared_mutex::tlocal_access_per_mutex{};
recursive_shared_mutex::recursive_shared_mutex(): m_rw_mutex{}, m_slot{shared_slot_counter++}
{}
void recursive_shared_mutex::lock()
{
access_counter_t &access = tlocal_access_per_mutex[m_slot];
assert(0 == access || access & write_bit); // cannot upgrade from read->write
if (!access)
m_rw_mutex.lock();
access = (access + 1) | write_bit;
}
bool recursive_shared_mutex::try_lock()
{
access_counter_t &access = tlocal_access_per_mutex[m_slot];
assert(0 == access || access & write_bit); // cannot upgrade from read->write
if (access || m_rw_mutex.try_lock())
{
access = (access + 1) | write_bit;
return true;
}
if (!access)
tlocal_access_per_mutex.erase(m_slot);
return false;
}
void recursive_shared_mutex::unlock()
{
access_counter_t &access = tlocal_access_per_mutex[m_slot];
assert(access & depth_mask); // write depth must be non zero
const bool still_held = --access & depth_mask;
if (!still_held)
{
m_rw_mutex.unlock();
tlocal_access_per_mutex.erase(m_slot);
}
}
void recursive_shared_mutex::lock_shared()
{
access_counter_t &access = tlocal_access_per_mutex[m_slot];
if (!(access++))
m_rw_mutex.lock_shared();
}
bool recursive_shared_mutex::try_lock_shared()
{
access_counter_t &access = tlocal_access_per_mutex[m_slot];
if (access || m_rw_mutex.try_lock_shared())
{
++access;
return true;
}
if (!access)
tlocal_access_per_mutex.erase(m_slot);
return false;
}
void recursive_shared_mutex::unlock_shared()
{
access_counter_t &access = tlocal_access_per_mutex[m_slot];
assert(access & depth_mask); // read depth must be non zero
const bool still_held = --access & depth_mask;
if (!still_held)
{
m_rw_mutex.unlock_shared();
tlocal_access_per_mutex.erase(m_slot);
}
}
} // namespace tools

@ -0,0 +1,86 @@
// Copyright (c) 2024, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <atomic>
#include <cstdint>
#include <limits>
#include <unordered_map>
#include <boost/thread/shared_mutex.hpp>
namespace tools
{
// Implements Lockable & SharedLockable C++14 concepts. Allows for recursion of all calls when the
// first acquisition was exclusive, and recursion of shared access calls whens first acquisition was
// shared. Attempting to acquire exclusive ownership of the mutex when this thread already has
// shared ownership is undefined behavior. Maximum recursion depth is 2^31. Maxium number of
// instances per program is 2^32. This implementation only requires one boost::shared_mutex and a
// per-thread map of access counters, and so will be relatively performant for most use cases.
// Because a boost::shared_mutex is used internally, access to this lock is guaranteed to be fair.
class recursive_shared_mutex
{
public:
recursive_shared_mutex();
// delete copy/move construct/assign operations
recursive_shared_mutex(const recursive_shared_mutex&) = delete;
recursive_shared_mutex(recursive_shared_mutex&&) = delete;
recursive_shared_mutex& operator=(const recursive_shared_mutex&) = delete;
recursive_shared_mutex& operator=(recursive_shared_mutex&&) = delete;
// Lockable
void lock();
bool try_lock();
void unlock();
// SharedLockable
void lock_shared();
bool try_lock_shared();
void unlock_shared();
private:
using slot_t = std::uint32_t;
using access_counter_t = std::uint32_t;
static constexpr access_counter_t access_counter_max = std::numeric_limits<access_counter_t>::max();
static constexpr access_counter_t depth_mask = access_counter_max >> 1;
static constexpr access_counter_t write_bit = access_counter_max - depth_mask;
// what the slot id will be for the next instance of recursive_shared_mutex
static std::atomic<slot_t> shared_slot_counter;
// keeps track of read/write depth and write mode per instance of recursive_shared_mutex
static thread_local std::unordered_map<slot_t, access_counter_t> tlocal_access_per_mutex;
boost::shared_mutex m_rw_mutex; // we use boost::shared_mutex since it is guaranteed fair, unlike std::shared_mutex
const slot_t m_slot; // this is an ID number used per-thread to identify whether already held (enables recursion)
};
} // namespace tools

@ -99,7 +99,8 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) :
m_btc_valid(false),
m_batch_success(true),
m_prepare_height(0),
m_rct_ver_cache()
m_rct_ver_cache(),
m_blockchain_lock()
{
LOG_PRINT_L3("Blockchain::" << __func__);
}
@ -139,7 +140,7 @@ bool Blockchain::scan_outputkeys_for_indexes(size_t tx_version, const txin_to_ke
LOG_PRINT_L3("Blockchain::" << __func__);
// ND: Disable locking and make method private.
//CRITICAL_REGION_LOCAL(m_blockchain_lock);
// m_blockchain_lock.lock();
// verify that the input has key offsets (that it exists properly, really)
if(!tx_in_to_key.key_offsets.size())
@ -284,7 +285,7 @@ bool Blockchain::init(BlockchainDB* db, const network_type nettype, bool offline
CHECK_AND_ASSERT_MES(nettype != FAKECHAIN || test_options, false, "fake chain network type used without options");
CRITICAL_REGION_LOCAL(m_tx_pool);
CRITICAL_REGION_LOCAL1(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
if (db == nullptr)
{
@ -552,7 +553,7 @@ void Blockchain::pop_blocks(uint64_t nblocks)
{
uint64_t i = 0;
CRITICAL_REGION_LOCAL(m_tx_pool);
CRITICAL_REGION_LOCAL1(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
bool stop_batch = m_db->batch_start();
@ -593,7 +594,7 @@ void Blockchain::pop_blocks(uint64_t nblocks)
block Blockchain::pop_block_from_blockchain()
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
m_timestamps_and_difficulties_height = 0;
m_reset_timestamps_and_difficulties_height = true;
@ -679,7 +680,7 @@ block Blockchain::pop_block_from_blockchain()
bool Blockchain::reset_and_set_genesis_block(const block& b)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
m_timestamps_and_difficulties_height = 0;
m_reset_timestamps_and_difficulties_height = true;
invalidate_block_template_cache();
@ -698,7 +699,7 @@ bool Blockchain::reset_and_set_genesis_block(const block& b)
crypto::hash Blockchain::get_tail_id(uint64_t& height) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RLOCK(m_blockchain_lock);
return m_db->top_block_hash(&height);
}
//------------------------------------------------------------------
@ -727,7 +728,7 @@ crypto::hash Blockchain::get_tail_id() const
bool Blockchain::get_short_chain_history(std::list<crypto::hash>& ids) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
uint64_t i = 0;
uint64_t current_multiplier = 1;
uint64_t sz = m_db->height();
@ -803,8 +804,7 @@ crypto::hash Blockchain::get_pending_block_id_by_height(uint64_t height) const
bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orphan) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
// try to find block in main chain
try
{
@ -880,7 +880,7 @@ start:
}
}
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
std::vector<uint64_t> timestamps;
std::vector<difficulty_type> difficulties;
uint64_t height;
@ -1016,7 +1016,7 @@ size_t Blockchain::recalculate_difficulties(boost::optional<uint64_t> start_heig
return 0;
}
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
const uint64_t start_height = start_height_opt ? *start_height_opt : check_difficulty_checkpoints().second;
const uint64_t top_height = m_db->height() - 1;
@ -1102,7 +1102,7 @@ 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);
RWLOCK(m_blockchain_lock);
uint64_t height = m_db->height();
if (blocks > height)
blocks = height;
@ -1118,7 +1118,7 @@ std::vector<time_t> Blockchain::get_last_block_timestamps(unsigned int blocks) c
bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain, uint64_t rollback_height)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
// fail if rollback_height passed is too high
if (rollback_height > m_db->height())
@ -1162,8 +1162,7 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain,
bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info>& alt_chain, bool discard_disconnected_chain)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
m_timestamps_and_difficulties_height = 0;
m_reset_timestamps_and_difficulties_height = true;
@ -1303,7 +1302,7 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std:
// based on its blocks alone, need to get more blocks from the main chain
if(alt_chain.size()< DIFFICULTY_BLOCKS_COUNT)
{
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
// Figure out start and stop offsets for main chain blocks
size_t main_chain_stop_offset = alt_chain.size() ? alt_chain.front().height : bei.height;
@ -1461,7 +1460,7 @@ bool Blockchain::validate_miner_transaction(const block& b, size_t cumulative_bl
void Blockchain::get_last_n_blocks_weights(std::vector<uint64_t>& weights, size_t count) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
auto h = m_db->height();
// this function is meaningless for an empty blockchain...granted it should never be empty
@ -1476,8 +1475,7 @@ void Blockchain::get_last_n_blocks_weights(std::vector<uint64_t>& weights, size_
uint64_t Blockchain::get_long_term_block_weight_median(uint64_t start_height, size_t count) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
PERF_TIMER(get_long_term_block_weights);
CHECK_AND_ASSERT_THROW_MES(count > 0, "count == 0");
@ -1555,7 +1553,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block,
m_tx_pool.lock();
const auto unlock_guard = epee::misc_utils::create_scope_leave_handler([&]() { m_tx_pool.unlock(); });
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
if (m_btc_valid && !from_block) {
// The pool cookie is atomic. The lack of locking is OK, as if it changes
// just as we compare it, we'll just use a slightly old template, but
@ -1851,7 +1849,7 @@ bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vect
if(timestamps.size() >= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)
return true;
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps.size();
CHECK_AND_ASSERT_MES(start_top_height < m_db->height(), false, "internal error: passed start_height not < " << " m_db->height() -- " << start_top_height << " >= " << m_db->height());
size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements : 0;
@ -1928,7 +1926,7 @@ bool Blockchain::build_alt_chain(const crypto::hash &prev_id, std::list<block_ex
bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
m_timestamps_and_difficulties_height = 0;
m_reset_timestamps_and_difficulties_height = true;
uint64_t block_height = get_block_height(b);
@ -2152,7 +2150,7 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata,block>>& blocks, std::vector<cryptonote::blobdata>& txs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
if(start_offset >= m_db->height())
return false;
@ -2174,7 +2172,7 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::vector<std
bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::vector<std::pair<cryptonote::blobdata,block>>& blocks) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
const uint64_t height = m_db->height();
if(start_offset >= height)
return false;
@ -2202,7 +2200,7 @@ bool Blockchain::get_blocks(uint64_t start_offset, size_t count, std::vector<std
bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
db_rtxn_guard rtxn_guard (m_db);
rsp.current_blockchain_height = get_current_blockchain_height();
std::vector<std::pair<cryptonote::blobdata,block>> blocks;
@ -2251,8 +2249,7 @@ bool Blockchain::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NO
bool Blockchain::get_alternative_blocks(std::vector<block>& blocks) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
blocks.reserve(m_db->get_alt_block_count());
m_db->for_all_alt_blocks([&blocks](const crypto::hash &blkid, const cryptonote::alt_block_data_t &data, const cryptonote::blobdata_ref *blob) {
if (!blob)
@ -2273,7 +2270,7 @@ bool Blockchain::get_alternative_blocks(std::vector<block>& blocks) const
size_t Blockchain::get_alternative_blocks_count() const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
return m_db->get_alt_block_count();
}
//------------------------------------------------------------------
@ -2307,8 +2304,7 @@ crypto::public_key Blockchain::get_output_key(uint64_t amount, uint64_t global_i
bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
res.outs.clear();
res.outs.reserve(req.outputs.size());
@ -2416,8 +2412,7 @@ bool Blockchain::get_output_distribution(uint64_t amount, uint64_t from_height,
bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, uint64_t& starter_offset) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
// make sure the request includes at least the genesis block, otherwise
// how can we expect to sync from the client that the block list came from?
if(qblock_ids.empty())
@ -2494,8 +2489,7 @@ template<class t_ids_container, class t_blocks_container, class t_missed_contain
bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container& blocks, t_missed_container& missed_bs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
reserve_container(blocks, block_ids.size());
for (const auto& block_hash : block_ids)
{
@ -2579,7 +2573,7 @@ static bool fill(BlockchainDB *db, const crypto::hash &tx_hash, tx_blob_entry &t
bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<cryptonote::blobdata>& txs, std::vector<crypto::hash>& missed_txs, bool pruned) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
txs.reserve(txs_ids.size());
for (const auto& tx_hash : txs_ids)
@ -2603,7 +2597,7 @@ bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>& txs_ids
bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>& txs_ids, std::vector<tx_blob_entry>& txs, std::vector<crypto::hash>& missed_txs, bool pruned) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
txs.reserve(txs_ids.size());
for (const auto& tx_hash : txs_ids)
@ -2639,8 +2633,7 @@ template<class t_ids_container, class t_tx_container, class t_missed_container>
bool Blockchain::get_split_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
reserve_container(txs, txs_ids.size());
for (const auto& tx_hash : txs_ids)
{
@ -2673,8 +2666,7 @@ template<class t_ids_container, class t_tx_container, class t_missed_container>
bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
reserve_container(txs, txs_ids.size());
for (const auto& tx_hash : txs_ids)
{
@ -2709,8 +2701,7 @@ bool Blockchain::get_transactions(const t_ids_container& txs_ids, t_tx_container
bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::vector<crypto::hash>& hashes, std::vector<uint64_t>* weights, uint64_t& start_height, uint64_t& current_height, bool clip_pruned) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
// if we can't find the split point, return false
if(!find_blockchain_supplement(qblock_ids, start_height))
{
@ -2748,8 +2739,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc
bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, bool clip_pruned, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, &resp.m_block_weights, resp.start_height, resp.total_height, clip_pruned);
if (result)
{
@ -2768,8 +2758,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc
bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::vector<std::pair<std::pair<cryptonote::blobdata, crypto::hash>, std::vector<std::pair<crypto::hash, cryptonote::blobdata> > > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, bool get_miner_tx_hash, size_t max_block_count, size_t max_tx_count) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
// if a specific start height has been requested
if(req_start_block > 0)
{
@ -2808,7 +2797,7 @@ bool Blockchain::add_block_as_invalid(const block& bl, const crypto::hash& h)
bool Blockchain::add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
auto i_res = m_invalid_blocks.insert(std::map<crypto::hash, block_extended_info>::value_type(h, bei));
CHECK_AND_ASSERT_MES(i_res.second, false, "at insertion invalid by tx returned status existed");
MINFO("BLOCK ADDED AS INVALID: " << h << std::endl << ", prev_id=" << bei.bl.prev_id << ", m_invalid_blocks count=" << m_invalid_blocks.size());
@ -2818,7 +2807,7 @@ bool Blockchain::add_block_as_invalid(const block_extended_info& bei, const cryp
void Blockchain::flush_invalid_blocks()
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
m_invalid_blocks.clear();
}
//------------------------------------------------------------------
@ -2886,7 +2875,7 @@ size_t Blockchain::get_total_transactions() const
bool Blockchain::check_for_double_spend(const transaction& tx, key_images_container& keys_this_block) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
struct add_transaction_input_visitor: public boost::static_visitor<bool>
{
key_images_container& m_spent_keys;
@ -2946,7 +2935,7 @@ bool Blockchain::check_for_double_spend(const transaction& tx, key_images_contai
bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, size_t n_txes, std::vector<std::vector<uint64_t>>& indexs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
uint64_t tx_index;
if (!m_db->tx_exists(tx_id, tx_index))
{
@ -2962,7 +2951,7 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, size_t n_txes
bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
uint64_t tx_index;
if (!m_db->tx_exists(tx_id, tx_index))
{
@ -3004,8 +2993,7 @@ void Blockchain::on_new_tx_from_block(const cryptonote::transaction &tx)
bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_height, crypto::hash& max_used_block_id, tx_verification_context &tvc, bool kept_by_block) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
#if defined(PER_BLOCK_CHECKPOINT)
// check if we're doing per-block checkpointing
if (m_db->height() < m_blocks_hash_check.size() && kept_by_block)
@ -3035,8 +3023,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh
bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context &tvc) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
const uint8_t hf_version = m_hardfork->get_current_version();
// from hard fork 2, we forbid dust and compound outputs
@ -3948,7 +3935,7 @@ bool Blockchain::check_tx_input(size_t tx_version, const txin_to_key& txin, cons
// ND:
// 1. Disable locking and make method private.
//CRITICAL_REGION_LOCAL(m_blockchain_lock);
// RWLOCK(m_blockchain_transaction)
struct outputs_visitor
{
@ -4138,7 +4125,7 @@ bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash&
LOG_PRINT_L3("Blockchain::" << __func__);
TIME_MEASURE_START(block_processing_time);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
TIME_MEASURE_START(t1);
static bool seen_future_version = false;
@ -4542,8 +4529,7 @@ bool Blockchain::prune_blockchain(uint32_t pruning_seed)
{
m_tx_pool.lock();
epee::misc_utils::auto_scope_leave_caller unlocker = epee::misc_utils::create_scope_leave_handler([&](){m_tx_pool.unlock();});
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
return m_db->prune_blockchain(pruning_seed);
}
//------------------------------------------------------------------
@ -4551,8 +4537,7 @@ bool Blockchain::update_blockchain_pruning()
{
m_tx_pool.lock();
epee::misc_utils::auto_scope_leave_caller unlocker = epee::misc_utils::create_scope_leave_handler([&](){m_tx_pool.unlock();});
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
return m_db->update_pruning();
}
//------------------------------------------------------------------
@ -4560,8 +4545,7 @@ bool Blockchain::check_blockchain_pruning()
{
m_tx_pool.lock();
epee::misc_utils::auto_scope_leave_caller unlocker = epee::misc_utils::create_scope_leave_handler([&](){m_tx_pool.unlock();});
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
return m_db->check_pruning();
}
//------------------------------------------------------------------
@ -4663,7 +4647,9 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc)
LOG_PRINT_L3("Blockchain::" << __func__);
crypto::hash id = get_block_hash(bl);
CRITICAL_REGION_LOCAL(m_tx_pool);//to avoid deadlock lets lock tx_pool for whole add/reorganize process
// To avoid deadlock lets lock tx_pool for whole add/reorganize process
CRITICAL_REGION_LOCAL(m_tx_pool);
CRITICAL_REGION_LOCAL1(m_blockchain_lock);
db_rtxn_guard rtxn_guard(m_db);
if(have_block(id))
@ -4687,8 +4673,8 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc)
}
rtxn_guard.stop();
return handle_block_to_main_chain(bl, id, bvc);
return handle_block_to_main_chain(bl, id, bvc);
}
catch (const std::exception &e)
{
@ -4705,7 +4691,7 @@ void Blockchain::check_against_checkpoints(const checkpoints& points, bool enfor
const auto& pts = points.get_points();
bool stop_batch;
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
stop_batch = m_db->batch_start();
const uint64_t blockchain_height = m_db->height();
for (const auto& pt : pts)
@ -4804,6 +4790,7 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync)
MTRACE("Blockchain::" << __func__);
CRITICAL_REGION_BEGIN(m_blockchain_lock);
TIME_MEASURE_START(t1);
try
@ -4899,8 +4886,7 @@ uint64_t Blockchain::prevalidate_block_hashes(uint64_t height, const std::vector
CHECK_AND_ASSERT_MES(weights.empty() || weights.size() == hashes.size(), 0, "Unexpected weights size");
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
// easy case: height >= hashes
if (height >= m_blocks_hash_of_hashes.size() * HASH_OF_HASHES_STEP)
return hashes.size();
@ -5055,6 +5041,7 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
// txpool and blockchain locks were not held
m_tx_pool.lock();
// This locking is special case, lets handle it manually
CRITICAL_REGION_LOCAL1(m_blockchain_lock);
if(blocks_entry.size() == 0)
@ -5442,7 +5429,7 @@ void Blockchain::add_block_notify(BlockNotifyCallback&& notify)
{
if (notify)
{
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
m_block_notifiers.push_back(std::move(notify));
}
}
@ -5451,7 +5438,7 @@ void Blockchain::add_miner_notify(MinerNotifyCallback&& notify)
{
if (notify)
{
CRITICAL_REGION_LOCAL(m_blockchain_lock);
RWLOCK(m_blockchain_lock);
m_miner_notifiers.push_back(std::move(notify));
}
}

@ -31,6 +31,7 @@
#pragma once
#include <boost/asio/io_service.hpp>
#include <boost/function/function_fwd.hpp>
#include "common/recursive_shared_mutex.h"
#if BOOST_VERSION >= 107400
#include <boost/serialization/library_version_type.hpp>
#endif
@ -48,6 +49,7 @@
#include "span.h"
#include "syncobj.h"
#include "common/recursive_shared_mutex.h"
#include "string_tools.h"
#include "rolling_median.h"
#include "cryptonote_basic/cryptonote_basic.h"
@ -1139,7 +1141,7 @@ namespace cryptonote
tx_memory_pool& m_tx_pool;
mutable epee::critical_section m_blockchain_lock; // TODO: add here reader/writer lock
mutable tools::recursive_shared_mutex m_blockchain_lock;
// main chain
size_t m_current_block_cumul_weight_limit;

@ -127,7 +127,7 @@ namespace cryptonote
}
//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs), m_cookie(0), m_txpool_max_weight(DEFAULT_TXPOOL_MAX_WEIGHT), m_txpool_weight(0), m_mine_stem_txes(false), m_next_check(std::time(nullptr))
tx_memory_pool::tx_memory_pool(Blockchain& bchs): m_blockchain(bchs), m_cookie(0), m_txpool_max_weight(DEFAULT_TXPOOL_MAX_WEIGHT), m_txpool_weight(0), m_mine_stem_txes(false), m_next_check(std::time(nullptr)), m_transactions_lock()
{
// class code expects unsigned values throughout
if (m_next_check < time_t(0))
@ -444,6 +444,8 @@ namespace cryptonote
bytes = m_txpool_max_weight;
CRITICAL_REGION_LOCAL1(m_blockchain);
LockedTXN lock(m_blockchain.get_db());
bool changed = false;

@ -43,6 +43,7 @@
#include "span.h"
#include "string_tools.h"
#include "syncobj.h"
#include "common/recursive_shared_mutex.h"
#include "math_helper.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_basic/verification_context.h"
@ -608,7 +609,7 @@ namespace cryptonote
#if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
public:
#endif
mutable epee::critical_section m_transactions_lock; //!< lock for the pool
mutable tools::recursive_shared_mutex m_transactions_lock;
#if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
private:
#endif

@ -88,6 +88,7 @@ else ()
add_subdirectory(block_weight)
add_subdirectory(hash)
add_subdirectory(net_load_tests)
add_subdirectory(lock_test)
endif()
if (BUILD_GUI_DEPS)

@ -0,0 +1,72 @@
# Copyright (c) 2014-2023, The Monero Project
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are
# permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of
# conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
# of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
set(lock_tests_source
lock_tests.cpp)
monero_add_minimal_executable(lock_tests
${lock_tests_source})
target_link_libraries(lock_tests
PRIVATE
epee
common
${GTEST_LIBRARIES}
${Boost_CHRONO_LIBRARY}
${Boost_DATE_TIME_LIBRARY}
${Boost_SYSTEM_LIBRARY}
${Boost_THREAD_LIBARRY}
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
if(CMAKE_CXX_COMPILER_ID MATCHES GNU OR CMAKE_CXX_COMPILER_ID MATCHES Clang)
# Due to a bug in upstream ubuntu-20.04
# we have to disable thread sanitizer until the fix
# pushed. Once fixed we can remove this condition and
# set_target_properties unconditionaly.
# https://bugs.launchpad.net/ubuntu/+source/gcc-9/+bug/2029910
if(EXISTS "/usr/lib/x86_64-linux-gnu/libtsan_preinit.o")
set_target_properties(lock_tests
PROPERTIES
COMPILE_FLAGS "-fsanitize=thread -Og -fno-omit-frame-pointer -g"
LINK_FLAGS "-fsanitize=thread -Og -fno-omit-frame-pointer -g")
endif()
endif()
set_property(TARGET lock_tests
PROPERTY
FOLDER "tests")
add_test(
NAME lock_tests
COMMAND lock_tests)
# This is not computation intensive test.
# just locking and unlocking in multiple threads.
# if it fails to complete in 1500 seconds in slow machines.
# Then it means it in somekind of deadlock, and should fail.
set_tests_properties(lock_tests PROPERTIES TIMEOUT 1500)

@ -0,0 +1,185 @@
// Copyright (c) 2018-2023, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "syncobj.h"
#include "common/recursive_shared_mutex.h"
#include <gtest/gtest.h>
#include <boost/thread.hpp>
#include <random>
// number of times each test runs
int test_iteration = 2;
// total number of threads
size_t total_number_of_threads;
// Between 20-80% of the total number of threads.
size_t number_of_writers;
// Remaining number of threads.
size_t number_of_readers;
// Cycles that readers are going to hold the lock. between 2-100 cycles
constexpr size_t reading_cycles_min = 2;
constexpr size_t reading_cycles_max = 25;
// Duration that readers are going to hold the lock. between 5-100 milliseconds
constexpr size_t reading_step_duration_min = 2;
constexpr size_t reading_step_duration_max = 10;
// Cycles that writers are going to hold the lock, between 2-100 cycles
constexpr size_t writing_cycles_min = 2;
constexpr size_t writing_cycles_max = 25;
// Duration that writers are going to hold the lock. between 5-100 milliseconds
constexpr size_t writing_step_duration_min = 2;
constexpr size_t writing_step_duration_max = 10;
tools::recursive_shared_mutex main_lock;
void calculate_parameters(size_t threads) {
total_number_of_threads = threads;
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> d( total_number_of_threads * 0.2 , total_number_of_threads * 0.8);
number_of_writers = d(rng);
number_of_readers = total_number_of_threads - number_of_writers;
}
void reader() {
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> d(reading_cycles_min, reading_cycles_max);
size_t reading_cycles = d(rng);
d = std::uniform_int_distribution<std::mt19937::result_type>(reading_step_duration_min, reading_step_duration_max);
main_lock.lock_shared();
for(int i = 0; i < reading_cycles; i++) {
boost::this_thread::sleep_for(boost::chrono::milliseconds(d(rng)));
}
bool recurse = !((bool) std::uniform_int_distribution<std::mt19937::result_type>(0, 10)(rng) % 4); // ~30%
if (recurse) {
reader();
}
main_lock.unlock_shared();
}
void writer() {
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> d(writing_cycles_min, writing_cycles_max);
size_t writing_cycles = d(rng);
d = std::uniform_int_distribution<std::mt19937::result_type>(writing_step_duration_min, writing_step_duration_max);
main_lock.lock();
for(int i = 0; i < writing_cycles; i++) {
boost::this_thread::sleep_for(boost::chrono::milliseconds(d(rng)));
}
bool recurse = !((bool) std::uniform_int_distribution<std::mt19937::result_type>(0, 10)(rng) % 4); // ~20%
if (recurse) {
bool which = std::uniform_int_distribution<std::mt19937::result_type>(0, 10)(rng) % 2;
if (which) {
writer();
}
else {
reader();
}
}
main_lock.unlock();
}
void RUN_TEST() {
std::vector<boost::thread> threads;
std::random_device dev;
std::mt19937 rng(dev());
std::uniform_int_distribution<std::mt19937::result_type> d(0, 10);
int reader_count = 0;
int writer_count = 0;
while(reader_count < number_of_readers
|| writer_count < number_of_writers ) {
bool which_one = d(rng) % 2;
if(which_one) {
threads.push_back(boost::thread(reader)); reader_count++;
}
else {
threads.push_back(boost::thread(writer)); writer_count++;
}
}
std::for_each(threads.begin(), threads.end(), [] (boost::thread& thread) {
if (thread.joinable()) thread.join();
});
}
TEST(reader_writer_lock_deadlock_tests, test_3)
{
calculate_parameters(3);
for(int i = 0; i < test_iteration; ++i)
RUN_TEST();
}
TEST(reader_writer_lock_deadlock_tests, test_5)
{
calculate_parameters(5);
for(int i = 0; i < test_iteration; ++i)
RUN_TEST();
}
TEST(reader_writer_lock_deadlock_tests, test_10)
{
calculate_parameters(10);
for(int i = 0; i < test_iteration; ++i)
RUN_TEST();
}
TEST(reader_writer_lock_deadlock_tests, test_100)
{
calculate_parameters(100);
for(int i = 0; i < test_iteration; ++i)
RUN_TEST();
}
TEST(reader_writer_lock_deadlock_tests, test_500)
{
calculate_parameters(500);
for(int i = 0; i < test_iteration; ++i)
RUN_TEST();
}
TEST(reader_writer_lock_deadlock_tests, test_1000)
{
calculate_parameters(1000);
for(int i = 0; i < test_iteration; ++i)
RUN_TEST();
}
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Loading…
Cancel
Save