Merge lp:~xavi-garcia-mena/keeper/sequential-execution-and-valgrind-fixes into lp:keeper

Proposed by Xavi Garcia
Status: Merged
Approved by: Charles Kerr
Approved revision: 55
Merged at revision: 55
Proposed branch: lp:~xavi-garcia-mena/keeper/sequential-execution-and-valgrind-fixes
Merge into: lp:keeper
Prerequisite: lp:~xavi-garcia-mena/keeper/std-sharedptr-qlocalsocket
Diff against target: 510 lines (+212/-54)
6 files modified
src/cli/main.cpp (+52/-10)
src/helper/backup-helper.cpp (+12/-2)
src/service/keeper.cpp (+45/-9)
src/storage-framework/storage_framework_client.cpp (+10/-1)
src/storage-framework/storage_framework_client.h (+3/-0)
tests/integration/helpers/helpers-test.cc (+90/-32)
To merge this branch: bzr merge lp:~xavi-garcia-mena/keeper/sequential-execution-and-valgrind-fixes
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
Review via email: mp+301797@code.launchpad.net

Commit message

Adds sequential execution of backup helpers.

Description of the change

Adds sequential execution of backup helpers.

Fixes the issue when deleting the "conn" variable in the keeper.cpp file detected with valgrind.

Modified the integration full test to run 2 backup helpers sequentially.

Modified backup_helper when detecting if it already finished.
Now it looks if it finishes when we receive the upstart signal. If we do that when detecting that the number of bytes expected and received from the backup helper match we may start the next backup helper too early, so for example, we were closing the storage-framework twice.

Added QFutureWatcher to wait for the storage framework socket to be closed before starting next task.

Added workaround to modify the number of bytes written right after calling write in the backup helper. The issue is that QlocalSocket::bytesWritten is called more times than expected. (We need to confirm this issue with Michi and James)

To post a comment you must log in.
Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

I also modified the client for testing purposes.
It does a backup of all the XDG folders when passing --use-uuids as the first parameter.

This was really useful when testing keeper-service on the desktop with the upstart untrusted-helper job.

Revision history for this message
Xavi Garcia (xavi-garcia-mena) wrote :

Also... There are some files following camelCase. I followed the coding style in the file. My idea is to create a separated branch changing everything from camelCase to snake_case :)

