storage_test_harness.cpp (7964B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: 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 #ifndef storage_test_harness_ 8 #define storage_test_harness_ 9 10 #include "storage_test_harness.h" 11 12 already_AddRefed<mozIStorageService> getService() { 13 nsCOMPtr<mozIStorageService> ss = 14 do_CreateInstance("@mozilla.org/storage/service;1"); 15 do_check_true(ss); 16 return ss.forget(); 17 } 18 19 already_AddRefed<mozIStorageConnection> getMemoryDatabase() { 20 nsCOMPtr<mozIStorageService> ss = getService(); 21 nsCOMPtr<mozIStorageConnection> conn; 22 nsresult rv = ss->OpenSpecialDatabase( 23 kMozStorageMemoryStorageKey, VoidCString(), 24 mozIStorageService::CONNECTION_DEFAULT, getter_AddRefs(conn)); 25 do_check_success(rv); 26 return conn.forget(); 27 } 28 29 already_AddRefed<mozIStorageConnection> getDatabase(nsIFile* aDBFile, 30 uint32_t aConnectionFlags) { 31 nsCOMPtr<nsIFile> dbFile; 32 nsresult rv; 33 if (!aDBFile) { 34 MOZ_RELEASE_ASSERT(NS_IsMainThread(), "Can't get tmp dir off mainthread."); 35 (void)NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, 36 getter_AddRefs(dbFile)); 37 NS_ASSERTION(dbFile, "The directory doesn't exists?!"); 38 39 rv = dbFile->Append(u"storage_test_db.sqlite"_ns); 40 do_check_success(rv); 41 } else { 42 dbFile = aDBFile; 43 } 44 45 nsCOMPtr<mozIStorageService> ss = getService(); 46 nsCOMPtr<mozIStorageConnection> conn; 47 rv = ss->OpenDatabase(dbFile, aConnectionFlags, getter_AddRefs(conn)); 48 do_check_success(rv); 49 return conn.forget(); 50 } 51 52 NS_IMPL_ISUPPORTS(AsyncStatementSpinner, mozIStorageStatementCallback, 53 mozIStorageCompletionCallback) 54 55 AsyncStatementSpinner::AsyncStatementSpinner() 56 : completionReason(0), mCompleted(false) {} 57 58 NS_IMETHODIMP 59 AsyncStatementSpinner::HandleResult(mozIStorageResultSet* aResultSet) { 60 return NS_OK; 61 } 62 63 NS_IMETHODIMP 64 AsyncStatementSpinner::HandleError(mozIStorageError* aError) { 65 int32_t result; 66 nsresult rv = aError->GetResult(&result); 67 NS_ENSURE_SUCCESS(rv, rv); 68 nsAutoCString message; 69 rv = aError->GetMessage(message); 70 NS_ENSURE_SUCCESS(rv, rv); 71 72 nsAutoCString warnMsg; 73 warnMsg.AppendLiteral( 74 "An error occurred while executing an async statement: "); 75 warnMsg.AppendInt(result); 76 warnMsg.Append(' '); 77 warnMsg.Append(message); 78 NS_WARNING(warnMsg.get()); 79 80 return NS_OK; 81 } 82 83 NS_IMETHODIMP 84 AsyncStatementSpinner::HandleCompletion(uint16_t aReason) { 85 completionReason = aReason; 86 mCompleted = true; 87 return NS_OK; 88 } 89 90 NS_IMETHODIMP 91 AsyncStatementSpinner::Complete(nsresult, nsISupports*) { 92 mCompleted = true; 93 return NS_OK; 94 } 95 96 void AsyncStatementSpinner::SpinUntilCompleted() { 97 nsCOMPtr<nsIThread> thread(::do_GetCurrentThread()); 98 nsresult rv = NS_OK; 99 bool processed = true; 100 while (!mCompleted && NS_SUCCEEDED(rv)) { 101 rv = thread->ProcessNextEvent(true, &processed); 102 } 103 } 104 105 #define NS_DECL_ASYNCSTATEMENTSPINNER \ 106 NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override; 107 108 NS_IMPL_ISUPPORTS(AsyncCompletionSpinner, mozIStorageCompletionCallback) 109 110 AsyncCompletionSpinner::AsyncCompletionSpinner() 111 : mCompletionReason(NS_OK), mCompleted(false) {} 112 113 NS_IMETHODIMP 114 AsyncCompletionSpinner::Complete(nsresult reason, nsISupports* value) { 115 mCompleted = true; 116 mCompletionReason = reason; 117 mCompletionValue = value; 118 return NS_OK; 119 } 120 121 void AsyncCompletionSpinner::SpinUntilCompleted() { 122 nsCOMPtr<nsIThread> thread(::do_GetCurrentThread()); 123 nsresult rv = NS_OK; 124 bool processed = true; 125 while (!mCompleted && NS_SUCCEEDED(rv)) { 126 rv = thread->ProcessNextEvent(true, &processed); 127 } 128 } 129 130 //////////////////////////////////////////////////////////////////////////////// 131 //// Async Helpers 132 133 /** 134 * Execute an async statement, blocking the main thread until we get the 135 * callback completion notification. 136 */ 137 void blocking_async_execute(mozIStorageBaseStatement* stmt) { 138 RefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner()); 139 140 nsCOMPtr<mozIStoragePendingStatement> pendy; 141 (void)stmt->ExecuteAsync(spinner, getter_AddRefs(pendy)); 142 spinner->SpinUntilCompleted(); 143 } 144 145 /** 146 * Invoke AsyncClose on the given connection, blocking the main thread until we 147 * get the completion notification. 148 */ 149 void blocking_async_close(mozIStorageConnection* db) { 150 RefPtr<AsyncStatementSpinner> spinner(new AsyncStatementSpinner()); 151 152 db->AsyncClose(spinner); 153 spinner->SpinUntilCompleted(); 154 } 155 156 //////////////////////////////////////////////////////////////////////////////// 157 //// Mutex Watching 158 159 /** 160 * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on 161 * the caller (generally main) thread. We do this by decorating the sqlite 162 * mutex logic with our own code that checks what thread it is being invoked on 163 * and sets a flag if it is invoked on the main thread. We are able to easily 164 * decorate the SQLite mutex logic because SQLite allows us to retrieve the 165 * current function pointers being used and then provide a new set. 166 */ 167 168 sqlite3_mutex_methods orig_mutex_methods; 169 sqlite3_mutex_methods wrapped_mutex_methods; 170 171 bool mutex_used_on_watched_thread = false; 172 PRThread* watched_thread = nullptr; 173 /** 174 * Ugly hack to let us figure out what a connection's async thread is. If we 175 * were MOZILLA_INTERNAL_API and linked as such we could just include 176 * mozStorageConnection.h and just ask Connection directly. But that turns out 177 * poorly. 178 * 179 * When the thread a mutex is invoked on isn't watched_thread we save it to this 180 * variable. 181 */ 182 nsIThread* last_non_watched_thread = nullptr; 183 184 /** 185 * Set a flag if the mutex is used on the thread we are watching, but always 186 * call the real mutex function. 187 */ 188 extern "C" void wrapped_MutexEnter(sqlite3_mutex* mutex) { 189 if (PR_GetCurrentThread() == watched_thread) { 190 mutex_used_on_watched_thread = true; 191 } else { 192 last_non_watched_thread = NS_GetCurrentThread(); 193 } 194 orig_mutex_methods.xMutexEnter(mutex); 195 } 196 197 extern "C" int wrapped_MutexTry(sqlite3_mutex* mutex) { 198 if (::PR_GetCurrentThread() == watched_thread) { 199 mutex_used_on_watched_thread = true; 200 } 201 return orig_mutex_methods.xMutexTry(mutex); 202 } 203 204 /** 205 * Call to clear the watch state and to set the watching against this thread. 206 * 207 * Check |mutex_used_on_watched_thread| to see if the mutex has fired since 208 * this method was last called. Since we're talking about the current thread, 209 * there are no race issues to be concerned about 210 */ 211 void watch_for_mutex_use_on_this_thread() { 212 watched_thread = ::PR_GetCurrentThread(); 213 mutex_used_on_watched_thread = false; 214 } 215 216 //////////////////////////////////////////////////////////////////////////////// 217 //// Async Helpers 218 219 /** 220 * A horrible hack to figure out what the connection's async thread is. By 221 * creating a statement and async dispatching we can tell from the mutex who 222 * is the async thread, PRThread style. Then we map that to an nsIThread. 223 */ 224 already_AddRefed<nsIThread> get_conn_async_thread(mozIStorageConnection* db) { 225 // Make sure we are tracking the current thread as the watched thread 226 watch_for_mutex_use_on_this_thread(); 227 228 // - statement with nothing to bind 229 nsCOMPtr<mozIStorageAsyncStatement> stmt; 230 db->CreateAsyncStatement("SELECT 1"_ns, getter_AddRefs(stmt)); 231 blocking_async_execute(stmt); 232 stmt->Finalize(); 233 234 nsCOMPtr<nsIThread> asyncThread = last_non_watched_thread; 235 236 // Additionally, check that the thread we get as the background thread is the 237 // same one as the one we report from getInterface. 238 nsCOMPtr<nsIEventTarget> target = do_GetInterface(db); 239 nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target); 240 do_check_eq(allegedAsyncThread, asyncThread); 241 return asyncThread.forget(); 242 } 243 244 #endif // storage_test_harness_h__