1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim set:sw=2 ts=2 et lcs=trail\:.,tab\:>~ : */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "storage_test_harness.h"
8
9 #include "mozilla/ReentrantMonitor.h"
10 #include "nsThreadUtils.h"
11 #include "mozIStorageStatement.h"
12
13 /**
14 * This file tests that our implementation around sqlite3_unlock_notify works
15 * as expected.
16 */
17
18 ////////////////////////////////////////////////////////////////////////////////
19 //// Helpers
20
21 enum State { STARTING, WRITE_LOCK, READ_LOCK, TEST_DONE };
22
23 class DatabaseLocker : public mozilla::Runnable {
24 public:
DatabaseLocker(const char * aSQL,nsIFile * aDBFile=nullptr)25 explicit DatabaseLocker(const char* aSQL, nsIFile* aDBFile = nullptr)
26 : mozilla::Runnable("DatabaseLocker"),
27 monitor("DatabaseLocker::monitor"),
28 mSQL(aSQL),
29 mState(STARTING),
30 mDBFile(aDBFile) {}
31
RunInBackground()32 void RunInBackground() {
33 (void)NS_NewNamedThread("DatabaseLocker", getter_AddRefs(mThread));
34 do_check_true(mThread);
35
36 do_check_success(mThread->Dispatch(this, NS_DISPATCH_NORMAL));
37 }
38
Shutdown()39 void Shutdown() {
40 if (mThread) {
41 mThread->Shutdown();
42 }
43 }
44
Run()45 NS_IMETHOD Run() override {
46 mozilla::ReentrantMonitorAutoEnter lock(monitor);
47
48 nsCOMPtr<mozIStorageConnection> db(getDatabase(mDBFile));
49
50 nsCString sql(mSQL);
51 nsCOMPtr<mozIStorageStatement> stmt;
52 do_check_success(db->CreateStatement(sql, getter_AddRefs(stmt)));
53
54 bool hasResult;
55 do_check_success(stmt->ExecuteStep(&hasResult));
56
57 Notify(WRITE_LOCK);
58 WaitFor(TEST_DONE);
59
60 return NS_OK;
61 }
62
WaitFor(State aState)63 void WaitFor(State aState) {
64 monitor.AssertCurrentThreadIn();
65 while (mState != aState) {
66 do_check_success(monitor.Wait());
67 }
68 }
69
Notify(State aState)70 void Notify(State aState) {
71 monitor.AssertCurrentThreadIn();
72 mState = aState;
73 do_check_success(monitor.Notify());
74 }
75
76 mozilla::ReentrantMonitor monitor;
77
78 protected:
79 nsCOMPtr<nsIThread> mThread;
80 const char* const mSQL;
81 State mState;
82 nsCOMPtr<nsIFile> mDBFile;
83 };
84
85 class DatabaseTester : public DatabaseLocker {
86 public:
DatabaseTester(mozIStorageConnection * aConnection,const char * aSQL)87 DatabaseTester(mozIStorageConnection* aConnection, const char* aSQL)
88 : DatabaseLocker(aSQL), mConnection(aConnection) {}
89
Run()90 NS_IMETHOD Run() override {
91 mozilla::ReentrantMonitorAutoEnter lock(monitor);
92 WaitFor(READ_LOCK);
93
94 nsCString sql(mSQL);
95 nsCOMPtr<mozIStorageStatement> stmt;
96 do_check_success(mConnection->CreateStatement(sql, getter_AddRefs(stmt)));
97
98 bool hasResult;
99 nsresult rv = stmt->ExecuteStep(&hasResult);
100 do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
101
102 // Finalize our statement and null out our connection before notifying to
103 // ensure that we close on the proper thread.
104 rv = stmt->Finalize();
105 do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
106 mConnection = nullptr;
107
108 Notify(TEST_DONE);
109
110 return NS_OK;
111 }
112
113 private:
114 nsCOMPtr<mozIStorageConnection> mConnection;
115 };
116
117 ////////////////////////////////////////////////////////////////////////////////
118 //// Test Functions
119
setup()120 void setup() {
121 nsCOMPtr<mozIStorageConnection> db(getDatabase());
122
123 // Create and populate a dummy table.
124 nsresult rv = db->ExecuteSimpleSQL(nsLiteralCString(
125 "CREATE TABLE test (id INTEGER PRIMARY KEY, data STRING)"));
126 do_check_success(rv);
127 rv = db->ExecuteSimpleSQL("INSERT INTO test (data) VALUES ('foo')"_ns);
128 do_check_success(rv);
129 rv = db->ExecuteSimpleSQL("INSERT INTO test (data) VALUES ('bar')"_ns);
130 do_check_success(rv);
131 rv =
132 db->ExecuteSimpleSQL("CREATE UNIQUE INDEX unique_data ON test (data)"_ns);
133 do_check_success(rv);
134 }
135
test_step_locked_does_not_block_main_thread()136 void test_step_locked_does_not_block_main_thread() {
137 nsCOMPtr<mozIStorageConnection> db(getDatabase());
138
139 // Need to prepare our statement ahead of time so we make sure to only test
140 // step and not prepare.
141 nsCOMPtr<mozIStorageStatement> stmt;
142 nsresult rv = db->CreateStatement(
143 "INSERT INTO test (data) VALUES ('test1')"_ns, getter_AddRefs(stmt));
144 do_check_success(rv);
145
146 nsCOMPtr<nsIFile> dbFile;
147 db->GetDatabaseFile(getter_AddRefs(dbFile));
148 RefPtr<DatabaseLocker> locker(
149 new DatabaseLocker("SELECT * FROM test", dbFile));
150 do_check_true(locker);
151 {
152 mozilla::ReentrantMonitorAutoEnter lock(locker->monitor);
153 locker->RunInBackground();
154
155 // Wait for the locker to notify us that it has locked the database
156 // properly.
157 locker->WaitFor(WRITE_LOCK);
158
159 bool hasResult;
160 rv = stmt->ExecuteStep(&hasResult);
161 do_check_eq(rv, NS_ERROR_FILE_IS_LOCKED);
162
163 locker->Notify(TEST_DONE);
164 }
165 locker->Shutdown();
166 }
167
test_drop_index_does_not_loop()168 void test_drop_index_does_not_loop() {
169 nsCOMPtr<mozIStorageConnection> db(getDatabase());
170
171 // Need to prepare our statement ahead of time so we make sure to only test
172 // step and not prepare.
173 nsCOMPtr<mozIStorageStatement> stmt;
174 nsresult rv =
175 db->CreateStatement("SELECT * FROM test"_ns, getter_AddRefs(stmt));
176 do_check_success(rv);
177
178 RefPtr<DatabaseTester> tester =
179 new DatabaseTester(db, "DROP INDEX unique_data");
180 do_check_true(tester);
181 {
182 mozilla::ReentrantMonitorAutoEnter lock(tester->monitor);
183 tester->RunInBackground();
184
185 // Hold a read lock on the database, and then let the tester try to execute.
186 bool hasResult;
187 rv = stmt->ExecuteStep(&hasResult);
188 do_check_success(rv);
189 do_check_true(hasResult);
190 tester->Notify(READ_LOCK);
191
192 // Make sure the tester finishes its test before we move on.
193 tester->WaitFor(TEST_DONE);
194 }
195 tester->Shutdown();
196 }
197
test_drop_table_does_not_loop()198 void test_drop_table_does_not_loop() {
199 nsCOMPtr<mozIStorageConnection> db(getDatabase());
200
201 // Need to prepare our statement ahead of time so we make sure to only test
202 // step and not prepare.
203 nsCOMPtr<mozIStorageStatement> stmt;
204 nsresult rv =
205 db->CreateStatement("SELECT * FROM test"_ns, getter_AddRefs(stmt));
206 do_check_success(rv);
207
208 RefPtr<DatabaseTester> tester(new DatabaseTester(db, "DROP TABLE test"));
209 do_check_true(tester);
210 {
211 mozilla::ReentrantMonitorAutoEnter lock(tester->monitor);
212 tester->RunInBackground();
213
214 // Hold a read lock on the database, and then let the tester try to execute.
215 bool hasResult;
216 rv = stmt->ExecuteStep(&hasResult);
217 do_check_success(rv);
218 do_check_true(hasResult);
219 tester->Notify(READ_LOCK);
220
221 // Make sure the tester finishes its test before we move on.
222 tester->WaitFor(TEST_DONE);
223 }
224 tester->Shutdown();
225 }
226
TEST(storage_unlock_notify,Test)227 TEST(storage_unlock_notify, Test)
228 {
229 // These must execute in order.
230 setup();
231 test_step_locked_does_not_block_main_thread();
232 test_drop_index_does_not_loop();
233 test_drop_table_does_not_loop();
234 }
235