Revision history for this message
Charles Kerr (charlesk) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/cli/main.cpp'
2--- src/cli/main.cpp 2016-06-08 16:40:34 +0000
3+++ src/cli/main.cpp 2016-08-02 15:14:16 +0000
4@@ -22,6 +22,7 @@
5 #include <util/logging.h>
6
7 #include "keeper_interface.h"
8+#include "keeper_user_interface.h"
9
10 #include <QCoreApplication>
11 #include <QDBusConnection>
12@@ -36,7 +37,7 @@
13 qInstallMessageHandler(util::loggingFunction);
14
15 QCoreApplication app(argc, argv);
16-// DBusTypes::registerMetaTypes();
17+ DBusTypes::registerMetaTypes();
18 // Variant::registerMetaTypes();
19 std::srand(unsigned(std::time(nullptr)));
20
21@@ -51,15 +52,56 @@
22 qDebug() << QDBusConnection::sessionBus().baseService();
23 }
24
25- QScopedPointer<DBusInterfaceKeeper> keeperInterface(new DBusInterfaceKeeper(DBusTypes::KEEPER_SERVICE,
26- DBusTypes::KEEPER_SERVICE_PATH,
27- QDBusConnection::sessionBus(), 0));
28-
29- QDBusReply<void> userResp = keeperInterface->call(QLatin1String("start"));
30-
31- if (!userResp.isValid())
32- {
33- qWarning() << "Error starting backup: " << userResp.error().message();
34+ qDebug() << "Argc = " << argc;
35+ if (argc == 2 && QString("--use-uuids") == argv[1])
36+ {
37+ QScopedPointer<DBusInterfaceKeeperUser> user_iface(new DBusInterfaceKeeperUser(
38+ DBusTypes::KEEPER_SERVICE,
39+ DBusTypes::KEEPER_USER_PATH,
40+ QDBusConnection::sessionBus()
41+ ) );
42+ QDBusReply<QVariantDictMap> choices = user_iface->call("GetBackupChoices");
43+ if (!choices.isValid())
44+ {
45+ qWarning() << "Error getting backup choices: " << choices.error().message();
46+ }
47+
48+ QStringList uuids;
49+ auto choices_values = choices.value();
50+ for(auto iter = choices_values.begin(); iter != choices_values.end(); ++iter)
51+ {
52+ const auto& values = iter.value();
53+ auto iter_values = values.find("type");
54+ if (iter_values != values.end())
55+ {
56+ if (iter_values.value().toString() == "folder")
57+ {
58+ // got it
59+ qDebug() << "Adding uuid " << iter.key() << " with type: " << "folder";
60+ uuids << iter.key();
61+ }
62+ }
63+ }
64+
65+ QDBusReply<void> backup_reply = user_iface->call("StartBackup", uuids);
66+
67+ if (!backup_reply.isValid())
68+ {
69+ qWarning() << "Error starting backup: " << backup_reply.error().message();
70+ }
71+ }
72+ else
73+ {
74+ QScopedPointer<DBusInterfaceKeeper> keeperInterface(new DBusInterfaceKeeper(DBusTypes::KEEPER_SERVICE,
75+ DBusTypes::KEEPER_SERVICE_PATH,
76+ QDBusConnection::sessionBus(), 0));
77+
78+ QDBusReply<void> userResp = keeperInterface->call(QLatin1String("start"));
79+
80+ if (!userResp.isValid())
81+ {
82+ qWarning() << "Error starting backup: " << userResp.error().message();
83+ }
84 }
85
86
87
88=== modified file 'src/helper/backup-helper.cpp'
89--- src/helper/backup-helper.cpp 2016-08-02 15:14:16 +0000
90+++ src/helper/backup-helper.cpp 2016-08-02 15:14:16 +0000
91@@ -122,6 +122,13 @@
92 std::bind(&BackupHelperPrivate::on_data_uploaded, this, std::placeholders::_1)
93 );
94
95+ auto testHelper = qgetenv("KEEPER_TEST_HELPER");
96+ if (!testHelper.isEmpty())
97+ {
98+ // In the testing environment we don't have upstart.
99+ // TODO investigate if there's a better way to send the started signal in the tests
100+ q_ptr->set_state(Helper::State::STARTED);
101+ }
102 reset_inactivity_timer();
103 }
104
105@@ -152,9 +159,11 @@
106
107 void on_data_uploaded(qint64 n)
108 {
109- n_uploaded_ += n;
110+ // TODO review this after checking if there's a bug in storage framework.
111+ // TODO The issue is that bytesWritten is called for every backup helper that was
112+ // TODO executed before.
113+// n_uploaded_ += n;
114 qDebug("n_read %zu n_uploaded %zu (newly uploaded %zu)", size_t(n_read_), size_t(n_uploaded_), size_t(n));
115- check_for_done();
116 process_more();
117 }
118
119@@ -184,6 +193,7 @@
120 if (n > 0) {
121 upload_buffer_.remove(0, int(n));
122 qDebug("upload_buffer_.size() is %zu after writing %zu to cloud", size_t(upload_buffer_.size()), size_t(n));
123+ n_uploaded_ += n;
124 continue;
125 }
126 else {
127
128=== modified file 'src/service/keeper.cpp'
129--- src/service/keeper.cpp 2016-08-02 15:14:16 +0000
130+++ src/service/keeper.cpp 2016-08-02 15:14:16 +0000
131@@ -47,6 +47,7 @@
132 QScopedPointer<StorageFrameworkClient> storage_;
133 QVector<Metadata> cached_backup_choices_;
134 QVector<Metadata> cached_restore_choices_;
135+ QStringList remaining_tasks_;
136
137 KeeperPrivate(Keeper* keeper,
138 const QSharedPointer<MetadataProvider>& backup_choices,
139@@ -58,6 +59,7 @@
140 , storage_(new StorageFrameworkClient())
141 , cached_backup_choices_()
142 , cached_restore_choices_()
143+ , remaining_tasks_()
144 {
145 // listen for backup helper state changes
146 QObject::connect(backup_helper_.data(), &Helper::state_changed,
147@@ -90,6 +92,26 @@
148 }
149 }
150
151+ void start_tasks(QStringList const& tasks)
152+ {
153+ remaining_tasks_ = tasks;
154+ start_remaining_tasks();
155+ }
156+
157+ void start_remaining_tasks()
158+ {
159+ if (remaining_tasks_.size())
160+ {
161+ auto task = remaining_tasks_.takeFirst();
162+ start_task(task);
163+ }
164+ }
165+
166+ void clear_remaining_taks()
167+ {
168+ remaining_tasks_.clear();
169+ }
170+
171 private:
172
173 void on_backup_helper_state_changed(Helper::State state)
174@@ -104,7 +126,13 @@
175 break;
176
177 case Helper::State::CANCELLED:
178+ qDebug() << "Backup helper cancelled... closing the socket.";
179+ storage_->closeUploader();
180+ break;
181 case Helper::State::FAILED:
182+ qDebug() << "Backup helper failed... closing the socket.";
183+ storage_->closeUploader();
184+ break;
185 case Helper::State::COMPLETE:
186 qDebug() << "Backup helper finished... closing the socket.";
187 storage_->closeUploader();
188@@ -155,6 +183,8 @@
189 : QObject(parent)
190 , d_ptr(new KeeperPrivate(this, backup_choices, restore_choices))
191 {
192+ Q_D(Keeper);
193+ QObject::connect(d->storage_.data(), &StorageFrameworkClient::uploaderClosed, this, &Keeper::socketClosed);
194 }
195
196 Keeper::~Keeper() = default;
197@@ -172,11 +202,7 @@
198 {
199 Q_D(Keeper);
200
201- // TODO maintain the list of remaining tasks
202- if (keys.size())
203- {
204- d->start_task(keys.at(0));
205- }
206+ d->start_tasks(keys);
207 }
208
209 QDBusUnixFileDescriptor
210@@ -187,8 +213,7 @@
211 qDebug("Helper::StartBackup(n_bytes=%zu)", size_t(n_bytes));
212
213 // the next time we get a socket from storage-framework, return it to our caller
214- auto conn = new QMetaObject::Connection();
215- auto on_socket_ready = [bus,msg,n_bytes,this,d,conn](std::shared_ptr<QLocalSocket> const &sf_socket)
216+ auto on_socket_ready = [bus,msg,n_bytes,this,d](std::shared_ptr<QLocalSocket> const &sf_socket)
217 {
218 if (sf_socket)
219 {
220@@ -200,10 +225,9 @@
221 auto reply = msg.createReply();
222 reply << QVariant::fromValue(QDBusUnixFileDescriptor(d->backup_helper_->get_helper_socket()));
223 bus.send(reply);
224- delete conn;
225 };
226 // cppcheck-suppress deallocuse
227- *conn = QObject::connect(d->storage_.data(), &StorageFrameworkClient::socketReady, on_socket_ready);
228+ QObject::connect(d->storage_.data(), &StorageFrameworkClient::socketReady, on_socket_ready);
229
230 // ask storage framework for a new socket
231 d->storage_->getNewFileForBackup(n_bytes);
232@@ -226,7 +250,19 @@
233
234 void Keeper::socketClosed()
235 {
236+ Q_D(Keeper);
237+
238 qDebug() << "The storage framework socket was closed";
239+ if (d->backup_helper_->state() == Helper::State::COMPLETE)
240+ {
241+ // the helper finished successfully, process more tasks
242+ d->start_remaining_tasks();
243+ }
244+ else
245+ {
246+ // error or cancel case... clear all remaining tasks
247+ d->clear_remaining_taks();
248+ }
249 }
250
251 QVector<Metadata>
252
253=== modified file 'src/storage-framework/storage_framework_client.cpp'
254--- src/storage-framework/storage_framework_client.cpp 2016-08-02 15:14:16 +0000
255+++ src/storage-framework/storage_framework_client.cpp 2016-08-02 15:14:16 +0000
256@@ -36,9 +36,12 @@
257 : QObject(parent)
258 , runtime_(Runtime::create())
259 , uploader_ready_watcher_(parent)
260+ , uploader_closed_watcher_(parent)
261 , uploader_()
262 {
263 QObject::connect(&uploader_ready_watcher_,&QFutureWatcher<std::shared_ptr<Uploader>>::finished, this, &StorageFrameworkClient::uploaderReady);
264+ QObject::connect(&uploader_closed_watcher_,&QFutureWatcher<std::shared_ptr<unity::storage::qt::client::File>>::finished, this, &StorageFrameworkClient::onUploaderClosed);
265+
266 }
267
268
269@@ -83,7 +86,7 @@
270 try
271 {
272 uploader_->socket()->disconnectFromServer();
273- uploader_->finish_upload();
274+ uploader_closed_watcher_.setFuture(uploader_->finish_upload());
275 }
276 catch (std::exception & e)
277 {
278@@ -97,3 +100,9 @@
279
280 Q_EMIT (socketReady(uploader_->socket()));
281 }
282+
283+void StorageFrameworkClient::onUploaderClosed()
284+{
285+ qDebug() << "Uploader was closed";
286+ Q_EMIT(uploaderClosed());
287+}
288
289=== modified file 'src/storage-framework/storage_framework_client.h'
290--- src/storage-framework/storage_framework_client.h 2016-08-02 15:14:16 +0000
291+++ src/storage-framework/storage_framework_client.h 2016-08-02 15:14:16 +0000
292@@ -42,14 +42,17 @@
293
294 public Q_SLOTS:
295 void uploaderReady();
296+ void onUploaderClosed();
297
298 Q_SIGNALS:
299 void socketReady(std::shared_ptr<QLocalSocket> const& sf_socket);
300+ void uploaderClosed();
301
302 private:
303 unity::storage::qt::client::Runtime::SPtr runtime_;
304
305 // watchers
306 QFutureWatcher<std::shared_ptr<unity::storage::qt::client::Uploader>> uploader_ready_watcher_;
307+ QFutureWatcher<std::shared_ptr<unity::storage::qt::client::File>> uploader_closed_watcher_;
308 std::shared_ptr<unity::storage::qt::client::Uploader> uploader_;
309 };
310
311=== modified file 'tests/integration/helpers/helpers-test.cc'
312--- tests/integration/helpers/helpers-test.cc 2016-08-01 10:06:24 +0000
313+++ tests/integration/helpers/helpers-test.cc 2016-08-02 15:14:16 +0000
314@@ -313,6 +313,9 @@
315 qDebug("test failed; leaving '%s'", data_home_dir.path().toUtf8().constData());
316
317 ASSERT_EQ(nullptr, bus);
318+
319+ // let's leave things clean
320+ EXPECT_TRUE(removeHelperMarkBeforeStarting());
321 }
322
323 bool startKeeperClient()
324@@ -326,6 +329,29 @@
325 return true;
326 }
327
328+ bool checkStorageFrameworkFiles(QStringList const & sourceDirs, bool compression=false)
329+ {
330+ QStringList dirs = sourceDirs;
331+
332+ while (dirs.size() > 0)
333+ {
334+ auto dir = dirs.takeLast();
335+ QString lastFile = getLastStorageFrameworkFile();
336+ if (lastFile.isEmpty())
337+ {
338+ qWarning() << "Did not found enough storage framework files";
339+ return false;
340+ }
341+ if (!compareTarContent (lastFile, dir, compression))
342+ {
343+ return false;
344+ }
345+ // remove the last file, so next iteration the last one is different
346+ QFile::remove(lastFile);
347+ }
348+ return true;
349+ }
350+
351 bool checkLastStorageFrameworkFile (QString const & sourceDir, bool compression=false)
352 {
353 QString lastFile = getLastStorageFrameworkFile();
354@@ -341,6 +367,8 @@
355 {
356 QTemporaryDir tempDir;
357
358+ qDebug() << "Comparing tar content for dir: " << sourceDir << " with tar: " << tarPath;
359+
360 QFileInfo checkFile(tarPath);
361 if (!checkFile.exists())
362 {
363@@ -411,26 +439,27 @@
364 qWarning() << "ERROR: Storage framework directory: [" << storage_framework_dir_path << "] does not exist.";
365 return QString();
366 }
367- QFileInfo lastFile;
368+
369+ QStringList sortedFiles;
370 QFileInfoList files = storage_framework_dir.entryInfoList();
371 for (int i = 0; i < files.size(); ++i)
372 {
373 QFileInfo file = files[i];
374 if (file.isFile())
375 {
376- if (file.created() > lastFile.created())
377- {
378- lastFile = file;
379- }
380+ sortedFiles << files[i].absoluteFilePath();
381 }
382 }
383- if (!lastFile.isFile())
384+
385+ // we detect the last file by name.
386+ // the file creation time had not enough precision
387+ sortedFiles.sort();
388+ if (sortedFiles.isEmpty())
389 {
390 qWarning() << "ERROR: last file in the storage-framework directory was not found";
391 return QString();
392 }
393-
394- return lastFile.absoluteFilePath();
395+ return sortedFiles.last();
396 }
397
398 bool checkStorageFrameworkContent(QString const & content)
399@@ -465,21 +494,53 @@
400
401
402
403- bool waitUntilHelperFinishes(int maxTimeout = 15000)
404+ bool waitUntilHelperFinishes(QString const & app_id, int maxTimeout = 15000, int times = 1)
405 {
406 // TODO create a new mock for upstart that controls the lifecycle of the
407 // helper process so we can do this in a cleaner way.
408 QFile helper_mark(SIMPLE_HELPER_MARK_FILE_PATH);
409 QElapsedTimer timer;
410 timer.start();
411- while (!timer.hasExpired(maxTimeout))
412+ auto times_to_wait = times;
413+ bool finished = false;
414+ while (!timer.hasExpired(maxTimeout) && !finished)
415 {
416 if (helper_mark.exists())
417 {
418- return true;
419+ if (--times_to_wait)
420+ {
421+ helper_mark.remove();
422+ timer.restart();
423+ qDebug() << "HELPER FINISHED, WAITING FOR " << times_to_wait << " MORE";
424+ }
425+ else
426+ {
427+ qDebug() << "ALL HELPERS FINISHED";
428+ finished = true;
429+ }
430+ sendUpstartHelperTermination(app_id);
431 }
432 }
433- return false;
434+ return finished;
435+ }
436+
437+ void sendUpstartHelperTermination(QString const &app_id)
438+ {
439+ // send the upstart signal so keeper-service is aware of the helper termination
440+ DbusTestDbusMockObject* objUpstart =
441+ dbus_test_dbus_mock_get_object(mock, UPSTART_PATH, UPSTART_INTERFACE, NULL);
442+
443+ QString eventInfoStr = QString("('stopped', ['JOB=untrusted-helper', 'INSTANCE=backup-helper::%1'])").arg(app_id.toStdString().c_str());
444+ dbus_test_dbus_mock_object_emit_signal(
445+ mock, objUpstart, "EventEmitted", G_VARIANT_TYPE("(sas)"),
446+ g_variant_new_parsed(
447+ eventInfoStr.toLocal8Bit().data()),
448+ NULL);
449+ g_usleep(100000);
450+ while (g_main_pending())
451+ {
452+ g_main_iteration(TRUE);
453+ }
454 }
455
456 QString getUUIDforXdgFolderPath(QString const &path, QVariantDictMap const & choices) const
457@@ -809,37 +870,34 @@
458 // fill something in the music dir
459 ASSERT_TRUE(FileUtils::fillTemporaryDirectory(user_dir, qrand() % 1000));
460
461-
462 // search for the user folder uuid
463 auto user_folder_uuid = getUUIDforXdgFolderPath(user_dir, choices.value());
464 ASSERT_FALSE(user_folder_uuid.isEmpty());
465 qDebug() << "User folder UUID is: " << user_folder_uuid;
466
467+ QString user_option_2 = "XDG_VIDEOS_DIR";
468+
469+ auto user_dir_2 = qgetenv(user_option_2.toLatin1().data());
470+ ASSERT_FALSE(user_dir_2.isEmpty());
471+ qDebug() << "USER DIR 2: " << user_dir_2;
472+
473+ // fill something in the music dir
474+ ASSERT_TRUE(FileUtils::fillTemporaryDirectory(user_dir_2, qrand() % 1000));
475+
476+ // search for the user folder uuid
477+ auto user_folder_uuid_2 = getUUIDforXdgFolderPath(user_dir_2, choices.value());
478+ ASSERT_FALSE(user_folder_uuid_2.isEmpty());
479+ qDebug() << "User folder 2 UUID is: " << user_folder_uuid_2;
480+
481 // Now we know the music folder uuid, let's start the backup for it.
482- QDBusReply<void> backup_reply = user_iface->call("StartBackup", QStringList{user_folder_uuid});
483+ QDBusReply<void> backup_reply = user_iface->call("StartBackup", QStringList{user_folder_uuid, user_folder_uuid_2});
484 ASSERT_TRUE(backup_reply.isValid()) << qPrintable(QDBusConnection::sessionBus().lastError().message());
485
486 // Wait until the helper finishes
487- EXPECT_TRUE(waitUntilHelperFinishes());
488-
489- // send the upstart signal so keeper-service is aware of the helper termination
490- DbusTestDbusMockObject* objUpstart =
491- dbus_test_dbus_mock_get_object(mock, UPSTART_PATH, UPSTART_INTERFACE, NULL);
492-
493- QString eventInfoStr = QString("('stopped', ['JOB=untrusted-helper', 'INSTANCE=backup-helper::%1'])").arg(DEKKO_APP_ID);
494- dbus_test_dbus_mock_object_emit_signal(
495- mock, objUpstart, "EventEmitted", G_VARIANT_TYPE("(sas)"),
496- g_variant_new_parsed(
497- eventInfoStr.toLocal8Bit().data()),
498- NULL);
499- g_usleep(100000);
500- while (g_main_pending())
501- {
502- g_main_iteration(TRUE);
503- }
504+ EXPECT_TRUE(waitUntilHelperFinishes(DEKKO_APP_ID, 15000, 2));
505
506 // check that the content of the file is the expected
507- EXPECT_TRUE(checkLastStorageFrameworkFile(user_dir));
508+ EXPECT_TRUE(checkStorageFrameworkFiles(QStringList{user_dir, user_dir_2}));
509 // let's leave things clean
510 EXPECT_TRUE(removeHelperMarkBeforeStarting());
511

Subscribers

People subscribed via source and target branches

to all changes: