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

Fix possible race condition in user-defined thread-safety mechanism #262

Merged
merged 5 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/core/agent/agent.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,11 +151,23 @@ class Agent {

/// If the thread-safety mechanism is set to user-specified this function
/// will be called before the operations are executed for this agent.\n
/// Subclasses define the critical region by adding the uids of all
/// Subclasses define the critical region by adding the AgentPointers of all
/// agents that must not be processed in parallel. \n
/// Don't forget to add the uid of the current agent.\n
/// Don't forget to add the current agent.\n
/// Here an example from NeuronSoma.\n
///
/// void NeuronSoma::CriticalRegion(std::vector<AgentPointer<>>* aptrs) {
/// aptrs->reserve(daughters_.size() + 1);
/// aptrs->push_back(Agent::GetAgentPtr<>());
/// for (auto& daughter : daughters_) {
/// aptrs->push_back(daughter);
/// }
/// }
///
/// \see `Param::thread_safety_mechanism`
virtual void CriticalRegion(std::vector<AgentUid>* uids) {}
/// \see `AgentPointer`
/// \see `NeuronSoma::CriticalRegion`
virtual void CriticalRegion(std::vector<AgentPointer<>>* aptrs) {}

uint32_t GetBoxIdx() const;

Expand Down
8 changes: 6 additions & 2 deletions src/core/agent/agent_pointer.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#include <type_traits>

#include "core/agent/agent_uid.h"
#include "core/execution_context/in_place_exec_ctxt.h"
#include "core/execution_context/execution_context.h"
#include "core/simulation.h"
#include "core/util/root.h"

Expand All @@ -37,7 +37,7 @@ class Agent;
/// the type returned by `Get` and can therefore inline the code from the callee
/// and perform optimizations.
/// @tparam TAgent agent type
template <typename TAgent>
template <typename TAgent = Agent>
class AgentPointer {
public:
explicit AgentPointer(const AgentUid& uid) : uid_(uid) {}
Expand Down Expand Up @@ -106,8 +106,12 @@ class AgentPointer {

const TAgent& operator*() const { return *(this->operator->()); }

bool operator<(const AgentPointer& other) const { return uid_ < other.uid_; }

operator bool() const { return *this != nullptr; } // NOLINT

operator AgentPointer<Agent>() const { return AgentPointer<Agent>(uid_); }

TAgent* Get() { return this->operator->(); }

const TAgent* Get() const { return this->operator->(); }
Expand Down
2 changes: 2 additions & 0 deletions src/core/execution_context/execution_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
#include <utility>
#include <vector>

#include "core/agent/agent_handle.h"
#include "core/agent/agent_uid.h"
#include "core/container/math_array.h"
#include "core/functor.h"
#include "core/operation/operation.h"

Expand Down
39 changes: 26 additions & 13 deletions src/core/execution_context/in_place_exec_ctxt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -157,27 +157,40 @@ void InPlaceExecutionContext::Execute(
critical_region_2_.clear();
locks_.clear();
agent->CriticalRegion(&critical_region_);
// Sort such that the locks further down are acquired in a sorted order
// This technique avoids deadlocks.
std::sort(critical_region_.begin(), critical_region_.end());
for (auto uid : critical_region_) {
locks_.push_back(GetAgent(uid)->GetLock());
// Remove all AgentPointers which correspond to a nullptr.
while (critical_region_.size() && critical_region_.back() == nullptr) {
critical_region_.pop_back();
}
// Remove all duplicate entries
critical_region_.erase(
std::unique(critical_region_.begin(), critical_region_.end()),
critical_region_.end());
for (auto aptr : critical_region_) {
locks_.push_back(aptr->GetLock());
}
for (auto* l : locks_) {
l->lock();
}
agent->CriticalRegion(&critical_region_2_);
// Sort such that the locks further down are acquired in a sorted order
// This technique avoids deadlocks.
std::sort(critical_region_2_.begin(), critical_region_2_.end());
bool same = true;
if (critical_region_.size() == critical_region_2_.size()) {
for (size_t i = 0; i < critical_region_.size(); ++i) {
if (critical_region_[i] != critical_region_2_[i]) {
same = false;
break;
}
}
} else {
same = false;
// Remove all AgentPointers which correspond to a nullptr.
while (critical_region_2_.size() &&
critical_region_2_.back() == nullptr) {
critical_region_2_.pop_back();
}
if (same) {
// Remove all duplicate entries
critical_region_2_.erase(
std::unique(critical_region_2_.begin(), critical_region_2_.end()),
critical_region_2_.end());
// if the critical regions are not the same, then another thread
// changed it before the locks were acquired. In this case we have to
// try again. Otherwise we can leave the while loop.
if (critical_region_ == critical_region_2_) {
break;
}
for (auto* l : locks_) {
Expand Down
19 changes: 12 additions & 7 deletions src/core/execution_context/in_place_exec_ctxt.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <vector>

#include "core/agent/agent_handle.h"
#include "core/agent/agent_pointer.h"
#include "core/agent/agent_uid.h"
#include "core/container/agent_uid_map.h"
#include "core/container/math_array.h"
Expand Down Expand Up @@ -140,13 +141,6 @@ class InPlaceExecutionContext : public ExecutionContext {

ThreadInfo* tinfo_;

/// Contains unique ids of agents that will be removed at the end of each
/// iteration. AgentUids are separated by numa node.
std::vector<AgentUid> remove_;
std::vector<AgentUid> critical_region_;
std::vector<AgentUid> critical_region_2_;
std::vector<Spinlock*> locks_;

/// Pointer to new agents
std::vector<Agent*> new_agents_;

Expand All @@ -169,6 +163,17 @@ class InPlaceExecutionContext : public ExecutionContext {

virtual void RemoveAgentsFromRm(
const std::vector<ExecutionContext*>& all_exec_ctxts);

private:
/// Contains unique ids of agents that will be removed at the end of each
/// iteration. AgentUids are separated by numa node.
std::vector<AgentUid> remove_;
/// Used to determine which agents must not be updated from different threads.
std::vector<AgentPointer<>> critical_region_;
/// Used to determine which agents must not be updated from different threads.
std::vector<AgentPointer<>> critical_region_2_;

std::vector<Spinlock*> locks_;
};

} // namespace bdm
Expand Down
1 change: 1 addition & 0 deletions src/core/type_index.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <vector>
#include "core/agent/agent.h"
#include "core/container/agent_uid_map.h"
#include "core/container/flatmap.h"

class TClass;
Expand Down
16 changes: 8 additions & 8 deletions src/neuroscience/neurite_element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,15 @@ void NeuriteElement::Update(const NewAgentEvent& event) {
}
}

void NeuriteElement::CriticalRegion(std::vector<AgentUid>* uids) {
uids->reserve(4);
uids->push_back(GetUid());
uids->push_back(mother_.GetUid());
if (daughter_left_ != nullptr) {
uids->push_back(daughter_left_.GetUid());
void NeuriteElement::CriticalRegion(std::vector<AgentPointer<>>* aptrs) {
aptrs->reserve(4);
aptrs->push_back(GetAgentPtr<>());
aptrs->push_back(mother_);
if (daughter_left_) {
aptrs->push_back(daughter_left_);
}
if (daughter_right_ != nullptr) {
uids->push_back(daughter_right_.GetUid());
if (daughter_right_) {
aptrs->push_back(daughter_right_);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/neuroscience/neurite_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class NeuriteElement : public Agent, public NeuronOrNeurite {

Spinlock* GetLock() override { return Base::GetLock(); }

void CriticalRegion(std::vector<AgentUid>* uids) override;
void CriticalRegion(std::vector<AgentPointer<>>* aptrs) override;

Shape GetShape() const override { return Shape::kCylinder; }

Expand Down
8 changes: 4 additions & 4 deletions src/neuroscience/neuron_soma.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ void NeuronSoma::Update(const NewAgentEvent& event) {
// do nothing for CellDivisionEvent or others
}

void NeuronSoma::CriticalRegion(std::vector<AgentUid>* uids) {
uids->reserve(daughters_.size() + 1);
uids->push_back(Agent::GetUid());
void NeuronSoma::CriticalRegion(std::vector<AgentPointer<>>* aptrs) {
aptrs->reserve(daughters_.size() + 1);
aptrs->push_back(Agent::GetAgentPtr<>());
for (auto& daughter : daughters_) {
uids->push_back(daughter.GetUid());
aptrs->push_back(daughter);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/neuroscience/neuron_soma.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class NeuronSoma : public Cell, public NeuronOrNeurite {

Spinlock* GetLock() override { return Base::GetLock(); }

void CriticalRegion(std::vector<AgentUid>* uids) override;
void CriticalRegion(std::vector<AgentPointer<>>* aptrs) override;

// ***************************************************************************
// METHODS FOR NEURON TREE STRUCTURE *
Expand Down