Merge lp:~stolowski/unity-scope-click/fix-transaction-error into lp:unity-scope-click/devel

Proposed by Paweł Stołowski
Status: Merged
Approved by: Alejandro J. Cura
Approved revision: 345
Merged at revision: 341
Proposed branch: lp:~stolowski/unity-scope-click/fix-transaction-error
Merge into: lp:unity-scope-click/devel
Diff against target: 330 lines (+193/-7)
3 files modified
libclickscope/click/departments-db.cpp (+32/-4)
libclickscope/click/departments-db.h (+1/-1)
libclickscope/tests/test_departments-db.cpp (+160/-2)
To merge this branch: bzr merge lp:~stolowski/unity-scope-click/fix-transaction-error
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
dobey (community) Approve
Alejandro J. Cura (community) Approve
Review via email: mp+227059@code.launchpad.net

Commit message

Use write-ahead-log pragma of sqlite to fix transaction errors. Throw if transaction fails. Explicitly finish every query.

Description of the change

Use write-ahead-log pragma of sqlite to fix transaction errors. Throw if transaction fails. Explicitly finish every query.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
dobey (dobey) wrote :

Are there tests that go along with this code? Should they have tests to ensure that finish() is called in all the required cases, to avoid regression?

review: Needs Information
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Is there a way to add some kind of testing to this branch? Ideally, it would be a test that makes a transaction fail, and that gets fixed with this branch.

review: Needs Fixing
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 :

> Is there a way to add some kind of testing to this branch? Ideally, it would
> be a test that makes a transaction fail, and that gets fixed with this branch.

Ok, I've added two tests, one of them is a "stress test" that makes either reading or writing fail without WAL.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alejandro J. Cura (alecu) wrote :

Branch looks good, and the WAL fix seems reasonable.
The stress test is not so slow in my desktop machine (~5 seconds), but it might be due to the SSD.
In any case, I think we should consider moving that test into the integration tests if it gets slow for other developers.

