Merge lp:~marcustomlinson/unity-scopes-shell/lp-1567429 into lp:unity-scopes-shell

Proposed by Marcus Tomlinson
Status: Merged
Approved by: Paweł Stołowski
Approved revision: 315
Merged at revision: 313
Proposed branch: lp:~marcustomlinson/unity-scopes-shell/lp-1567429
Merge into: lp:unity-scopes-shell
Diff against target: 275 lines (+65/-16)
5 files modified
src/Unity/scope.cpp (+25/-6)
src/Unity/scope.h (+5/-0)
src/Unity/scopes.cpp (+2/-0)
src/Unity/settingsmodel.cpp (+31/-10)
src/Unity/settingsmodel.h (+2/-0)
To merge this branch: bzr merge lp:~marcustomlinson/unity-scopes-shell/lp-1567429
Reviewer Review Type Date Requested Status
Paweł Stołowski (community) Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
Review via email: mp+291480@code.launchpad.net

Commit message

Update child scopes in a background thread to greatly improve the performance of Scope::settings()

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

Looks good, I think it will improve the perceived performance of Settings page quite a bit already. I yet need to check the silo and how it impacts search.

I think there is one other potential improvement we could make though: make the call to m_scopeProxy->child_scopes() non-blocking. I think currently since it blocks the main thread we may see the loading bar animation struggling during search. What do you think about starting the call in a separate thread and *only* waiting for the call to finish when the settings are actually accessed/needed (e.g. in call to data() if there is no better place)?

review: Needs Information
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

> Looks good, I think it will improve the perceived performance of Settings page
> quite a bit already. I yet need to check the silo and how it impacts search.
>
> I think there is one other potential improvement we could make though: make
> the call to m_scopeProxy->child_scopes() non-blocking. I think currently since
> it blocks the main thread we may see the loading bar animation struggling
> during search. What do you think about starting the call in a separate thread
> and *only* waiting for the call to finish when the settings are actually
> accessed/needed (e.g. in call to data() if there is no better place)?

Good point. Ok I've moved the child scopes update to a background thread, expected to complete upon a call to settings()

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Thanks, great stuff with QFuture! This looks very good, but I think with these changes we need to address the following now:

