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

Removing cosim::utility::shared_mutex #692

Merged
merged 4 commits into from
Apr 4, 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
2 changes: 1 addition & 1 deletion cmake/project-config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ include(CMakeFindDependencyMacro)
list(APPEND CMAKE_MODULE_PATH "${PACKAGE_PREFIX_DIR}/@LIBCOSIM_CMAKE_INSTALL_DIR@")

find_dependency(MS_GSL REQUIRED)
find_dependency(Boost REQUIRED COMPONENTS date_time fiber log)
find_dependency(Boost REQUIRED COMPONENTS date_time log)
set(FMILibrary_USE_SHARED_LIB @FMILibrary_USE_SHARED_LIB@)
find_dependency(FMILibrary REQUIRED)
find_dependency(LIBZIP REQUIRED)
Expand Down
6 changes: 0 additions & 6 deletions include/cosim/execution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,6 @@ class execution
/**
* Advance the co-simulation forward to the given logical time.
*
* This function returns immediately, and its actions will be performed
* asynchronously. As it is not possible to perform more than one
* asynchronous operation at a time per `execution` object, client code
* must verify that the operation completed before calling the function
* again (e.g. by calling `boost::fibers::future::get()` on the result).
*
* \param targetTime
* The logical time at which the co-simulation should pause (optional).
* If specified, this must always be greater than the value of
Expand Down
2 changes: 1 addition & 1 deletion include/cosim/file_cache.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class temporary_file_cache : public file_cache

/**
* A persistent file cache which can be safely accessed by multiple
* processes, threads and fibers concurrently.
* processes and threads concurrently.
*/
class persistent_file_cache : public file_cache
{
Expand Down
83 changes: 8 additions & 75 deletions src/cosim/utility/concurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,69 +22,6 @@ namespace cosim
namespace utility
{


// =============================================================================
// shared_mutex
// =============================================================================


void shared_mutex::lock()
{
std::unique_lock<std::mutex> lock(mutex_);
condition_.wait(lock, [&] { return sharedCount_ == 0; });

// Release the mutex from the unique_lock, so it doesn't get automatically
// unlocked when the function exits.
lock.release();
}


bool shared_mutex::try_lock()
{
std::unique_lock<std::mutex> lock(mutex_, std::try_to_lock);
if (!lock.owns_lock()) return false;
if (sharedCount_ > 0) return false;

// Release the mutex from the unique_lock, so it doesn't get automatically
// unlocked when the function exits.
lock.release();
return true;
}


void shared_mutex::unlock()
{
mutex_.unlock();
condition_.notify_one();
}


void shared_mutex::lock_shared()
{
std::lock_guard<std::mutex> lock(mutex_);
++sharedCount_;
}


bool shared_mutex::try_lock_shared()
{
std::unique_lock<std::mutex> lock(mutex_, std::try_to_lock);
if (!lock.owns_lock()) return false;
++sharedCount_;
return true;
}


void shared_mutex::unlock_shared()
{
std::unique_lock<std::mutex> lock(mutex_);
--sharedCount_;
if (sharedCount_ == 0) {
lock.unlock();
condition_.notify_one();
}
}

// =============================================================================
// file_lock
// =============================================================================
Expand All @@ -106,13 +43,9 @@ file_lock::file_lock(
void file_lock::lock()
{
// NOTE: The reason we can't use std::lock() here is that we must make
// sure that the mutex gets locked before the file. Otherwise, the
// code might block on the file lock, when the lock is in fact held by
// a different fiber in the same process. Trying to lock the mutex first
// gives us a chance to yield to the other fiber if the operation would
// otherwise block. An additional reason is that it is unspecified
// to which extent boost::interprocess::file_lock is thread safe.
std::unique_lock<shared_mutex> mutexLock(fileMutex_->mutex);
// sure that the mutex gets locked before the file, since
// boost::interprocess::file_lock isn't thread safe.
std::unique_lock<std::shared_mutex> mutexLock(fileMutex_->mutex);
std::unique_lock<boost_wrapper> fileLock(fileMutex_->file);
mutexLock_ = std::move(mutexLock);
fileLock_ = std::move(fileLock);
Expand All @@ -122,7 +55,7 @@ void file_lock::lock()
bool file_lock::try_lock()
{
// See note on locking order in lock() above.
std::unique_lock<shared_mutex> mutexLock(fileMutex_->mutex, std::try_to_lock);
std::unique_lock<std::shared_mutex> mutexLock(fileMutex_->mutex, std::try_to_lock);
if (!mutexLock.owns_lock()) return false;
std::unique_lock<boost_wrapper> fileLock(fileMutex_->file, std::try_to_lock);
if (!fileLock.owns_lock()) return false;
Expand All @@ -135,14 +68,14 @@ bool file_lock::try_lock()
void file_lock::unlock()
{
std::get<std::unique_lock<boost_wrapper>>(fileLock_).unlock();
std::get<std::unique_lock<shared_mutex>>(mutexLock_).unlock();
std::get<std::unique_lock<std::shared_mutex>>(mutexLock_).unlock();
}


void file_lock::lock_shared()
{
// See note on locking order in lock() above.
std::shared_lock<shared_mutex> mutexLock(fileMutex_->mutex);
std::shared_lock<std::shared_mutex> mutexLock(fileMutex_->mutex);
std::shared_lock<boost_wrapper> fileLock(fileMutex_->file);
mutexLock_ = std::move(mutexLock);
fileLock_ = std::move(fileLock);
Expand All @@ -152,7 +85,7 @@ void file_lock::lock_shared()
bool file_lock::try_lock_shared()
{
// See note on locking order in lock() above.
std::shared_lock<shared_mutex> mutexLock(fileMutex_->mutex, std::try_to_lock);
std::shared_lock<std::shared_mutex> mutexLock(fileMutex_->mutex, std::try_to_lock);
if (!mutexLock.owns_lock()) return false;
std::shared_lock<boost_wrapper> fileLock(fileMutex_->file, std::try_to_lock);
if (!fileLock.owns_lock()) return false;
Expand All @@ -165,7 +98,7 @@ bool file_lock::try_lock_shared()
void file_lock::unlock_shared()
{
std::get<std::shared_lock<boost_wrapper>>(fileLock_).unlock();
std::get<std::shared_lock<shared_mutex>>(mutexLock_).unlock();
std::get<std::shared_lock<std::shared_mutex>>(mutexLock_).unlock();
}


Expand Down
87 changes: 18 additions & 69 deletions src/cosim/utility/concurrency.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,51 +27,6 @@ namespace cosim
namespace utility
{

/**
* A shared mutex à la `std::shared_mutex`, but with support for fibers.
*
* This class works in the same way as `std::shared_mutex`, but as it is
* implemented in terms of Boost.Fiber primitives, "blocking" operations
* are really "yielding" operations.
*
* The class meets the
* [SharedMutex](https://en.cppreference.com/w/cpp/named_req/SharedMutex)
* requirements.
*/
class shared_mutex
{
public:
/// Locks the mutex, blocks if the mutex is not available.
void lock();

/// Tries to lock the mutex and returns immediately whether it succeeded.
bool try_lock();

/// Unlocks the mutex.
void unlock();

/**
* Locks the mutex for shared ownership, blocks if the mutex is not
* available.
*/
void lock_shared();

/**
* Tries to lock the mutex for shared ownership, returns immediately
* whether it succeeded.
*/
bool try_lock_shared();

/// Unlocks the mutex from shared ownership.
void unlock_shared();

private:
std::mutex mutex_;
std::condition_variable condition_;
int sharedCount_ = 0;
};


/// Whether and how a `file_lock` should acquire a lock on construction.
enum class file_lock_initial_state
{
Expand All @@ -91,24 +46,18 @@ enum class file_lock_initial_state
*
* This class provides interprocess synchronisation based on
* `boost::interprocess::file_lock`, augmenting it with support for
* inter-fiber and inter-thread synchronisation. This is achieved by
* combining the file lock with a lock on a global `shared_mutex` object
* associated with the file.
*
* Note that a single `file_lock` object may only be used by one fiber at a
* time. That is, if it is locked by one fiber, it must be unlocked by the
* same fiber. Other fibers may not attempt to call its locking or unlocking
* functions in the meantime.
* inter-thread synchronisation. This is achieved by combining the file lock
* with a lock on a global `std::shared_mutex` object associated with the
* file.
*
* Furthermore, once a fiber has locked a file, the same fiber may not attempt
* to use a different `file_lock` object to lock the same file, as this would
* cause a deadlock.
* Note that `file_lock` objects should not be shared among threads. The
* inter-thread synchronisation is handled internally via the global
* per-file mutex.
*
* Therefore, to synchronise between fibers (including those running in
* separate threads), it is recommended to create one and only one `file_lock`
* object associated with the same file in each fiber. If, for some reason,
* a `file_lock` *must* be transferred between fibers, do so only when it is
* in the unlocked state.
* Furthermore, once a thread has locked a file, the same thread may not
* attempt to use a different `file_lock` object to lock the same file, as
* this would cause a deadlock. (This is also because they would share the
* same global mutex.)
*
* The lock automatically gets unlocked on destruction.
*
Expand Down Expand Up @@ -148,7 +97,7 @@ class file_lock
* \pre
* This `file_lock` object is not already locked.
* The file is not locked by a different `file_lock` object in the
* same fiber.
* same thread.
*/
void lock();

Expand All @@ -159,15 +108,15 @@ class file_lock
* \pre
* This `file_lock` object is not already locked.
* The file is not locked by a different `file_lock` object in the
* same fiber.
* same thread.
*/
bool try_lock();

/**
* Unlocks the file.
*
* \pre
* This `file_lock` object has been locked in the current fiber.
* This `file_lock` object has been locked in the current thread.
*/
void unlock();

Expand All @@ -177,7 +126,7 @@ class file_lock
* \pre
* This `file_lock` object is not already locked.
* The file is not locked by a different `file_lock` object in the
* same fiber.
* same thread.
*/
void lock_shared();

Expand All @@ -188,7 +137,7 @@ class file_lock
* \pre
* This `file_lock` object is not already locked.
* The file is not locked by a different `file_lock` object in the
* same fiber.
* same thread.
*/
bool try_lock_shared();

Expand All @@ -197,7 +146,7 @@ class file_lock
*
* \pre
* This `file_lock` object has been locked for shared ownership in
* the current fiber.
* the current thread.
*/
void unlock_shared();

Expand Down Expand Up @@ -229,7 +178,7 @@ class file_lock
struct file_mutex
{
file_mutex(const cosim::filesystem::path& path);
shared_mutex mutex;
std::shared_mutex mutex;
boost_wrapper file;
};

Expand All @@ -241,7 +190,7 @@ class file_lock
std::shared_ptr<file_mutex> fileMutex_;

// The locks we hold on the mutex and the file lock.
std::variant<std::unique_lock<shared_mutex>, std::shared_lock<shared_mutex>> mutexLock_;
std::variant<std::unique_lock<std::shared_mutex>, std::shared_lock<std::shared_mutex>> mutexLock_;
std::variant<std::unique_lock<boost_wrapper>, std::shared_lock<boost_wrapper>> fileLock_;
};

Expand Down
13 changes: 0 additions & 13 deletions tests/utility_concurrency_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,6 @@ void test_locking(F1&& getMutex1, F2&& getMutex2, F3&& getMutex3)
mutex3.unlock();
}


BOOST_AUTO_TEST_CASE(shared_mutex)
{
cosim::utility::shared_mutex mutex1;
cosim::utility::shared_mutex mutex2;
cosim::utility::shared_mutex mutex3;
test_locking(
[&]() -> cosim::utility::shared_mutex& { return mutex1; },
[&]() -> cosim::utility::shared_mutex& { return mutex2; },
[&]() -> cosim::utility::shared_mutex& { return mutex3; });
}


BOOST_AUTO_TEST_CASE(file_lock)
{
const auto workDir = cosim::utility::temp_dir();
Expand Down
2 changes: 1 addition & 1 deletion version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.3
0.9.0