test_unlock_notify.cpp (6744B)
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: 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 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 39 void Shutdown() { 40 if (mThread) { 41 mThread->Shutdown(); 42 } 43 } 44 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 63 void WaitFor(State aState) { 64 monitor.AssertCurrentThreadIn(); 65 while (mState != aState) { 66 do_check_success(monitor.Wait()); 67 } 68 } 69 70 void Notify(State aState) { 71 monitor.AssertCurrentThreadIn(); 72 mState = aState; 73 do_check_success(monitor.Notify()); 74 } 75 76 mozilla::ReentrantMonitor monitor MOZ_UNANNOTATED; 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: 87 DatabaseTester(mozIStorageConnection* aConnection, const char* aSQL) 88 : DatabaseLocker(aSQL), mConnection(aConnection) {} 89 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 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 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 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 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 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 }