storage_test_harness.h (7362B)
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_h__ 8 #define storage_test_harness_h__ 9 10 #include "gtest/gtest.h" 11 12 #include "prthread.h" 13 #include "nsAppDirectoryServiceDefs.h" 14 #include "nsComponentManagerUtils.h" 15 #include "nsDirectoryServiceDefs.h" 16 #include "nsDirectoryServiceUtils.h" 17 #include "nsServiceManagerUtils.h" 18 #include "nsIThread.h" 19 #include "nsThreadUtils.h" 20 #include "mozilla/ReentrantMonitor.h" 21 #include "mozilla/AutoSQLiteLifetime.h" 22 23 #include "mozIStorageService.h" 24 #include "mozIStorageConnection.h" 25 #include "mozIStorageStatementCallback.h" 26 #include "mozIStorageCompletionCallback.h" 27 #include "mozIStorageBindingParamsArray.h" 28 #include "mozIStorageBindingParams.h" 29 #include "mozIStorageAsyncStatement.h" 30 #include "mozIStorageStatement.h" 31 #include "mozIStoragePendingStatement.h" 32 #include "mozIStorageError.h" 33 #include "nsIInterfaceRequestorUtils.h" 34 #include "nsIEventTarget.h" 35 36 #include "sqlite3.h" 37 38 #define do_check_true(aCondition) EXPECT_TRUE(aCondition) 39 40 #define do_check_false(aCondition) EXPECT_FALSE(aCondition) 41 42 #define do_check_success(aResult) do_check_true(NS_SUCCEEDED(aResult)) 43 44 #define do_check_eq(aExpected, aActual) do_check_true(aExpected == aActual) 45 46 #define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK) 47 48 already_AddRefed<mozIStorageService> getService(); 49 50 already_AddRefed<mozIStorageConnection> getMemoryDatabase(); 51 52 already_AddRefed<mozIStorageConnection> getDatabase( 53 nsIFile* aDBFile = nullptr, 54 uint32_t aConnectionFlags = mozIStorageService::CONNECTION_DEFAULT); 55 56 class AsyncStatementSpinner : public mozIStorageStatementCallback, 57 public mozIStorageCompletionCallback { 58 public: 59 NS_DECL_ISUPPORTS 60 NS_DECL_MOZISTORAGESTATEMENTCALLBACK 61 NS_DECL_MOZISTORAGECOMPLETIONCALLBACK 62 63 AsyncStatementSpinner(); 64 65 void SpinUntilCompleted(); 66 67 uint16_t completionReason; 68 69 protected: 70 virtual ~AsyncStatementSpinner() = default; 71 volatile bool mCompleted; 72 }; 73 74 class AsyncCompletionSpinner : public mozIStorageCompletionCallback { 75 public: 76 NS_DECL_ISUPPORTS 77 NS_DECL_MOZISTORAGECOMPLETIONCALLBACK 78 79 AsyncCompletionSpinner(); 80 81 void SpinUntilCompleted(); 82 83 nsresult mCompletionReason; 84 nsCOMPtr<nsISupports> mCompletionValue; 85 86 protected: 87 virtual ~AsyncCompletionSpinner() = default; 88 volatile bool mCompleted; 89 }; 90 91 #define NS_DECL_ASYNCSTATEMENTSPINNER \ 92 NS_IMETHOD HandleResult(mozIStorageResultSet* aResultSet) override; 93 94 //////////////////////////////////////////////////////////////////////////////// 95 //// Async Helpers 96 97 /** 98 * Execute an async statement, blocking the main thread until we get the 99 * callback completion notification. 100 */ 101 void blocking_async_execute(mozIStorageBaseStatement* stmt); 102 103 /** 104 * Invoke AsyncClose on the given connection, blocking the main thread until we 105 * get the completion notification. 106 */ 107 void blocking_async_close(mozIStorageConnection* db); 108 109 //////////////////////////////////////////////////////////////////////////////// 110 //// Mutex Watching 111 112 /** 113 * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on 114 * the caller (generally main) thread. We do this by decorating the sqlite 115 * mutex logic with our own code that checks what thread it is being invoked on 116 * and sets a flag if it is invoked on the main thread. We are able to easily 117 * decorate the SQLite mutex logic because SQLite allows us to retrieve the 118 * current function pointers being used and then provide a new set. 119 */ 120 121 extern sqlite3_mutex_methods orig_mutex_methods; 122 extern sqlite3_mutex_methods wrapped_mutex_methods; 123 124 extern bool mutex_used_on_watched_thread; 125 extern PRThread* watched_thread; 126 /** 127 * Ugly hack to let us figure out what a connection's async thread is. If we 128 * were MOZILLA_INTERNAL_API and linked as such we could just include 129 * mozStorageConnection.h and just ask Connection directly. But that turns out 130 * poorly. 131 * 132 * When the thread a mutex is invoked on isn't watched_thread we save it to this 133 * variable. 134 */ 135 extern nsIThread* last_non_watched_thread; 136 137 /** 138 * Set a flag if the mutex is used on the thread we are watching, but always 139 * call the real mutex function. 140 */ 141 extern "C" void wrapped_MutexEnter(sqlite3_mutex* mutex); 142 143 extern "C" int wrapped_MutexTry(sqlite3_mutex* mutex); 144 145 class HookSqliteMutex { 146 public: 147 HookSqliteMutex() { 148 // We need to initialize and teardown SQLite to get it to set up the 149 // default mutex handlers for us so we can steal them and wrap them. 150 do_check_ok(sqlite3_initialize()); 151 do_check_ok(sqlite3_shutdown()); 152 do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods)); 153 do_check_ok( 154 ::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods)); 155 wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter; 156 wrapped_mutex_methods.xMutexTry = wrapped_MutexTry; 157 do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods)); 158 } 159 160 ~HookSqliteMutex() { 161 do_check_ok(sqlite3_shutdown()); 162 do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &orig_mutex_methods)); 163 mozilla::AutoSQLiteLifetime::Init(); 164 int rc = mozilla::AutoSQLiteLifetime::getInitResult(); 165 MOZ_RELEASE_ASSERT(rc == SQLITE_OK); 166 } 167 }; 168 169 /** 170 * Call to clear the watch state and to set the watching against this thread. 171 * 172 * Check |mutex_used_on_watched_thread| to see if the mutex has fired since 173 * this method was last called. Since we're talking about the current thread, 174 * there are no race issues to be concerned about 175 */ 176 void watch_for_mutex_use_on_this_thread(); 177 178 //////////////////////////////////////////////////////////////////////////////// 179 //// Thread Wedgers 180 181 /** 182 * A runnable that blocks until code on another thread invokes its unwedge 183 * method. By dispatching this to a thread you can ensure that no subsequent 184 * runnables dispatched to the thread will execute until you invoke unwedge. 185 * 186 * The wedger is self-dispatching, just construct it with its target. 187 */ 188 class ThreadWedger : public mozilla::Runnable { 189 public: 190 explicit ThreadWedger(nsIEventTarget* aTarget) 191 : mozilla::Runnable("ThreadWedger"), 192 mReentrantMonitor("thread wedger"), 193 unwedged(false) { 194 aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL); 195 } 196 197 NS_IMETHOD Run() override { 198 mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor); 199 200 if (!unwedged) automon.Wait(); 201 202 return NS_OK; 203 } 204 205 void unwedge() { 206 mozilla::ReentrantMonitorAutoEnter automon(mReentrantMonitor); 207 unwedged = true; 208 automon.Notify(); 209 } 210 211 private: 212 mozilla::ReentrantMonitor mReentrantMonitor MOZ_UNANNOTATED; 213 bool unwedged; 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 226 #endif // storage_test_harness_h__