include src/kernel/actor/ActorImpl.hpp
include src/kernel/actor/CommObserver.cpp
include src/kernel/actor/CommObserver.hpp
+include src/kernel/actor/MutexObserver.cpp
+include src/kernel/actor/MutexObserver.hpp
include src/kernel/actor/SimcallObserver.cpp
include src/kernel/actor/SimcallObserver.hpp
include src/kernel/context/Context.cpp
using IoImplPtr = boost::intrusive_ptr<IoImpl>;
class MutexImpl;
using MutexImplPtr = boost::intrusive_ptr<MutexImpl>;
+ class MutexAcquisitionImpl;
+ using MutexAcquisitionImplPtr = boost::intrusive_ptr<MutexAcquisitionImpl>;
XBT_PUBLIC void intrusive_ptr_add_ref(MutexImpl* mutex);
XBT_PUBLIC void intrusive_ptr_release(MutexImpl* mutex);
class SynchroImpl;
- using RawImplPtr = boost::intrusive_ptr<SynchroImpl>;
+ using SynchroImplPtr = boost::intrusive_ptr<SynchroImpl>;
class SemaphoreImpl;
using SemaphoreImplPtr = boost::intrusive_ptr<SemaphoreImpl>;
XBT_PUBLIC void intrusive_ptr_add_ref(SemaphoreImpl* sem);
else
comm->dst_timeout_ = sleep;
} else {
- RawImplPtr synchro(new SynchroImpl([this, issuer]() {
+ SynchroImplPtr synchro(new SynchroImpl([this, issuer]() {
this->unregister_simcall(&issuer->simcall_);
issuer->waiting_synchro_ = nullptr;
issuer->exception_ = nullptr;
// Support for the boost::intrusive_ptr<ActivityImpl> datatype
friend XBT_PUBLIC void intrusive_ptr_add_ref(ActivityImpl* activity);
friend XBT_PUBLIC void intrusive_ptr_release(ActivityImpl* activity);
+ int get_refcount() { return refcount_; } // For debugging purpose
static xbt::signal<void(ActivityImpl const&)> on_suspended;
static xbt::signal<void(ActivityImpl const&)> on_resumed;
smx_simcall_t simcall = &proc.simcall_;
const auto* observer = dynamic_cast<kernel::actor::ConditionWaitSimcall*>(simcall->observer_);
xbt_assert(observer != nullptr);
- observer->get_mutex()->lock(simcall->issuer_);
+ observer->get_mutex()->lock_async(simcall->issuer_)->wait_for(simcall->issuer_, -1);
}
XBT_OUT();
}
mutex->unlock(issuer);
}
- RawImplPtr synchro(new SynchroImpl([this, issuer]() {
+ SynchroImplPtr synchro(new SynchroImpl([this, issuer]() {
this->remove_sleeping_actor(*issuer);
auto* observer = dynamic_cast<kernel::actor::ConditionWaitSimcall*>(issuer->simcall_.observer_);
xbt_assert(observer != nullptr);
namespace kernel {
namespace activity {
-void MutexImpl::lock(actor::ActorImpl* issuer)
+bool MutexAcquisitionImpl::test(actor::ActorImpl*)
{
- XBT_IN("(%p; %p)", this, issuer);
- MC_CHECK_NO_DPOR();
+ return mutex_->owner_ == issuer_;
+}
+void MutexAcquisitionImpl::wait_for(actor::ActorImpl* issuer, double timeout)
+{
+ xbt_assert(mutex_->locked_); // it was locked either by someone else or by me during the lock_async
+ xbt_assert(
+ issuer == issuer_,
+ "Actors can only wait acquisitions that they created themselves while this one was created by actor id %ld.",
+ issuer_->get_pid());
+ xbt_assert(timeout < 0, "Timeouts on mutex acquisitions are not implemented yet.");
+
+ this->register_simcall(&issuer_->simcall_); // Block on that acquisition
+
+ if (mutex_->get_owner() == issuer_) { // I'm the owner
+ finish();
+ } else {
+ // Already in the queue
+ }
+}
+void MutexAcquisitionImpl::finish()
+{
+ xbt_assert(simcalls_.size() == 1, "Unexpected number of simcalls waiting: %zu", simcalls_.size());
+ smx_simcall_t simcall = simcalls_.front();
+ simcalls_.pop_front();
+
+ simcall->issuer_->waiting_synchro_ = nullptr;
+ simcall->issuer_->simcall_answer();
+}
+
+MutexAcquisitionImplPtr MutexImpl::lock_async(actor::ActorImpl* issuer)
+{
+ auto res = MutexAcquisitionImplPtr(new kernel::activity::MutexAcquisitionImpl(issuer, this), true);
if (locked_) {
/* FIXME: check if the host is active ? */
/* Somebody using the mutex, use a synchronization to get host failures */
- RawImplPtr synchro(new SynchroImpl([this, issuer]() { this->remove_sleeping_actor(*issuer); }));
- (*synchro).set_host(issuer->get_host()).start();
- synchro->register_simcall(&issuer->simcall_);
- sleeping_.push_back(*issuer);
+ sleeping_.push_back(res);
} else {
- /* mutex free */
locked_ = true;
owner_ = issuer;
- issuer->simcall_answer();
}
- XBT_OUT();
+ return res;
}
/** Tries to lock the mutex for a actor
if (not sleeping_.empty()) {
/* Give the ownership to the first waiting actor */
- owner_ = &sleeping_.front();
+ auto acq = sleeping_.front();
+ owner_ = acq->get_issuer();
+
+ if (acq == owner_->waiting_synchro_)
+ acq->finish();
+
sleeping_.pop_front();
- owner_->waiting_synchro_ = nullptr;
- owner_->simcall_answer();
} else {
/* nobody to wake up */
locked_ = false;
#define SIMGRID_KERNEL_ACTIVITY_MUTEX_HPP
#include "simgrid/s4u/Mutex.hpp"
+#include "src/kernel/activity/ActivityImpl.hpp"
#include "src/kernel/actor/ActorImpl.hpp"
#include <boost/intrusive/list.hpp>
namespace kernel {
namespace activity {
+/** Mutex Acquisition: the act / process of acquiring the mutex.
+ *
+ * You can declare some interest on a mutex without being blocked waiting if it's already occupied.
+ * If it gets freed by its current owned, you become the new owner, even if you're still not blocked on it.
+ * Nobody can lock it behind your back or overpass you in the queue in any way, even if you're still not blocked on it.
+ *
+ * Afterward, when you do consider the lock, the test() or wait() operations are both non-blocking since you're the
+ * owner. People who declared interest in the mutex after you get stuck in the queue behind you.
+ *
+ *
+ * Splitting the locking process this way is interesting for symmetry with the other activities such as exec or
+ * communication that do have an async variant and could be mildly interesting to the users once exposed in S4U, but
+ * that's not the only reason. It's also very important to the MC world: the Mutex::lock_async() is always enabled
+ * (nothing can prevent you from adding yourself to the queue of potential owners) while Acquisition::wait() is
+ * persistent: it's not always enabled but once it gets enabled (because you're the owner), it remains enabled for ever.
+ *
+ * Mutex::lock() is not persistent: sometimes it's enabled if the mutex is free, and then it gets disabled if
+ * someone else locks the mutex, and then it becomes enabled again once the mutex is freed. This is why Mutex::lock()
+ * is not used in our MC computational model: we ban non-persistent transitions because they would make some
+ * computations much more complex.
+ *
+ * In particular, computing the extension of an unfolding's configuration is polynomial when you only have persistent
+ * transitions while it's O(2^n) when some of the transitions are non-persistent (you have to consider again all subsets
+ * of a set if some transitions may become disabled in between, while you don't have to reconsider them if you can reuse
+ * your previous computations).
+ */
+class XBT_PUBLIC MutexAcquisitionImpl
+ : public ActivityImpl_T<MutexAcquisitionImpl> { // Acquisition: n. The act or process of acquiring.
+ actor::ActorImpl* issuer_ = nullptr;
+ MutexImpl* mutex_ = nullptr;
+
+public:
+ MutexAcquisitionImpl(actor::ActorImpl* issuer, MutexImpl* mutex) : issuer_(issuer), mutex_(mutex) {}
+ MutexImplPtr get_mutex() { return mutex_; }
+ actor::ActorImpl* get_issuer() { return issuer_; }
+
+ bool test(actor::ActorImpl* issuer = nullptr) override;
+ void wait_for(actor::ActorImpl* issuer, double timeout) override;
+ void post() override
+ { /*no surf action*/
+ }
+ void finish() override;
+ void set_exception(actor::ActorImpl* issuer) override
+ { /* nothing to do */
+ }
+};
+
class XBT_PUBLIC MutexImpl {
std::atomic_int_fast32_t refcount_{1};
s4u::Mutex piface_;
bool locked_ = false;
actor::ActorImpl* owner_ = nullptr;
// List of sleeping actors:
- actor::SynchroList sleeping_;
+ std::deque<MutexAcquisitionImplPtr> sleeping_;
+
+ friend MutexAcquisitionImpl;
public:
MutexImpl() : piface_(this) {}
MutexImpl(MutexImpl const&) = delete;
MutexImpl& operator=(MutexImpl const&) = delete;
- void lock(actor::ActorImpl* issuer);
+ MutexAcquisitionImplPtr lock_async(actor::ActorImpl* issuer);
bool try_lock(actor::ActorImpl* issuer);
void unlock(actor::ActorImpl* issuer);
bool is_locked() const { return locked_; }
MutexImpl* ref();
void unref();
- void remove_sleeping_actor(actor::ActorImpl& actor) { xbt::intrusive_erase(sleeping_, actor); }
actor::ActorImpl* get_owner() const { return owner_; }
// boost::intrusive_ptr<Mutex> support:
xbt_assert(std::isfinite(timeout), "timeout is not finite!");
if (value_ <= 0) {
- RawImplPtr synchro(new SynchroImpl([this, issuer]() {
+ SynchroImplPtr synchro(new SynchroImpl([this, issuer]() {
this->remove_sleeping_actor(*issuer);
auto* observer = dynamic_cast<kernel::actor::SemAcquireSimcall*>(issuer->simcall_.observer_);
xbt_assert(observer != nullptr);
#include "simgrid/s4u/Host.hpp"
#include "src/kernel/activity/CommImpl.hpp"
#include "src/kernel/activity/MailboxImpl.hpp"
-#include "src/kernel/activity/MutexImpl.hpp"
#include "src/kernel/actor/ActorImpl.hpp"
#include "src/kernel/actor/SimcallObserver.hpp"
#include "src/mc/mc_config.hpp"
--- /dev/null
+/* Copyright (c) 2019-2022. The SimGrid Team. All rights reserved. */
+
+/* This program is free software; you can redistribute it and/or modify it
+ * under the terms of the license (GNU LGPL) which comes with this package. */
+
+#include "src/kernel/actor/MutexObserver.hpp"
+#include "simgrid/s4u/Host.hpp"
+#include "src/kernel/activity/MutexImpl.hpp"
+#include "src/kernel/actor/ActorImpl.hpp"
+#include "src/mc/mc_config.hpp"
+
+#include <sstream>
+
+XBT_LOG_NEW_DEFAULT_SUBCATEGORY(obs_mutex, mc_observer, "Logging specific to mutex simcalls observation");
+
+namespace simgrid {
+namespace kernel {
+namespace actor {
+
+#if 0
+bool MutexSimcall::depends(SimcallObserver* other)
+{
+ if (dynamic_cast<RandomSimcall*>(other) != nullptr)
+ return other->depends(this); /* Other is random, that is very permissive. Use that relation instead. */
+
+#if 0 /* This code is currently broken and shouldn't be used. We must implement asynchronous locks before */
+ MutexSimcall* that = dynamic_cast<MutexSimcall*>(other);
+ if (that == nullptr)
+ return true; // Depends on anything we don't know
+
+ /* Theorem 4.4.7: Any pair of synchronization actions of distinct actors concerning distinct mutexes are independent */
+ if (this->get_issuer() != that->get_issuer() && this->get_mutex() != that->get_mutex())
+ return false;
+
+ /* Theorem 4.4.8 An AsyncMutexLock is independent with a MutexUnlock of another actor */
+ if (((dynamic_cast<MutexLockSimcall*>(this) != nullptr && dynamic_cast<MutexUnlockSimcall*>(that)) ||
+ (dynamic_cast<MutexLockSimcall*>(that) != nullptr && dynamic_cast<MutexUnlockSimcall*>(this))) &&
+ get_issuer() != other->get_issuer())
+ return false;
+#endif
+ return true; // Depend on things we don't know for sure that they are independent
+}
+#endif
+
+MutexObserver::MutexObserver(ActorImpl* actor, activity::MutexImpl* mutex) : SimcallObserver(actor), mutex_(mutex) {}
+MutexTestObserver::MutexTestObserver(ActorImpl* actor, activity::MutexImpl* mutex) : MutexObserver(actor, mutex) {}
+
+MutexLockAsyncObserver::MutexLockAsyncObserver(ActorImpl* actor, activity::MutexImpl* mutex)
+ : MutexObserver(actor, mutex)
+{
+}
+MutexLockWaitObserver::MutexLockWaitObserver(ActorImpl* actor, activity::MutexAcquisitionImplPtr synchro)
+ : MutexObserver(actor, synchro->get_mutex().get()), synchro_(synchro)
+{
+}
+
+bool MutexLockWaitObserver::is_enabled()
+{
+ return synchro_->test();
+}
+
+} // namespace actor
+} // namespace kernel
+} // namespace simgrid
--- /dev/null
+/* Copyright (c) 2019-2022. The SimGrid Team. All rights reserved. */
+
+/* This program is free software; you can redistribute it and/or modify it
+ * under the terms of the license (GNU LGPL) which comes with this package. */
+
+#ifndef SIMGRID_MC_MUTEX_OBSERVER_HPP
+#define SIMGRID_MC_MUTEX_OBSERVER_HPP
+
+#include "simgrid/forward.h"
+#include "src/kernel/activity/MutexImpl.hpp"
+#include "src/kernel/actor/ActorImpl.hpp"
+#include "src/kernel/actor/SimcallObserver.hpp"
+
+#include <string>
+
+namespace simgrid {
+namespace kernel {
+namespace actor {
+
+/* abstract */
+class MutexObserver : public SimcallObserver {
+ activity::MutexImpl* const mutex_;
+
+public:
+ MutexObserver(ActorImpl* actor, activity::MutexImpl* mutex);
+ virtual ~MutexObserver() = default;
+ activity::MutexImpl* get_mutex() const { return mutex_; }
+};
+
+class MutexTestObserver : public MutexObserver {
+public:
+ MutexTestObserver(ActorImpl* actor, activity::MutexImpl* mutex);
+};
+
+class MutexLockAsyncObserver : public MutexObserver {
+public:
+ MutexLockAsyncObserver(ActorImpl* actor, activity::MutexImpl* mutex);
+};
+
+class MutexLockWaitObserver : public MutexObserver {
+ activity::MutexAcquisitionImplPtr synchro_;
+
+public:
+ MutexLockWaitObserver(ActorImpl* actor, activity::MutexAcquisitionImplPtr synchro);
+ bool is_enabled() override;
+};
+
+class MutexUnlockObserver : public MutexObserver {
+ using MutexObserver::MutexObserver;
+};
+
+} // namespace actor
+} // namespace kernel
+} // namespace simgrid
+
+#endif
#include "simgrid/s4u/Host.hpp"
#include "src/kernel/activity/CommImpl.hpp"
#include "src/kernel/activity/MailboxImpl.hpp"
-#include "src/kernel/activity/MutexImpl.hpp"
#include "src/kernel/actor/ActorImpl.hpp"
#include "src/mc/mc_config.hpp"
stream << min_ << ' ' << max_;
}
-bool MutexSimcall::depends(SimcallObserver* other)
-{
- if (dynamic_cast<RandomSimcall*>(other) != nullptr)
- return other->depends(this); /* Other is random, that is very permissive. Use that relation instead. */
-
-#if 0 /* This code is currently broken and shouldn't be used. We must implement asynchronous locks before */
- MutexSimcall* that = dynamic_cast<MutexSimcall*>(other);
- if (that == nullptr)
- return true; // Depends on anything we don't know
-
- /* Theorem 4.4.7: Any pair of synchronization actions of distinct actors concerning distinct mutexes are independent */
- if (this->get_issuer() != that->get_issuer() && this->get_mutex() != that->get_mutex())
- return false;
-
- /* Theorem 4.4.8 An AsyncMutexLock is independent with a MutexUnlock of another actor */
- if (((dynamic_cast<MutexLockSimcall*>(this) != nullptr && dynamic_cast<MutexUnlockSimcall*>(that)) ||
- (dynamic_cast<MutexLockSimcall*>(that) != nullptr && dynamic_cast<MutexUnlockSimcall*>(this))) &&
- get_issuer() != other->get_issuer())
- return false;
-#endif
- return true; // Depend on things we don't know for sure that they are independent
-}
-
void RandomSimcall::prepare(int times_considered)
{
next_value_ = min_ + times_considered;
return max_ - min_ + 1;
}
-/*
-std::string MutexLockSimcall::to_string(int times_considered) const
-{
- auto mutex = get_mutex();
- std::string res = SimcallObserver::to_string(times_considered) + (blocking_ ? "Mutex LOCK" : "Mutex TRYLOCK");
- res += "(locked = " + std::to_string(mutex->is_locked());
- res += ", owner = " + std::to_string(mutex->get_owner() ? mutex->get_owner()->get_pid() : -1);
- res += ", sleeping = n/a)";
- return res;
-}*/
-
-bool MutexLockSimcall::is_enabled()
-{
- return not blocking_ || get_mutex()->get_owner() == nullptr || get_mutex()->get_owner() == get_issuer();
-}
-
bool ConditionWaitSimcall::is_enabled()
{
static bool warned = false;
bool depends(SimcallObserver* other) override;
};
-class MutexSimcall : public SimcallObserver {
- activity::MutexImpl* const mutex_;
-
-public:
- MutexSimcall(ActorImpl* actor, activity::MutexImpl* mutex) : SimcallObserver(actor), mutex_(mutex) {}
- activity::MutexImpl* get_mutex() const { return mutex_; }
- bool depends(SimcallObserver* other) override;
-};
-
-class MutexUnlockSimcall : public MutexSimcall {
- using MutexSimcall::MutexSimcall;
-};
-
-class MutexLockSimcall : public MutexSimcall {
- const bool blocking_;
-
-public:
- MutexLockSimcall(ActorImpl* actor, activity::MutexImpl* mutex, bool blocking = true)
- : MutexSimcall(actor, mutex), blocking_(blocking)
- {
- }
- bool is_enabled() override;
-};
-
class ConditionWaitSimcall : public ResultingSimcall<bool> {
activity::ConditionVariableImpl* const cond_;
activity::MutexImpl* const mutex_;
/* This program is free software; you can redistribute it and/or modify it
* under the terms of the license (GNU LGPL) which comes with this package. */
+#include <simgrid/modelchecker.h>
#include <simgrid/mutex.h>
#include <simgrid/s4u/Mutex.hpp>
#include <src/kernel/activity/MutexImpl.hpp>
-#include <src/kernel/actor/SimcallObserver.hpp>
+#include <src/kernel/actor/MutexObserver.hpp>
+#include <src/mc/mc_replay.hpp>
namespace simgrid {
namespace s4u {
void Mutex::lock()
{
kernel::actor::ActorImpl* issuer = kernel::actor::ActorImpl::self();
- kernel::actor::MutexLockSimcall observer{issuer, pimpl_};
- kernel::actor::simcall_blocking([&observer] { observer.get_mutex()->lock(observer.get_issuer()); }, &observer);
+
+ if (MC_is_active() || MC_record_replay_is_active()) { // Split in 2 simcalls for transition persistency
+ kernel::actor::MutexLockAsyncObserver lock_observer{issuer, pimpl_};
+ auto acquisition = kernel::actor::simcall([issuer, this] { return pimpl_->lock_async(issuer); }, &lock_observer);
+
+ kernel::actor::MutexLockWaitObserver wait_observer{issuer, acquisition};
+ kernel::actor::simcall_blocking([issuer, acquisition] { return acquisition->wait_for(issuer, -1); },
+ &wait_observer);
+
+ } else { // Do it in one simcall only
+ kernel::actor::simcall_blocking([issuer, this] { pimpl_->lock_async(issuer)->wait_for(issuer, -1); });
+ }
}
/** @brief Release the ownership of the mutex, unleashing a blocked actor (if any)
void Mutex::unlock()
{
kernel::actor::ActorImpl* issuer = kernel::actor::ActorImpl::self();
- kernel::actor::MutexUnlockSimcall observer{issuer, pimpl_};
+ kernel::actor::MutexUnlockObserver observer{issuer, pimpl_};
kernel::actor::simcall([this, issuer] { this->pimpl_->unlock(issuer); }, &observer);
}
bool Mutex::try_lock()
{
kernel::actor::ActorImpl* issuer = kernel::actor::ActorImpl::self();
- kernel::actor::MutexLockSimcall observer{issuer, pimpl_, false};
+ kernel::actor::MutexTestObserver observer{issuer, pimpl_};
return kernel::actor::simcall([&observer] { return observer.get_mutex()->try_lock(observer.get_issuer()); },
&observer);
}
src/kernel/actor/ActorImpl.hpp
src/kernel/actor/CommObserver.cpp
src/kernel/actor/CommObserver.hpp
+ src/kernel/actor/MutexObserver.cpp
+ src/kernel/actor/MutexObserver.hpp
src/kernel/actor/SimcallObserver.cpp
src/kernel/actor/SimcallObserver.hpp