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
for (auto &command : getApp()->commands->items_.getVector())
for (auto &command : getApp()->commands->items_)
{
addString(command.name, TaggedString::Command);
}

View file

@ -4,6 +4,7 @@
#include <QTimer>
#include <boost/noncopyable.hpp>
#include <pajlada/signals/signal.hpp>
#include <shared_mutex>
#include <vector>
#include "debug/AssertInGuiThread.hpp"
@ -20,7 +21,57 @@ struct SignalVectorItemArgs {
template <typename TVectorItem>
class ReadOnlySignalVector : boost::noncopyable
{
using VecIt = typename std::vector<TVectorItem>::iterator;
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()
{
QObject::connect(&this->itemsChangedTimer_, &QTimer::timeout,
@ -34,6 +85,27 @@ public:
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
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
{
assertInGuiThread();
@ -56,6 +128,7 @@ public:
protected:
std::vector<TVectorItem> vector_;
QTimer itemsChangedTimer_;
mutable std::shared_mutex mutex_;
};
template <typename TVectorItem>
@ -69,10 +142,15 @@ public:
void removeItem(int index, void *caller = nullptr)
{
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];
this->vector_.erase(this->vector_.begin() + index);
lock.unlock(); // manual unlock
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemRemoved.invoke(args);
@ -93,6 +171,9 @@ public:
void *caller = nullptr) override
{
assertInGuiThread();
{
std::unique_lock lock(this->mutex_);
if (index == -1)
{
index = this->vector_.size();
@ -103,6 +184,7 @@ public:
}
this->vector_.insert(this->vector_.begin() + index, item);
}
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args);
@ -124,11 +206,16 @@ public:
void *caller = nullptr) override
{
assertInGuiThread();
int index = -1;
auto it = std::lower_bound(this->vector_.begin(), this->vector_.end(),
item, Compare{});
int index = it - this->vector_.begin();
{
std::unique_lock lock(this->mutex_);
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};
this->itemInserted.invoke(args);

View file

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

View file

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

View file

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

View file

@ -107,7 +107,7 @@ public:
if (!this->emotesChecked_)
{
const auto &accvec =
getApp()->accounts->twitch.accounts.getVector();
getApp()->accounts->twitch.accounts;
for (const auto &acc : accvec)
{
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->mixerSetting_.setValue(
this->channelMap[Platform::Mixer].getVector());
this->channelMap[Platform::Mixer]);
});*/
liveStatusTimer_ = new QTimer();
@ -69,7 +69,7 @@ void NotificationController::updateChannelNotification(
bool NotificationController::isChannelNotified(const QString &channelName,
Platform p)
{
for (const auto &channel : this->channelMap[p].getVector())
for (const auto &channel : this->channelMap[p])
{
if (channelName.toLower() == channel.toLower())
{

View file

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

View file

@ -348,7 +348,7 @@ void TwitchModerationElement::addToContainer(MessageLayoutContainer &container,
int(container.getScale() * 16));
for (const auto &action :
getApp()->moderationActions->items.getVector())
getApp()->moderationActions->items)
{
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_);
for (const auto &user : this->accounts.getVector())
for (const auto &user : this->accounts)
{
userNames.push_back(user->getUserName());
}
@ -48,7 +48,7 @@ std::shared_ptr<TwitchAccount> TwitchAccountManager::findUserByUsername(
{
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)
{

View file

@ -60,7 +60,7 @@ bool TwitchMessageBuilder::isIgnored() const
auto app = getApp();
// 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_))
{
@ -221,7 +221,7 @@ MessagePtr TwitchMessageBuilder::build()
}
}
auto app = getApp();
const auto &phrases = app->ignores->phrases.getVector();
const auto &phrases = app->ignores->phrases;
auto removeEmotesInRange =
[](int pos, int len,
std::vector<std::tuple<int, EmotePtr, EmoteName>>

View file

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