- m_settingsModel->update_child_scopes(m_scopesInstance->getAllMetadata()) is now called from a new thread, so m_settingsModel needs to synchronize access to its members. We may have a situation where Settings page is already opened by the UI and registry change triggers the update of child scopes at the same time.
- the destructor of Scope needs to wait for m_childScopesFuture, possibly with cancel() first (according to QFuture docs, the destructor of QFuture doesn't wait for it to finish). Alternatively use QFutureSynchronizer.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Marcus Tomlinson (marcustomlinson) wrote :

> Thanks, great stuff with QFuture! This looks very good, but I think with these
> changes we need to address the following now:
>
> - m_settingsModel->update_child_scopes(m_scopesInstance->getAllMetadata()) is
> now called from a new thread, so m_settingsModel needs to synchronize access
> to its members. We may have a situation where Settings page is already opened
> by the UI and registry change triggers the update of child scopes at the same
> time.
> - the destructor of Scope needs to wait for m_childScopesFuture, possibly with
> cancel() first (according to QFuture docs, the destructor of QFuture doesn't
> wait for it to finish). Alternatively use QFutureSynchronizer.

Very true. k, done.

Revision history for this message
Paweł Stołowski (stolowski) wrote :

Ok, looks good now, thanks!

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

http://paste.ubuntu.com/15789165/ has the outcome of this branch, an improvement for sure.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

New patch brigns down the thing to 0msec now, seems much nicer and can't see it regressing in any obvious way

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
313. By Marcus Tomlinson

Remove unnecessary sync() call

314. By Marcus Tomlinson

Don't emit settingsChanged() unless settings actually change

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
315. By Marcus Tomlinson

Emit settingsChanged when child scope state changes

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

Looks good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Unity/scope.cpp'
2--- src/Unity/scope.cpp 2016-03-17 22:35:44 +0000
3+++ src/Unity/scope.cpp 2016-04-19 08:05:18 +0000
4@@ -91,6 +91,7 @@
5 , m_hasNavigation(false)
6 , m_favorite(false)
7 , m_initialQueryDone(false)
8+ , m_childScopesDirty(true)
9 , m_searchController(new CollectionController)
10 , m_activationController(new CollectionController)
11 , m_status(Status::Okay)
12@@ -125,6 +126,7 @@
13
14 Scope::~Scope()
15 {
16+ m_childScopesFuture.waitForFinished();
17 }
18
19 void Scope::processSearchChunk(PushEvent* pushEvent)
20@@ -247,6 +249,7 @@
21 {
22 // refresh Settings view if needed
23 if (require_child_scopes_refresh()) {
24+ m_childScopesDirty = true;
25 update_child_scopes();
26 }
27
28@@ -721,6 +724,10 @@
29
30 setSearchInProgress(true);
31
32+ // If applicable, update this scope's child scopes now, as part of the search process
33+ // (i.e. while the loading bar is visible).
34+ update_child_scopes();
35+
36 if (m_proxy) {
37 scopes::SearchMetadata meta(QLocale::system().name().toStdString(), m_formFactor.toStdString());
38 auto const userAgent = m_scopesInstance->userAgentString();
39@@ -888,10 +895,6 @@
40
41 unity::shell::scopes::SettingsModelInterface* Scope::settings() const
42 {
43- if (m_settingsModel && m_scopesInstance)
44- {
45- m_settingsModel->update_child_scopes(m_scopesInstance->getAllMetadata());
46- }
47 return m_settingsModel.data();
48 }
49
50@@ -906,9 +909,20 @@
51
52 void Scope::update_child_scopes()
53 {
54- if (m_settingsModel && m_scopesInstance)
55+ // only run the update if child scopes have changed
56+ if (m_childScopesDirty && m_settingsModel && m_scopesInstance)
57 {
58- m_settingsModel->update_child_scopes(m_scopesInstance->getAllMetadata());
59+ // reset the flag so that re-entering this method won't restart the update unnecessarily
60+ m_childScopesDirty = false;
61+
62+ // just in case we have another update still running, wait here for it to complete
63+ m_childScopesFuture.waitForFinished();
64+
65+ // run the update in a seperate thread
66+ m_childScopesFuture = QtConcurrent::run([this]
67+ {
68+ m_settingsModel->update_child_scopes(m_scopesInstance->getAllMetadata());
69+ });
70 }
71 }
72
73@@ -1258,6 +1272,11 @@
74 m_activationController->invalidate();
75 }
76
77+void Scope::invalidateChildScopes()
78+{
79+ m_childScopesDirty = true;
80+}
81+
82 void Scope::invalidateResults()
83 {
84 if (m_isActive) {
85
86=== modified file 'src/Unity/scope.h'
87--- src/Unity/scope.h 2016-03-07 08:29:14 +0000
88+++ src/Unity/scope.h 2016-04-19 08:05:18 +0000
89@@ -21,6 +21,7 @@
90 #define NG_SCOPE_H
91
92 // Qt
93+#include <QFuture>
94 #include <QObject>
95 #include <QString>
96 #include <QTimer>
97@@ -180,6 +181,7 @@
98 void setSearchQueryString(const QString& search_query);
99
100 public Q_SLOTS:
101+ void invalidateChildScopes();
102 void invalidateResults();
103 virtual void dispatchSearch();
104 void setSearchInProgress(bool searchInProgress);
105@@ -248,6 +250,9 @@
106 bool m_favorite;
107 bool m_initialQueryDone;
108
109+ bool m_childScopesDirty;
110+ QFuture<void> m_childScopesFuture;
111+
112 QMap<std::string, QList<std::shared_ptr<unity::scopes::CategorisedResult>>> m_category_results;
113 std::unique_ptr<CollectionController> m_searchController;
114 std::unique_ptr<CollectionController> m_activationController;
115
116=== modified file 'src/Unity/scopes.cpp'
117--- src/Unity/scopes.cpp 2016-02-04 11:43:34 +0000
118+++ src/Unity/scopes.cpp 2016-04-19 08:05:18 +0000
119@@ -550,10 +550,12 @@
120 qDebug() << "Refreshing scope metadata";
121 refreshScopeMetadata();
122 Q_FOREACH(Scope::Ptr scope, m_scopes) {
123+ scope->invalidateChildScopes();
124 scope->invalidateResults();
125 }
126
127 Q_FOREACH(Scope::Ptr scope, m_tempScopes) {
128+ scope->invalidateChildScopes();
129 scope->invalidateResults();
130 }
131 }
132
133=== modified file 'src/Unity/settingsmodel.cpp'
134--- src/Unity/settingsmodel.cpp 2016-03-30 12:34:12 +0000
135+++ src/Unity/settingsmodel.cpp 2016-04-19 08:05:18 +0000
136@@ -152,6 +152,8 @@
137
138 QVariant SettingsModel::data(const QModelIndex& index, int role) const
139 {
140+ QMutexLocker locker(&m_mutex);
141+
142 int row = index.row();
143 QVariant result;
144
145@@ -247,6 +249,8 @@
146
147 QVariant SettingsModel::value(const QString& id) const
148 {
149+ QMutexLocker locker(&m_mutex);
150+
151 // Check for the setting id in the child scopes list first, in case the
152 // aggregator is incorrectly using a scope id as a settings as well.
153 if (m_child_scopes_data_by_id.contains(id))
154@@ -303,6 +307,8 @@
155
156 void SettingsModel::update_child_scopes(QMap<QString, sc::ScopeMetadata::SPtr> const& scopes_metadata)
157 {
158+ QMutexLocker locker(&m_mutex);
159+
160 if (!scopes_metadata.contains(m_scopeId) ||
161 !scopes_metadata[m_scopeId]->is_aggregator())
162 {
163@@ -346,15 +352,6 @@
164 }
165 QString displayName = QString::fromStdString(_("Display results from %1")).arg(QString(scopes_metadata[id]->display_name().c_str()));
166
167- QSharedPointer<QTimer> timer(new QTimer());
168- timer->setProperty("setting_id", id);
169- timer->setSingleShot(true);
170- timer->setInterval(m_settingsTimeout);
171- timer->setTimerType(Qt::VeryCoarseTimer);
172- connect(timer.data(), SIGNAL(timeout()), this,
173- SLOT(settings_timeout()));
174- m_child_scopes_timers[id] = timer;
175-
176 QSharedPointer<Data> setting(
177 new Data(id, displayName, QStringLiteral("boolean"), QVariantMap(), QVariant(), QVariant::Bool));
178
179@@ -364,13 +361,17 @@
180
181 if (reset) {
182 endResetModel();
183- Q_EMIT countChanged();
184 }
185+
186+ locker.unlock();
187+ Q_EMIT countChanged();
188 }
189
190 bool SettingsModel::setData(const QModelIndex &index, const QVariant &value,
191 int role)
192 {
193+ QMutexLocker locker(&m_mutex);
194+
195 int row = index.row();
196 QVariant result;
197
198@@ -400,6 +401,18 @@
199 {
200 case Roles::RoleValue:
201 {
202+ if (!m_child_scopes_timers.contains(data->id))
203+ {
204+ QSharedPointer<QTimer> timer(new QTimer());
205+ timer->setProperty("setting_id", data->id);
206+ timer->setSingleShot(true);
207+ timer->setInterval(m_settingsTimeout);
208+ timer->setTimerType(Qt::VeryCoarseTimer);
209+ connect(timer.data(), SIGNAL(timeout()), this,
210+ SLOT(settings_timeout()));
211+ m_child_scopes_timers[data->id] = timer;
212+ }
213+
214 QSharedPointer<QTimer> timer = m_child_scopes_timers[data->id];
215 timer->setProperty("index", row - m_data.size());
216 timer->setProperty("value", value);
217@@ -422,16 +435,20 @@
218
219 int SettingsModel::count() const
220 {
221+ QMutexLocker locker(&m_mutex);
222 return m_data.size() + m_child_scopes_data.size();
223 }
224
225 bool SettingsModel::require_child_scopes_refresh() const
226 {
227+ QMutexLocker locker(&m_mutex);
228 return m_requireChildScopesRefresh;
229 }
230
231 void SettingsModel::settings_timeout()
232 {
233+ QMutexLocker locker(&m_mutex);
234+
235 QObject *timer = sender();
236 if (!timer)
237 {
238@@ -454,6 +471,9 @@
239 try
240 {
241 m_scopeProxy->set_child_scopes(m_child_scopes);
242+
243+ locker.unlock();
244+ Q_EMIT settingsChanged();
245 }
246 catch (std::exception const& e)
247 {
248@@ -488,6 +508,7 @@
249 FileLock lock = unixLock(m_settings_path, true);
250 m_settings->sync(); // make sure the change to setting value is synced to fs
251
252+ locker.unlock();
253 Q_EMIT settingsChanged();
254 }
255 catch(const unity::FileException& e)
256
257=== modified file 'src/Unity/settingsmodel.h'
258--- src/Unity/settingsmodel.h 2016-03-30 12:34:12 +0000
259+++ src/Unity/settingsmodel.h 2016-04-19 08:05:18 +0000
260@@ -29,6 +29,7 @@
261
262 #include <QAbstractListModel>
263 #include <QList>
264+#include <QMutex>
265 #include <QSharedPointer>
266
267 QT_BEGIN_NAMESPACE
268@@ -93,6 +94,7 @@
269 void tryLoadSettings() const;
270
271 protected:
272+ mutable QMutex m_mutex;
273 QString m_scopeId;
274 unity::scopes::ScopeProxy m_scopeProxy;
275 int m_settingsTimeout;

Subscribers

People subscribed via source and target branches

to all changes: