lock SignalVector with shared_lock to allow reading on other threads

This commit is contained in:
fourtf 2019-07-31 22:29:07 +02:00
parent 4e4c7d4c0b
commit fff979b3c0
12 changed files with 120 additions and 35 deletions

View file

@ -163,7 +163,7 @@ void CompletionModel::refresh(const QString &prefix)
} }
// Commands // Commands
for (auto &command : getApp()->commands->items_.getVector()) for (auto &command : getApp()->commands->items_)
{ {
addString(command.name, TaggedString::Command); addString(command.name, TaggedString::Command);
} }

View file

@ -4,6 +4,7 @@
#include <QTimer> #include <QTimer>
#include <boost/noncopyable.hpp> #include <boost/noncopyable.hpp>
#include <pajlada/signals/signal.hpp> #include <pajlada/signals/signal.hpp>
#include <shared_mutex>
#include <vector> #include <vector>
#include "debug/AssertInGuiThread.hpp" #include "debug/AssertInGuiThread.hpp"
@ -20,7 +21,57 @@ struct SignalVectorItemArgs {
template <typename TVectorItem> template <typename TVectorItem>
class ReadOnlySignalVector : boost::noncopyable class ReadOnlySignalVector : boost::noncopyable
{ {
using VecIt = typename std::vector<TVectorItem>::iterator;
public: public:
struct Iterator
: public std::iterator<std::input_iterator_tag, TVectorItem> {
Iterator(VecIt &&it, std::shared_mutex &mutex)
: it_(std::move(it))
, lock_(mutex)
, mutex_(mutex)
{
}
Iterator(const Iterator &other)
: it_(other.it_)
, lock_(other.mutex_)
, mutex_(other.mutex_)
{
}
TVectorItem &operator*()
{
return it_.operator*();
}
Iterator &operator++()
{
++this->it_;
return *this;
}
bool operator==(const Iterator &other)
{
return this->it_ == other.it_;
}
bool operator!=(const Iterator &other)
{
return this->it_ != other.it_;
}
auto operator-(const Iterator &other)
{
return this->it_ - other.it_;
}
private:
VecIt it_;
std::shared_lock<std::shared_mutex> lock_;
std::shared_mutex &mutex_;
};
ReadOnlySignalVector() ReadOnlySignalVector()
{ {
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout, QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
@ -34,6 +85,27 @@ public:
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved; pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
pajlada::Signals::NoArgSignal delayedItemsChanged; pajlada::Signals::NoArgSignal delayedItemsChanged;
Iterator begin() const
{
return Iterator(
const_cast<std::vector<TVectorItem> &>(this->vector_).begin(),
this->mutex_);
}
Iterator end() const
{
return Iterator(
const_cast<std::vector<TVectorItem> &>(this->vector_).end(),
this->mutex_);
}
bool empty() const
{
std::shared_lock lock(this->mutex_);
return this->vector_.empty();
}
const std::vector<TVectorItem> &getVector() const const std::vector<TVectorItem> &getVector() const
{ {
assertInGuiThread(); assertInGuiThread();
@ -56,6 +128,7 @@ public:
protected: protected:
std::vector<TVectorItem> vector_; std::vector<TVectorItem> vector_;
QTimer itemsChangedTimer_; QTimer itemsChangedTimer_;
mutable std::shared_mutex mutex_;
}; };
template <typename TVectorItem> template <typename TVectorItem>
@ -69,10 +142,15 @@ public:
void removeItem(int index, void *caller = nullptr) void removeItem(int index, void *caller = nullptr)
{ {
assertInGuiThread(); assertInGuiThread();
assert(index >= 0 && index < this->vector_.size()); std::unique_lock lock(this->mutex_);
assert(index >= 0 && index < int(this->vector_.size()));
TVectorItem item = this->vector_[index]; TVectorItem item = this->vector_[index];
this->vector_.erase(this->vector_.begin() + index); this->vector_.erase(this->vector_.begin() + index);
lock.unlock(); // manual unlock
SignalVectorItemArgs<TVectorItem> args{item, index, caller}; SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemRemoved.invoke(args); this->itemRemoved.invoke(args);
@ -93,16 +171,20 @@ public:
void *caller = nullptr) override void *caller = nullptr) override
{ {
assertInGuiThread(); assertInGuiThread();
if (index == -1)
{
index = this->vector_.size();
}
else
{
assert(index >= 0 && index <= this->vector_.size());
}
this->vector_.insert(this->vector_.begin() + index, item); {
std::unique_lock lock(this->mutex_);
if (index == -1)
{
index = this->vector_.size();
}
else
{
assert(index >= 0 && index <= this->vector_.size());
}
this->vector_.insert(this->vector_.begin() + index, item);
}
SignalVectorItemArgs<TVectorItem> args{item, index, caller}; SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args); this->itemInserted.invoke(args);
@ -124,11 +206,16 @@ public:
void *caller = nullptr) override void *caller = nullptr) override
{ {
assertInGuiThread(); assertInGuiThread();
int index = -1;
auto it = std::lower_bound(this->vector_.begin(), this->vector_.end(), {
item, Compare{}); std::unique_lock lock(this->mutex_);
int index = it - this->vector_.begin();
this->vector_.insert(it, item); auto it = std::lower_bound(this->vector_.begin(),
this->vector_.end(), item, Compare{});
index = it - this->vector_.begin();
this->vector_.insert(it, item);
}
SignalVectorItemArgs<TVectorItem> args{item, index, caller}; SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args); this->itemInserted.invoke(args);

View file

@ -16,7 +16,7 @@ AccountController::AccountController()
this->twitch.accounts.itemRemoved.connect([this](const auto &args) { this->twitch.accounts.itemRemoved.connect([this](const auto &args) {
if (args.caller != this) if (args.caller != this)
{ {
auto &accs = this->twitch.accounts.getVector(); auto &accs = this->twitch.accounts;
auto it = std::find(accs.begin(), accs.end(), args.item); auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end()); assert(it != accs.end());
@ -29,7 +29,7 @@ AccountController::AccountController()
{ {
case ProviderId::Twitch: case ProviderId::Twitch:
{ {
auto &accs = this->twitch.accounts.getVector(); auto &accs = this->twitch.accounts;
auto it = std::find(accs.begin(), accs.end(), args.item); auto it = std::find(accs.begin(), accs.end(), args.item);
assert(it != accs.end()); assert(it != accs.end());

View file

@ -174,7 +174,7 @@ void CommandController::initialize(Settings &, Paths &paths)
auto addFirstMatchToMap = [this](auto args) { auto addFirstMatchToMap = [this](auto args) {
this->commandsMap_.remove(args.item.name); this->commandsMap_.remove(args.item.name);
for (const Command &cmd : this->items_.getVector()) for (const Command &cmd : this->items_)
{ {
if (cmd.name == args.item.name) if (cmd.name == args.item.name)
{ {
@ -185,7 +185,7 @@ void CommandController::initialize(Settings &, Paths &paths)
int maxSpaces = 0; int maxSpaces = 0;
for (const Command &cmd : this->items_.getVector()) for (const Command &cmd : this->items_)
{ {
auto localMaxSpaces = cmd.name.count(' '); auto localMaxSpaces = cmd.name.count(' ');
if (localMaxSpaces > maxSpaces) if (localMaxSpaces > maxSpaces)

View file

@ -64,7 +64,7 @@ UserHighlightModel *HighlightController::createUserModel(QObject *parent)
bool HighlightController::isHighlightedUser(const QString &username) bool HighlightController::isHighlightedUser(const QString &username)
{ {
const auto &userItems = this->highlightedUsers.getVector(); const auto &userItems = this->highlightedUsers;
for (const auto &highlightedUser : userItems) for (const auto &highlightedUser : userItems)
{ {
if (highlightedUser.isMatch(username)) if (highlightedUser.isMatch(username))
@ -87,9 +87,7 @@ HighlightBlacklistModel *HighlightController::createBlacklistModel(
bool HighlightController::blacklistContains(const QString &username) bool HighlightController::blacklistContains(const QString &username)
{ {
std::vector<HighlightBlacklistUser> blacklistItems = for (const auto &blacklistedUser : this->blacklistedUsers)
this->blacklistedUsers.getVector();
for (const auto &blacklistedUser : blacklistItems)
{ {
if (blacklistedUser.isMatch(username)) if (blacklistedUser.isMatch(username))
{ {

View file

@ -107,7 +107,7 @@ public:
if (!this->emotesChecked_) if (!this->emotesChecked_)
{ {
const auto &accvec = const auto &accvec =
getApp()->accounts->twitch.accounts.getVector(); getApp()->accounts->twitch.accounts;
for (const auto &acc : accvec) for (const auto &acc : accvec)
{ {
const auto &accemotes = *acc->accessEmotes(); const auto &accemotes = *acc->accessEmotes();

View file

@ -41,7 +41,7 @@ void NotificationController::initialize(Settings &settings, Paths &paths)
this->channelMap[Platform::Mixer].delayedItemsChanged.connect([this] { // this->channelMap[Platform::Mixer].delayedItemsChanged.connect([this] { //
this->mixerSetting_.setValue( this->mixerSetting_.setValue(
this->channelMap[Platform::Mixer].getVector()); this->channelMap[Platform::Mixer]);
});*/ });*/
liveStatusTimer_ = new QTimer(); liveStatusTimer_ = new QTimer();
@ -69,7 +69,7 @@ void NotificationController::updateChannelNotification(
bool NotificationController::isChannelNotified(const QString &channelName, bool NotificationController::isChannelNotified(const QString &channelName,
Platform p) Platform p)
{ {
for (const auto &channel : this->channelMap[p].getVector()) for (const auto &channel : this->channelMap[p])
{ {
if (channelName.toLower() == channel.toLower()) if (channelName.toLower() == channel.toLower())
{ {

View file

@ -25,7 +25,7 @@ PingModel *PingController::createModel(QObject *parent)
bool PingController::isMuted(const QString &channelName) bool PingController::isMuted(const QString &channelName)
{ {
for (const auto &channel : this->channelVector.getVector()) for (const auto &channel : this->channelVector)
{ {
if (channelName.toLower() == channel.toLower()) if (channelName.toLower() == channel.toLower())
{ {

View file

@ -348,7 +348,7 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
int(container.getScale() * 16)); int(container.getScale() * 16));
for (const auto &action : for (const auto &action :
getApp()->moderationActions->items.getVector()) getApp()->moderationActions->items)
{ {
if (auto image = action.getImage()) if (auto image = action.getImage())
{ {

View file

@ -35,7 +35,7 @@ std::vector<QString> TwitchAccountManager::getUsernames() const
std::lock_guard<std::mutex> lock(this->mutex_); std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts.getVector()) for (const auto &user : this->accounts)
{ {
userNames.push_back(user->getUserName()); userNames.push_back(user->getUserName());
} }
@ -48,7 +48,7 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
{ {
std::lock_guard<std::mutex> lock(this->mutex_); std::lock_guard<std::mutex> lock(this->mutex_);
for (const auto &user : this->accounts.getVector()) for (const auto &user : this->accounts)
{ {
if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0) if (username.compare(user->getUserName(), Qt::CaseInsensitive) == 0)
{ {

View file

@ -60,7 +60,7 @@ bool TwitchMessageBuilder::isIgnored() const
auto app = getApp(); auto app = getApp();
// TODO(pajlada): Do we need to check if the phrase is valid first? // TODO(pajlada): Do we need to check if the phrase is valid first?
for (const auto &phrase : app->ignores->phrases.getVector()) for (const auto &phrase : app->ignores->phrases)
{ {
if (phrase.isBlock() && phrase.isMatch(this->originalMessage_)) if (phrase.isBlock() && phrase.isMatch(this->originalMessage_))
{ {
@ -221,7 +221,7 @@ MessagePtr TwitchMessageBuilder::build()
} }
} }
auto app = getApp(); auto app = getApp();
const auto &phrases = app->ignores->phrases.getVector(); const auto &phrases = app->ignores->phrases;
auto removeEmotesInRange = auto removeEmotesInRange =
[](int pos, int len, [](int pos, int len,
std::vector<std::tuple<int, EmotePtr, EmoteName>> std::vector<std::tuple<int, EmotePtr, EmoteName>>

View file

@ -177,7 +177,7 @@ void SplitHeader::initializeLayout()
{ {
case Qt::LeftButton: case Qt::LeftButton:
if (getApp() if (getApp()
->moderationActions->items.getVector() ->moderationActions->items
.empty()) .empty())
{ {
getApp()->windows->showSettingsDialog( getApp()->windows->showSettingsDialog(
@ -224,7 +224,7 @@ void SplitHeader::initializeLayout()
// update moderation button when items changed // update moderation button when items changed
this->managedConnect( this->managedConnect(
getApp()->moderationActions->items.delayedItemsChanged, [this] { getApp()->moderationActions->items.delayedItemsChanged, [this] {
if (getApp()->moderationActions->items.getVector().empty()) if (getApp()->moderationActions->items.empty())
{ {
if (this->split_->getModerationMode()) if (this->split_->getModerationMode())
this->split_->setModerationMode(true); this->split_->setModerationMode(true);
@ -509,7 +509,7 @@ void SplitHeader::updateModerationModeIcon()
{ {
auto moderationMode = auto moderationMode =
this->split_->getModerationMode() && this->split_->getModerationMode() &&
!getApp()->moderationActions->items.getVector().empty(); !getApp()->moderationActions->items.empty();
this->moderationButton_->setPixmap( this->moderationButton_->setPixmap(
moderationMode ? getApp()->resources->buttons.modModeEnabled moderationMode ? getApp()->resources->buttons.modModeEnabled