mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-21 22:24:07 +01:00
Make AccessGuard use a shared_mutex instead (#2702)
This allows `accessConst` to use a shared lock instead of a unique lock
This commit is contained in:
parent
c413a0984e
commit
115d198434
9 changed files with 109 additions and 33 deletions
|
@ -11,7 +11,7 @@ ChannelChatters::ChannelChatters(Channel &channel)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const UsernameSet> ChannelChatters::accessChatters() const
|
SharedAccessGuard<const UsernameSet> ChannelChatters::accessChatters() const
|
||||||
{
|
{
|
||||||
return this->chatters_.accessConst();
|
return this->chatters_.accessConst();
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ public:
|
||||||
ChannelChatters(Channel &channel);
|
ChannelChatters(Channel &channel);
|
||||||
virtual ~ChannelChatters() = default; // add vtable
|
virtual ~ChannelChatters() = default; // add vtable
|
||||||
|
|
||||||
AccessGuard<const UsernameSet> accessChatters() const;
|
SharedAccessGuard<const UsernameSet> accessChatters() const;
|
||||||
|
|
||||||
void addRecentChatter(const QString &user);
|
void addRecentChatter(const QString &user);
|
||||||
void addJoinedUser(const QString &user);
|
void addJoinedUser(const QString &user);
|
||||||
|
|
|
@ -1,39 +1,33 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T, typename LockType = std::unique_lock<std::shared_mutex>>
|
||||||
class AccessGuard
|
class AccessGuard
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AccessGuard(T &element, std::mutex &mutex)
|
AccessGuard(T &element, std::shared_mutex &mutex)
|
||||||
: element_(&element)
|
: element_(&element)
|
||||||
, mutex_(&mutex)
|
, lock_(mutex)
|
||||||
{
|
{
|
||||||
this->mutex_->lock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard(AccessGuard<T> &&other)
|
AccessGuard(AccessGuard<T, LockType> &&other)
|
||||||
: element_(other.element_)
|
: element_(other.element_)
|
||||||
, mutex_(other.mutex_)
|
, lock_(std::move(other.lock_))
|
||||||
{
|
{
|
||||||
other.isValid_ = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<T> &operator=(AccessGuard<T> &&other)
|
AccessGuard<T, LockType> &operator=(AccessGuard<T, LockType> &&other)
|
||||||
{
|
{
|
||||||
other.isValid_ = false;
|
|
||||||
this->element_ = other.element_;
|
this->element_ = other.element_;
|
||||||
this->mutex_ = other.element_;
|
this->lock_ = std::move(other.lock_);
|
||||||
}
|
|
||||||
|
|
||||||
~AccessGuard()
|
return *this;
|
||||||
{
|
|
||||||
if (this->isValid_)
|
|
||||||
this->mutex_->unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
T *operator->() const
|
T *operator->() const
|
||||||
|
@ -48,10 +42,13 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T *element_{};
|
T *element_{};
|
||||||
std::mutex *mutex_{};
|
LockType lock_;
|
||||||
bool isValid_{true};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using SharedAccessGuard =
|
||||||
|
AccessGuard<const T, std::shared_lock<std::shared_mutex>>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class UniqueAccess
|
class UniqueAccess
|
||||||
{
|
{
|
||||||
|
@ -90,14 +87,14 @@ public:
|
||||||
|
|
||||||
template <typename X = T,
|
template <typename X = T,
|
||||||
typename = std::enable_if_t<!std::is_const<X>::value>>
|
typename = std::enable_if_t<!std::is_const<X>::value>>
|
||||||
AccessGuard<const X> accessConst() const
|
SharedAccessGuard<const X> accessConst() const
|
||||||
{
|
{
|
||||||
return AccessGuard<const T>(this->element_, this->mutex_);
|
return SharedAccessGuard<const T>(this->element_, this->mutex_);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable T element_;
|
mutable T element_;
|
||||||
mutable std::mutex mutex_;
|
mutable std::shared_mutex mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -176,12 +176,14 @@ void TwitchAccount::checkFollow(const QString targetUserID,
|
||||||
[] {});
|
[] {});
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const std::set<TwitchUser>> TwitchAccount::accessBlocks() const
|
SharedAccessGuard<const std::set<TwitchUser>> TwitchAccount::accessBlocks()
|
||||||
|
const
|
||||||
{
|
{
|
||||||
return this->ignores_.accessConst();
|
return this->ignores_.accessConst();
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const std::set<QString>> TwitchAccount::accessBlockedUserIds() const
|
SharedAccessGuard<const std::set<QString>> TwitchAccount::accessBlockedUserIds()
|
||||||
|
const
|
||||||
{
|
{
|
||||||
return this->ignoresUserIds_.accessConst();
|
return this->ignoresUserIds_.accessConst();
|
||||||
}
|
}
|
||||||
|
@ -345,7 +347,7 @@ void TwitchAccount::loadUserstateEmotes(QStringList emoteSetKeys)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const TwitchAccount::TwitchAccountEmoteData>
|
SharedAccessGuard<const TwitchAccount::TwitchAccountEmoteData>
|
||||||
TwitchAccount::accessEmotes() const
|
TwitchAccount::accessEmotes() const
|
||||||
{
|
{
|
||||||
return this->emotes_.accessConst();
|
return this->emotes_.accessConst();
|
||||||
|
|
|
@ -106,12 +106,12 @@ public:
|
||||||
void checkFollow(const QString targetUserID,
|
void checkFollow(const QString targetUserID,
|
||||||
std::function<void(FollowResult)> onFinished);
|
std::function<void(FollowResult)> onFinished);
|
||||||
|
|
||||||
AccessGuard<const std::set<QString>> accessBlockedUserIds() const;
|
SharedAccessGuard<const std::set<QString>> accessBlockedUserIds() const;
|
||||||
AccessGuard<const std::set<TwitchUser>> accessBlocks() const;
|
SharedAccessGuard<const std::set<TwitchUser>> accessBlocks() const;
|
||||||
|
|
||||||
void loadEmotes();
|
void loadEmotes();
|
||||||
void loadUserstateEmotes(QStringList emoteSetKeys);
|
void loadUserstateEmotes(QStringList emoteSetKeys);
|
||||||
AccessGuard<const TwitchAccountEmoteData> accessEmotes() const;
|
SharedAccessGuard<const TwitchAccountEmoteData> accessEmotes() const;
|
||||||
|
|
||||||
// Automod actions
|
// Automod actions
|
||||||
void autoModAllow(const QString msgID);
|
void autoModAllow(const QString msgID);
|
||||||
|
|
|
@ -454,8 +454,8 @@ void TwitchChannel::setRoomId(const QString &id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const TwitchChannel::RoomModes> TwitchChannel::accessRoomModes()
|
SharedAccessGuard<const TwitchChannel::RoomModes>
|
||||||
const
|
TwitchChannel::accessRoomModes() const
|
||||||
{
|
{
|
||||||
return this->roomModes_.accessConst();
|
return this->roomModes_.accessConst();
|
||||||
}
|
}
|
||||||
|
@ -472,7 +472,7 @@ bool TwitchChannel::isLive() const
|
||||||
return this->streamStatus_.access()->live;
|
return this->streamStatus_.access()->live;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccessGuard<const TwitchChannel::StreamStatus>
|
SharedAccessGuard<const TwitchChannel::StreamStatus>
|
||||||
TwitchChannel::accessStreamStatus() const
|
TwitchChannel::accessStreamStatus() const
|
||||||
{
|
{
|
||||||
return this->streamStatus_.accessConst();
|
return this->streamStatus_.accessConst();
|
||||||
|
|
|
@ -83,8 +83,8 @@ public:
|
||||||
int chatterCount();
|
int chatterCount();
|
||||||
virtual bool isLive() const override;
|
virtual bool isLive() const override;
|
||||||
QString roomId() const;
|
QString roomId() const;
|
||||||
AccessGuard<const RoomModes> accessRoomModes() const;
|
SharedAccessGuard<const RoomModes> accessRoomModes() const;
|
||||||
AccessGuard<const StreamStatus> accessStreamStatus() const;
|
SharedAccessGuard<const StreamStatus> accessStreamStatus() const;
|
||||||
|
|
||||||
// Emotes
|
// Emotes
|
||||||
const TwitchBadges &globalTwitchBadges() const;
|
const TwitchBadges &globalTwitchBadges() const;
|
||||||
|
|
|
@ -36,6 +36,7 @@ set(chatterino_SOURCES
|
||||||
|
|
||||||
set(test_SOURCES
|
set(test_SOURCES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/main.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/AccessGuard.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/NetworkCommon.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/NetworkCommon.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/NetworkRequest.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/NetworkRequest.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/UsernameSet.cpp
|
${CMAKE_CURRENT_LIST_DIR}/src/UsernameSet.cpp
|
||||||
|
|
76
tests/src/AccessGuard.cpp
Normal file
76
tests/src/AccessGuard.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#include "common/UniqueAccess.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <random>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
using namespace chatterino;
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
TEST(AccessGuardLocker, NonConcurrentUsage)
|
||||||
|
{
|
||||||
|
std::shared_mutex m;
|
||||||
|
int e = 0;
|
||||||
|
|
||||||
|
{
|
||||||
|
AccessGuard<int> guard(e, m);
|
||||||
|
*guard = 3;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(e, 3);
|
||||||
|
|
||||||
|
{
|
||||||
|
AccessGuard<int> guard(e, m);
|
||||||
|
*guard = 4;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(e, 4);
|
||||||
|
|
||||||
|
{
|
||||||
|
SharedAccessGuard<int> guard(e, m);
|
||||||
|
EXPECT_EQ(*guard, 4);
|
||||||
|
}
|
||||||
|
EXPECT_EQ(e, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(AccessGuardLocker, ConcurrentUsage)
|
||||||
|
{
|
||||||
|
// This test doesn't actually prove anything on normal use, rather it needs to be run with AddressSanitizer/ThreadSanitizer and not error out for this to give any confidence
|
||||||
|
std::shared_mutex m;
|
||||||
|
int e = 0;
|
||||||
|
|
||||||
|
auto startTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
auto w = [&e, &m] {
|
||||||
|
std::mt19937_64 eng{std::random_device{}()};
|
||||||
|
std::uniform_int_distribution<> dist{1, 4};
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds{dist(eng)});
|
||||||
|
if (rand() % 2 == 0)
|
||||||
|
{
|
||||||
|
AccessGuard<int> guard(e, m);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds{dist(eng)});
|
||||||
|
*guard += 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SharedAccessGuard<int> guard(e, m);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds{dist(eng)});
|
||||||
|
int hehe = *guard;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
|
for (int i = 0; i < 500; ++i)
|
||||||
|
{
|
||||||
|
threads.emplace_back(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &t : threads)
|
||||||
|
{
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue