Merge lp:~gerboland/qtmir/dont-delete-qml-cache-on-good-stop into lp:qtmir

Proposed by Gerry Boland
Status: Merged
Approved by: Nick Dedekind
Approved revision: no longer in the source branch.
Merged at revision: 383
Proposed branch: lp:~gerboland/qtmir/dont-delete-qml-cache-on-good-stop
Merge into: lp:qtmir
Diff against target: 325 lines (+202/-6)
4 files modified
src/modules/Unity/Application/application.cpp (+64/-6)
src/modules/Unity/Application/application.h (+3/-0)
src/modules/Unity/Application/application_manager.cpp (+1/-0)
tests/modules/ApplicationManager/application_manager_test.cpp (+134/-0)
To merge this branch: bzr merge lp:~gerboland/qtmir/dont-delete-qml-cache-on-good-stop
Reviewer Review Type Date Requested Status
Michał Sawicz Approve
Nick Dedekind (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+272761@code.launchpad.net

Commit message

Add "Closing" state to Application, use it to distinguish user-induced close from app-induced close. Don't clear QML cache if user-induced.

Much code taken from a partially-related branch by Nick Dedekind:
https://code.launchpad.net/~nick-dedekind/qtmir/polite-close/+merge/262188

Note there is 1 disabled test, which needs additional work to fix.

Description of the change

 * Are there any related MPs required for this MP to build/function as expected? Please list.
N
 * Did you perform an exploratory manual test run of your code change and any related functionality?
Y
 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
N/A

To post a comment you must log in.
Revision history for this message
Gerry Boland (gerboland) wrote :

If app crashes, the QML cache may not be cleared. This requires additional work, which I'd prefer to consider in a later MR.

Revision history for this message
Gerry Boland (gerboland) wrote :

To test, get a shell on the phone and watch the contents of the ~/.cache/QML/Apps/ directory. Each app will get a directory with its appId name, which contains its cache. Deleting the cache means deleteing this directory.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

"virtual memory exhausted: Cannot allocate memory" - trying CI again

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

small comment.

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

small comment.

Revision history for this message
Nick Dedekind (nick-dedekind) :
review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

LGTM

review: Approve
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes.

 * Did CI run pass? If not, please explain why.
Yes

review: Approve
Revision history for this message
Michał Sawicz (saviq) wrote :

Approving the uncommit, sorry for the confusion.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/modules/Unity/Application/application.cpp'
2--- src/modules/Unity/Application/application.cpp 2015-08-20 16:04:25 +0000
3+++ src/modules/Unity/Application/application.cpp 2015-10-01 08:04:31 +0000
4@@ -73,10 +73,27 @@
5 qCDebug(QTMIR_APPLICATIONS) << "Application::~Application";
6
7 // (ricmm) -- To be on the safe side, better wipe the application QML compile cache if it crashes on startup
8- if (m_processState == Application::ProcessUnknown
9- || state() == Application::Starting
10- || state() == Application::Running) {
11- wipeQMLCache();
12+ if (m_processState == Application::ProcessUnknown) {
13+ wipeQMLCache();
14+ }
15+
16+ switch (m_state) {
17+ case InternalState::Starting:
18+ case InternalState::Running:
19+ case InternalState::RunningInBackground:
20+ case InternalState::SuspendingWaitSession:
21+ case InternalState::SuspendingWaitProcess:
22+ wipeQMLCache();
23+ break;
24+ case InternalState::Closing:
25+ case InternalState::Suspended:
26+ case InternalState::StoppedResumable:
27+ break;
28+ case InternalState::Stopped:
29+ if (m_processState == Application::ProcessFailed) { // process crashed
30+ wipeQMLCache();
31+ }
32+ break;
33 }
34
35 if (m_session) {
36@@ -203,6 +220,8 @@
37 return "SuspendingWaitProcess";
38 case InternalState::Suspended:
39 return "Suspended";
40+ case InternalState::Closing:
41+ return "Closing";
42 case InternalState::StoppedResumable:
43 return "StoppedResumable";
44 case InternalState::Stopped:
45@@ -266,6 +285,7 @@
46 case InternalState::RunningInBackground:
47 case InternalState::SuspendingWaitSession:
48 case InternalState::SuspendingWaitProcess:
49+ case InternalState::Closing:
50 return Running;
51 case InternalState::Suspended:
52 return Suspended;
53@@ -321,6 +341,8 @@
54 case InternalState::SuspendingWaitProcess:
55 // should leave the app alone until it reaches Suspended state
56 break;
57+ case InternalState::Closing:
58+ break;
59 case InternalState::StoppedResumable:
60 respawn();
61 break;
62@@ -350,6 +372,9 @@
63 case InternalState::Suspended:
64 // it's already going where we it's wanted
65 break;
66+ case InternalState::Closing:
67+ // don't suspend while it is closing
68+ break;
69 case InternalState::StoppedResumable:
70 case InternalState::Stopped:
71 // the app doesn't have a process in the first place, so there's nothing to suspend
72@@ -382,6 +407,32 @@
73 m_pid = pid;
74 }
75
76+void Application::close()
77+{
78+ qCDebug(QTMIR_APPLICATIONS) << "Application::close - appId=" << appId();
79+
80+ switch (m_state) {
81+ case InternalState::Starting:
82+ case InternalState::Running:
83+ setInternalState(InternalState::Closing);
84+ break;
85+ case InternalState::RunningInBackground:
86+ case InternalState::SuspendingWaitSession:
87+ case InternalState::SuspendingWaitProcess:
88+ case InternalState::Suspended:
89+ setRequestedState(RequestedRunning);
90+ setInternalState(InternalState::Closing);
91+ break;
92+ case InternalState::Closing:
93+ // already on the way
94+ break;
95+ case InternalState::StoppedResumable:
96+ case InternalState::Stopped:
97+ // too late
98+ break;
99+ }
100+}
101+
102 void Application::setArguments(const QStringList arguments)
103 {
104 m_arguments = arguments;
105@@ -411,6 +462,7 @@
106 case InternalState::Starting:
107 case InternalState::Running:
108 case InternalState::RunningInBackground:
109+ case InternalState::Closing:
110 m_session->resume();
111 break;
112 case InternalState::SuspendingWaitSession:
113@@ -474,6 +526,9 @@
114 case InternalState::Suspended:
115 releaseWakelock();
116 break;
117+ case InternalState::Closing:
118+ acquireWakelock();
119+ break;
120 case InternalState::StoppedResumable:
121 releaseWakelock();
122 break;
123@@ -546,7 +601,8 @@
124 if (m_state == InternalState::Starting) {
125 // that was way too soon. let it go away
126 setInternalState(InternalState::Stopped);
127- } else if (m_state == InternalState::StoppedResumable) {
128+ } else if (m_state == InternalState::StoppedResumable ||
129+ m_state == InternalState::Closing) {
130 // The application stopped nicely, likely closed itself. Thus not meant to be resumed later.
131 setInternalState(InternalState::Stopped);
132 } else {
133@@ -657,7 +713,8 @@
134 case Session::Stopped:
135 if (!canBeResumed()
136 || m_state == InternalState::Starting
137- || m_state == InternalState::Running) {
138+ || m_state == InternalState::Running
139+ || m_state == InternalState::Closing) {
140 /* 1. application is not managed by upstart
141 * 2. application is managed by upstart, but has stopped before it managed
142 * to create a surface, we can assume it crashed on startup, and thus
143@@ -665,6 +722,7 @@
144 * 3. application is managed by upstart and is in foreground (i.e. has
145 * Running state), if Mir reports the application disconnects, it
146 * either crashed or stopped itself.
147+ * 4. We're expecting the application to stop after a close request
148 */
149 setInternalState(InternalState::Stopped);
150 } else {
151
152=== modified file 'src/modules/Unity/Application/application.h'
153--- src/modules/Unity/Application/application.h 2015-08-20 16:10:50 +0000
154+++ src/modules/Unity/Application/application.h 2015-10-01 08:04:31 +0000
155@@ -72,6 +72,7 @@
156 SuspendingWaitSession,
157 SuspendingWaitProcess,
158 Suspended,
159+ Closing, // The user has requested the app be closed
160 StoppedResumable, // The process stopped but we want to keep the Application object around
161 // so it can be respawned as if it never stopped running in the first place.
162 Stopped // It closed itself, crashed or it stopped and we can't respawn it
163@@ -125,6 +126,8 @@
164
165 pid_t pid() const;
166
167+ void close();
168+
169 // for tests
170 InternalState internalState() const { return m_state; }
171
172
173=== modified file 'src/modules/Unity/Application/application_manager.cpp'
174--- src/modules/Unity/Application/application_manager.cpp 2015-08-21 00:00:16 +0000
175+++ src/modules/Unity/Application/application_manager.cpp 2015-10-01 08:04:31 +0000
176@@ -438,6 +438,7 @@
177 return false;
178 }
179
180+ application->close();
181 remove(application);
182
183 bool result = m_taskController->stop(application->longAppId());
184
185=== modified file 'tests/modules/ApplicationManager/application_manager_test.cpp'
186--- tests/modules/ApplicationManager/application_manager_test.cpp 2015-08-31 09:51:28 +0000
187+++ tests/modules/ApplicationManager/application_manager_test.cpp 2015-10-01 08:04:31 +0000
188@@ -1853,3 +1853,137 @@
189 ASSERT_EQ(Application::InternalState::RunningInBackground, application->internalState());
190 EXPECT_EQ(Application::Running, application->state());
191 }
192+
193+/*
194+ * Test that when user stops an application, application does not delete QML cache
195+ */
196+TEST_F(ApplicationManagerTests,QMLcacheRetainedOnAppStop)
197+{
198+ using namespace ::testing;
199+ const QString appId("testAppId1234");
200+ quint64 procId = 5551;
201+
202+ // Set up Mocks & signal watcher
203+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
204+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
205+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
206+
207+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
208+
209+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
210+ .Times(1)
211+ .WillOnce(Return(true));
212+
213+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
214+ applicationManager.onProcessStarting(appId);
215+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
216+ bool authed = true;
217+ applicationManager.authorizeSession(procId, authed);
218+ onSessionStarting(session);
219+
220+ // Create fake QML cache for this app
221+ QString path(QDir::homePath() + QStringLiteral("/.cache/QML/Apps/") + appId);
222+ QDir dir(path);
223+ dir.mkpath(path);
224+
225+ // Stop app
226+ applicationManager.stopApplication(appId);
227+
228+ EXPECT_EQ(0, applicationManager.count());
229+ EXPECT_TRUE(dir.exists());
230+}
231+
232+/*
233+ * Test that if running application stops unexpectedly, AppMan deletes the QML cache
234+ */
235+TEST_F(ApplicationManagerTests,DISABLED_QMLcacheDeletedOnAppCrash)
236+{
237+ using namespace ::testing;
238+ const QString appId("testAppId12345");
239+ quint64 procId = 5551;
240+
241+ // Set up Mocks & signal watcher
242+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
243+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
244+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
245+
246+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
247+
248+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
249+ .Times(1)
250+ .WillOnce(Return(true));
251+
252+ Application *the_app = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
253+ applicationManager.onProcessStarting(appId);
254+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
255+ bool authed = true;
256+ applicationManager.authorizeSession(procId, authed);
257+ onSessionStarting(session);
258+
259+ // Have app in fully Running state
260+ FakeMirSurface *aSurface = new FakeMirSurface;
261+ onSessionCreatedSurface(session.get(), aSurface);
262+ aSurface->drawFirstFrame();
263+ ASSERT_EQ(Application::InternalState::Running, the_app->internalState());
264+
265+ // Create fake QML cache for this app
266+ QString path(QDir::homePath() + QStringLiteral("/.cache/QML/Apps/") + appId);
267+ QDir dir(path);
268+ dir.mkpath(path);
269+
270+ // Report app crash
271+ onSessionStopping(session);
272+ // Upstart notifies of **crashing** app
273+ applicationManager.onProcessFailed(appId, true);
274+ applicationManager.onProcessStopped(appId);
275+
276+ EXPECT_EQ(0, applicationManager.count());
277+ EXPECT_FALSE(dir.exists());
278+}
279+
280+/*
281+ * Test that if running application stops itself cleanly, AppMan retains the QML cache
282+ */
283+TEST_F(ApplicationManagerTests,QMLcacheRetainedOnAppShutdown)
284+{
285+ using namespace ::testing;
286+ const QString appId("testAppId123456");
287+ quint64 procId = 5551;
288+
289+ // Set up Mocks & signal watcher
290+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
291+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
292+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
293+
294+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
295+
296+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
297+ .Times(1)
298+ .WillOnce(Return(true));
299+
300+ Application *the_app = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
301+ applicationManager.onProcessStarting(appId);
302+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
303+ bool authed = true;
304+ applicationManager.authorizeSession(procId, authed);
305+ onSessionStarting(session);
306+
307+ // Have app in fully Running state
308+ FakeMirSurface *aSurface = new FakeMirSurface;
309+ onSessionCreatedSurface(session.get(), aSurface);
310+ aSurface->drawFirstFrame();
311+ ASSERT_EQ(Application::InternalState::Running, the_app->internalState());
312+
313+ // Create fake QML cache for this app
314+ QString path(QDir::homePath() + QStringLiteral("/.cache/QML/Apps/") + appId);
315+ QDir dir(path);
316+ dir.mkpath(path);
317+
318+ // Report app stop
319+ onSessionStopping(session);
320+ // Upstart notifies of stopping app
321+ applicationManager.onProcessStopped(appId);
322+
323+ EXPECT_EQ(0, applicationManager.count());
324+ EXPECT_TRUE(dir.exists());
325+}

Subscribers

People subscribed via source and target branches