tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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__