review: Approve
Revision history for this message
dobey (dobey) :
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) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'libclickscope/click/departments-db.cpp'
--- libclickscope/click/departments-db.cpp 2014-07-10 08:57:59 +0000
+++ libclickscope/click/departments-db.cpp 2014-07-17 11:09:40 +0000
@@ -99,6 +99,11 @@
99 // them for now.99 // them for now.
100 // query.exec("PRAGMA foreign_keys = ON");100 // query.exec("PRAGMA foreign_keys = ON");
101101
102 // activate write-ahead logging, see http://sqlite.org/wal.html to avoid transaction errors with concurrent reads and writes
103 query.exec("PRAGMA journal_mode=WAL");
104
105 db_.transaction();
106
102 // package id -> department id mapping table107 // package id -> department id mapping table
103 if (!query.exec("CREATE TABLE IF NOT EXISTS pkgmap (pkgid TEXT, deptid TEXT, CONSTRAINT pkey PRIMARY KEY (pkgid, deptid))"))108 if (!query.exec("CREATE TABLE IF NOT EXISTS pkgmap (pkgid TEXT, deptid TEXT, CONSTRAINT pkey PRIMARY KEY (pkgid, deptid))"))
104 {109 {
@@ -130,6 +135,11 @@
130 {135 {
131 report_db_error(query.lastError(), "Failed to create depts_v view");136 report_db_error(query.lastError(), "Failed to create depts_v view");
132 }137 }
138
139 if (!db_.commit())
140 {
141 report_db_error(db_.lastError(), "Failed to commit init transaction");
142 }
133}143}
134144
135void DepartmentsDb::report_db_error(const QSqlError& error, const std::string& message)145void DepartmentsDb::report_db_error(const QSqlError& error, const std::string& message)
@@ -151,9 +161,12 @@
151161
152 if (select_dept_name_->next())162 if (select_dept_name_->next())
153 {163 {
154 return select_dept_name_->value(0).toString().toStdString();164 auto const res = select_dept_name_->value(0).toString().toStdString();
165 select_dept_name_->finish();
166 return res;
155 }167 }
156 }168 }
169 select_dept_name_->finish();
157 throw std::logic_error("No name for department " + department_id);170 throw std::logic_error("No name for department " + department_id);
158}171}
159172
@@ -166,9 +179,12 @@
166 }179 }
167 if (!select_parent_dept_->next())180 if (!select_parent_dept_->next())
168 {181 {
182 select_dept_name_->finish();
169 throw std::logic_error("Unknown department '" + department_id + "'");183 throw std::logic_error("Unknown department '" + department_id + "'");
170 }184 }
171 return select_parent_dept_->value(0).toString().toStdString();185 auto const res = select_parent_dept_->value(0).toString().toStdString();
186 select_parent_dept_->finish();
187 return res;
172}188}
173189
174std::list<DepartmentsDb::DepartmentInfo> DepartmentsDb::get_children_departments(const std::string& department_id)190std::list<DepartmentsDb::DepartmentInfo> DepartmentsDb::get_children_departments(const std::string& department_id)
@@ -187,6 +203,7 @@
187 depts.push_back(inf);203 depts.push_back(inf);
188 }204 }
189205
206 select_children_depts_->finish();
190 return depts;207 return depts;
191}208}
192209
@@ -203,6 +220,7 @@
203 {220 {
204 pkgs.insert(query->value(0).toString().toStdString());221 pkgs.insert(query->value(0).toString().toStdString());
205 }222 }
223 query->finish();
206 return pkgs;224 return pkgs;
207}225}
208226
@@ -226,6 +244,7 @@
226 // delete package mapping first from any departments244 // delete package mapping first from any departments
227 delete_pkgmap_query_->bindValue(":pkgid", QVariant(QString::fromStdString(package_id)));245 delete_pkgmap_query_->bindValue(":pkgid", QVariant(QString::fromStdString(package_id)));
228 delete_pkgmap_query_->exec();246 delete_pkgmap_query_->exec();
247 delete_pkgmap_query_->finish();
229248
230 insert_pkgmap_query_->bindValue(":pkgid", QVariant(QString::fromStdString(package_id)));249 insert_pkgmap_query_->bindValue(":pkgid", QVariant(QString::fromStdString(package_id)));
231 insert_pkgmap_query_->bindValue(":deptid", QVariant(QString::fromStdString(department_id)));250 insert_pkgmap_query_->bindValue(":deptid", QVariant(QString::fromStdString(department_id)));
@@ -238,9 +257,12 @@
238 report_db_error(insert_pkgmap_query_->lastError(), "Failed to insert into pkgmap");257 report_db_error(insert_pkgmap_query_->lastError(), "Failed to insert into pkgmap");
239 }258 }
240259
260 insert_pkgmap_query_->finish();
261
241 if (!db_.commit())262 if (!db_.commit())
242 {263 {
243 std::cerr << "Failed to commit transaction" << std::endl;264 db_.rollback();
265 report_db_error(db_.lastError(), "Failed to commit transaction in store_package_mapping");
244 }266 }
245}267}
246268
@@ -262,6 +284,7 @@
262 {284 {
263 report_db_error(insert_dept_id_query_->lastError(), "Failed to insert into depts");285 report_db_error(insert_dept_id_query_->lastError(), "Failed to insert into depts");
264 }286 }
287 insert_dept_id_query_->finish();
265}288}
266289
267void DepartmentsDb::store_department_name(const std::string& department_id, const std::string& locale, const std::string& name)290void DepartmentsDb::store_department_name(const std::string& department_id, const std::string& locale, const std::string& name)
@@ -284,6 +307,7 @@
284 {307 {
285 report_db_error(insert_dept_name_query_->lastError(), "Failed to insert into deptnames");308 report_db_error(insert_dept_name_query_->lastError(), "Failed to insert into deptnames");
286 }309 }
310 insert_dept_name_query_->finish();
287}311}
288312
289int DepartmentsDb::department_mapping_count() const313int DepartmentsDb::department_mapping_count() const
@@ -350,11 +374,15 @@
350 report_db_error(delete_depts_query_->lastError(), "Failed to delete from depts");374 report_db_error(delete_depts_query_->lastError(), "Failed to delete from depts");
351 }375 }
352376
377 delete_deptnames_query_->finish();
378 delete_depts_query_->finish();
379
353 store_departments_(depts, locale);380 store_departments_(depts, locale);
354381
355 if (!db_.commit())382 if (!db_.commit())
356 {383 {
357 std::cerr << "Failed to commit transaction" << std::endl;384 db_.rollback();
385 report_db_error(db_.lastError(), "Failed to commit transaction in store_departments");
358 }386 }
359}387}
360388
361389
=== modified file 'libclickscope/click/departments-db.h'
--- libclickscope/click/departments-db.h 2014-07-10 08:57:59 +0000
+++ libclickscope/click/departments-db.h 2014-07-17 11:09:40 +0000
@@ -82,7 +82,7 @@
8282
83 static std::unique_ptr<DepartmentsDb> create_db();83 static std::unique_ptr<DepartmentsDb> create_db();
8484
85private:85protected:
86 void init_db(const std::string& name);86 void init_db(const std::string& name);
87 void store_departments_(const click::DepartmentList& depts, const std::string& locale);87 void store_departments_(const click::DepartmentList& depts, const std::string& locale);
88 static void report_db_error(const QSqlError& error, const std::string& message);88 static void report_db_error(const QSqlError& error, const std::string& message);
8989
=== modified file 'libclickscope/tests/test_departments-db.cpp'
--- libclickscope/tests/test_departments-db.cpp 2014-07-02 13:14:36 +0000
+++ libclickscope/tests/test_departments-db.cpp 2014-07-17 11:09:40 +0000
@@ -36,6 +36,30 @@
3636
37using namespace click;37using namespace click;
3838
39class DepartmentsDbCheck : public DepartmentsDb
40{
41public:
42 DepartmentsDbCheck(const std::string& name)
43 : DepartmentsDb(name)
44 {
45 }
46
47 void check_sql_queries_finished()
48 {
49 EXPECT_FALSE(delete_pkgmap_query_->isActive());
50 EXPECT_FALSE(delete_depts_query_->isActive());
51 EXPECT_FALSE(delete_deptnames_query_->isActive());
52 EXPECT_FALSE(insert_pkgmap_query_->isActive());
53 EXPECT_FALSE(insert_dept_id_query_->isActive());
54 EXPECT_FALSE(insert_dept_name_query_->isActive());
55 EXPECT_FALSE(select_pkgs_by_dept_->isActive());
56 EXPECT_FALSE(select_pkgs_by_dept_recursive_->isActive());
57 EXPECT_FALSE(select_parent_dept_->isActive());
58 EXPECT_FALSE(select_children_depts_->isActive());
59 EXPECT_FALSE(select_dept_name_->isActive());
60 }
61};
62
39class DepartmentsDbTest: public ::testing::Test63class DepartmentsDbTest: public ::testing::Test
40{64{
41public:65public:
@@ -43,7 +67,7 @@
4367
44 void SetUp() override68 void SetUp() override
45 {69 {
46 db.reset(new DepartmentsDb(db_path));70 db.reset(new DepartmentsDbCheck(db_path));
47 db->store_department_name("tools", "", "Tools");71 db->store_department_name("tools", "", "Tools");
48 db->store_department_name("office", "", "Office");72 db->store_department_name("office", "", "Office");
4973
@@ -72,7 +96,18 @@
72 }96 }
7397
74protected:98protected:
75 std::unique_ptr<DepartmentsDb> db;99 std::unique_ptr<DepartmentsDbCheck> db;
100};
101
102class DepartmentsDbConcurrencyTest: public ::testing::Test
103{
104public:
105 const std::string db_path = TEST_DIR "/departments-db-test2.sqlite";
106
107 void TearDown() override
108 {
109 unlink(db_path.c_str());
110 }
76};111};
77112
78TEST_F(DepartmentsDbTest, testDepartmentNameLookup)113TEST_F(DepartmentsDbTest, testDepartmentNameLookup)
@@ -224,3 +259,126 @@
224 EXPECT_EQ(4u, db->package_count());259 EXPECT_EQ(4u, db->package_count());
225}260}
226261
262TEST_F(DepartmentsDbTest, testSqlQueriesFinished)
263{
264 db->get_department_name("games", {"en_EN", ""});
265 db->check_sql_queries_finished();
266
267 db->get_parent_department_id("rpg");
268 db->check_sql_queries_finished();
269
270 db->get_packages_for_department("rpg", false);
271 db->check_sql_queries_finished();
272
273 db->get_packages_for_department("rpg", true);
274 db->check_sql_queries_finished();
275
276 db->get_children_departments("games");
277 db->check_sql_queries_finished();
278
279 db->department_name_count();
280 db->check_sql_queries_finished();
281
282 db->department_mapping_count();
283 db->check_sql_queries_finished();
284
285 db->package_count();
286 db->check_sql_queries_finished();
287
288 db->store_package_mapping("game2", "fps");
289 db->check_sql_queries_finished();
290
291 db->store_department_name("games", "pl_PL", "Gry");
292 db->check_sql_queries_finished();
293
294 db->store_department_mapping("office", "tools");
295 db->check_sql_queries_finished();
296}
297
298// Keep the numbers below at a reasonable level; it takes around 20 seconds
299// to run this test on a i7-2620M with the default values.
300const int NUM_OF_WRITE_OPS = 50;
301const int NUM_OF_READ_OPS = 100;
302
303void populate_departments(DepartmentsDb *db, int repeat_count)
304{
305 click::DepartmentList depts;
306 // generate departments with one subdepartment
307 for (int i = 0; i < 20; i++)
308 {
309 auto const id = std::to_string(i);
310 auto parent = std::make_shared<click::Department>(id, "Department " + id, "href", true);
311 parent->set_subdepartments({std::make_shared<click::Department>(id + "sub", "Subdepartment of " + id, "href", false)});
312 depts.push_back(parent);
313 }
314
315 for (int i = 0; i < repeat_count; i++)
316 {
317 ASSERT_NO_THROW(db->store_departments(depts, ""));
318
319 // generate apps
320 for (int j = 0; j < 50; j++)
321 {
322 auto const id = std::to_string(j);
323 ASSERT_NO_THROW(db->store_package_mapping("app" + id, id));
324 }
325 }
326}
327
328TEST_F(DepartmentsDbConcurrencyTest, ConcurrentReadWrite)
329{
330 // populate the db initially to make sure reader doesn't fail if it's faster than writer
331 {
332 DepartmentsDb db(db_path);
333 populate_departments(&db, 1);
334 }
335
336 pid_t writer_pid = fork();
337 if (writer_pid < 0)
338 {
339 FAIL();
340 }
341 else if (writer_pid == 0) // writer child process
342 {
343 SCOPED_TRACE("writer");
344 std::unique_ptr<DepartmentsDb> db;
345 ASSERT_NO_THROW(db.reset(new DepartmentsDb(db_path)));
346
347 populate_departments(db.get(), NUM_OF_WRITE_OPS);
348
349 exit(0);
350 }
351 else // parent process
352 {
353 pid_t reader_pid = fork();
354 if (reader_pid < 0)
355 {
356 FAIL();
357 }
358 else if (reader_pid == 0) // reader child process
359 {
360 SCOPED_TRACE("reader");
361 std::unique_ptr<DepartmentsDb> db;
362 ASSERT_NO_THROW(db.reset(new DepartmentsDb(db_path)));
363
364 for (int i = 0; i < NUM_OF_READ_OPS; i++)
365 {
366 ASSERT_NO_THROW(db->get_department_name("1", {""}));
367 ASSERT_NO_THROW(db->get_parent_department_id("1"));
368 ASSERT_NO_THROW(db->get_packages_for_department("1", false));
369 ASSERT_NO_THROW(db->get_packages_for_department("1", true));
370 ASSERT_NO_THROW(db->get_children_departments(""));
371 ASSERT_NO_THROW(db->department_name_count());
372 ASSERT_NO_THROW(db->department_mapping_count());
373 ASSERT_NO_THROW(db->package_count());
374 }
375 exit(0);
376 }
377 else // parent process
378 {
379 wait(nullptr);
380 wait(nullptr);
381 }
382 }
383}
384

Subscribers

People subscribed via source and target branches

to all changes: