storage/test/test_true_async.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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/. */
     7 #include "storage_test_harness.h"
     8 #include "prthread.h"
     9 #include "nsIEventTarget.h"
    10 #include "nsIInterfaceRequestorUtils.h"
    12 #include "sqlite3.h"
    14 #include "mozilla/ReentrantMonitor.h"
    16 using mozilla::ReentrantMonitor;
    17 using mozilla::ReentrantMonitorAutoEnter;
    19 /**
    20  * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on
    21  * the caller (generally main) thread.  We do this by decorating the sqlite
    22  * mutex logic with our own code that checks what thread it is being invoked on
    23  * and sets a flag if it is invoked on the main thread.  We are able to easily
    24  * decorate the SQLite mutex logic because SQLite allows us to retrieve the
    25  * current function pointers being used and then provide a new set.
    26  */
    28 /* ===== Mutex Watching ===== */
    30 sqlite3_mutex_methods orig_mutex_methods;
    31 sqlite3_mutex_methods wrapped_mutex_methods;
    33 bool mutex_used_on_watched_thread = false;
    34 PRThread *watched_thread = nullptr;
    35 /**
    36  * Ugly hack to let us figure out what a connection's async thread is.  If we
    37  * were MOZILLA_INTERNAL_API and linked as such we could just include
    38  * mozStorageConnection.h and just ask Connection directly.  But that turns out
    39  * poorly.
    40  *
    41  * When the thread a mutex is invoked on isn't watched_thread we save it to this
    42  * variable.
    43  */
    44 PRThread *last_non_watched_thread = nullptr;
    46 /**
    47  * Set a flag if the mutex is used on the thread we are watching, but always
    48  * call the real mutex function.
    49  */
    50 extern "C" void wrapped_MutexEnter(sqlite3_mutex *mutex)
    51 {
    52   PRThread *curThread = ::PR_GetCurrentThread();
    53   if (curThread == watched_thread)
    54     mutex_used_on_watched_thread = true;
    55   else
    56     last_non_watched_thread = curThread;
    57   orig_mutex_methods.xMutexEnter(mutex);
    58 }
    60 extern "C" int wrapped_MutexTry(sqlite3_mutex *mutex)
    61 {
    62   if (::PR_GetCurrentThread() == watched_thread)
    63     mutex_used_on_watched_thread = true;
    64   return orig_mutex_methods.xMutexTry(mutex);
    65 }
    68 #define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
    70 void hook_sqlite_mutex()
    71 {
    72   // We need to initialize and teardown SQLite to get it to set up the
    73   // default mutex handlers for us so we can steal them and wrap them.
    74   do_check_ok(sqlite3_initialize());
    75   do_check_ok(sqlite3_shutdown());
    76   do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods));
    77   do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods));
    78   wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter;
    79   wrapped_mutex_methods.xMutexTry = wrapped_MutexTry;
    80   do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods));
    81 }
    83 /**
    84  * Call to clear the watch state and to set the watching against this thread.
    85  *
    86  * Check |mutex_used_on_watched_thread| to see if the mutex has fired since
    87  * this method was last called.  Since we're talking about the current thread,
    88  * there are no race issues to be concerned about
    89  */
    90 void watch_for_mutex_use_on_this_thread()
    91 {
    92   watched_thread = ::PR_GetCurrentThread();
    93   mutex_used_on_watched_thread = false;
    94 }
    97 ////////////////////////////////////////////////////////////////////////////////
    98 //// Thread Wedgers
   100 /**
   101  * A runnable that blocks until code on another thread invokes its unwedge
   102  * method.  By dispatching this to a thread you can ensure that no subsequent
   103  * runnables dispatched to the thread will execute until you invoke unwedge.
   104  *
   105  * The wedger is self-dispatching, just construct it with its target.
   106  */
   107 class ThreadWedger : public nsRunnable
   108 {
   109 public:
   110   ThreadWedger(nsIEventTarget *aTarget)
   111   : mReentrantMonitor("thread wedger")
   112   , unwedged(false)
   113   {
   114     aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL);
   115   }
   117   NS_IMETHOD Run()
   118   {
   119     ReentrantMonitorAutoEnter automon(mReentrantMonitor);
   121     if (!unwedged)
   122       automon.Wait();
   124     return NS_OK;
   125   }
   127   void unwedge()
   128   {
   129     ReentrantMonitorAutoEnter automon(mReentrantMonitor);
   130     unwedged = true;
   131     automon.Notify();
   132   }
   134 private:
   135   ReentrantMonitor mReentrantMonitor;
   136   bool unwedged;
   137 };
   139 ////////////////////////////////////////////////////////////////////////////////
   140 //// Async Helpers
   142 /**
   143  * A horrible hack to figure out what the connection's async thread is.  By
   144  * creating a statement and async dispatching we can tell from the mutex who
   145  * is the async thread, PRThread style.  Then we map that to an nsIThread.
   146  */
   147 already_AddRefed<nsIThread>
   148 get_conn_async_thread(mozIStorageConnection *db)
   149 {
   150   // Make sure we are tracking the current thread as the watched thread
   151   watch_for_mutex_use_on_this_thread();
   153   // - statement with nothing to bind
   154   nsCOMPtr<mozIStorageAsyncStatement> stmt;
   155   db->CreateAsyncStatement(
   156     NS_LITERAL_CSTRING("SELECT 1"),
   157     getter_AddRefs(stmt));
   158   blocking_async_execute(stmt);
   159   stmt->Finalize();
   161   nsCOMPtr<nsIThreadManager> threadMan =
   162     do_GetService("@mozilla.org/thread-manager;1");
   163   nsCOMPtr<nsIThread> asyncThread;
   164   threadMan->GetThreadFromPRThread(last_non_watched_thread,
   165                                    getter_AddRefs(asyncThread));
   167   // Additionally, check that the thread we get as the background thread is the
   168   // same one as the one we report from getInterface.
   169   nsCOMPtr<nsIEventTarget> target = do_GetInterface(db);
   170   nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
   171   PRThread *allegedPRThread;
   172   (void)allegedAsyncThread->GetPRThread(&allegedPRThread);
   173   do_check_eq(allegedPRThread, last_non_watched_thread);
   174   return asyncThread.forget();
   175 }
   178 ////////////////////////////////////////////////////////////////////////////////
   179 //// Tests
   181 void
   182 test_TrueAsyncStatement()
   183 {
   184   // (only the first test needs to call this)
   185   hook_sqlite_mutex();
   187   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
   189   // Start watching for forbidden mutex usage.
   190   watch_for_mutex_use_on_this_thread();
   192   // - statement with nothing to bind
   193   nsCOMPtr<mozIStorageAsyncStatement> stmt;
   194   db->CreateAsyncStatement(
   195     NS_LITERAL_CSTRING("CREATE TABLE test (id INTEGER PRIMARY KEY)"),
   196     getter_AddRefs(stmt)
   197   );
   198   blocking_async_execute(stmt);
   199   stmt->Finalize();
   200   do_check_false(mutex_used_on_watched_thread);
   202   // - statement with something to bind ordinally
   203   db->CreateAsyncStatement(
   204     NS_LITERAL_CSTRING("INSERT INTO test (id) VALUES (?)"),
   205     getter_AddRefs(stmt)
   206   );
   207   stmt->BindInt32ByIndex(0, 1);
   208   blocking_async_execute(stmt);
   209   stmt->Finalize();
   210   do_check_false(mutex_used_on_watched_thread);
   212   // - statement with something to bind by name
   213   db->CreateAsyncStatement(
   214     NS_LITERAL_CSTRING("INSERT INTO test (id) VALUES (:id)"),
   215     getter_AddRefs(stmt)
   216   );
   217   nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
   218   stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
   219   nsCOMPtr<mozIStorageBindingParams> params;
   220   paramsArray->NewBindingParams(getter_AddRefs(params));
   221   params->BindInt32ByName(NS_LITERAL_CSTRING("id"), 2);
   222   paramsArray->AddParams(params);
   223   params = nullptr;
   224   stmt->BindParameters(paramsArray);
   225   paramsArray = nullptr;
   226   blocking_async_execute(stmt);
   227   stmt->Finalize();
   228   do_check_false(mutex_used_on_watched_thread);
   230   // - now, make sure creating a sync statement does trigger our guard.
   231   // (If this doesn't happen, our test is bunk and it's important to know that.)
   232   nsCOMPtr<mozIStorageStatement> syncStmt;
   233   db->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM test"),
   234                       getter_AddRefs(syncStmt));
   235   syncStmt->Finalize();
   236   do_check_true(mutex_used_on_watched_thread);
   238   blocking_async_close(db);
   239 }
   241 /**
   242  * Test that cancellation before a statement is run successfully stops the
   243  * statement from executing.
   244  */
   245 void
   246 test_AsyncCancellation()
   247 {
   248   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
   250   // -- wedge the thread
   251   nsCOMPtr<nsIThread> target(get_conn_async_thread(db));
   252   do_check_true(target);
   253   nsRefPtr<ThreadWedger> wedger (new ThreadWedger(target));
   255   // -- create statements and cancel them
   256   // - async
   257   nsCOMPtr<mozIStorageAsyncStatement> asyncStmt;
   258   db->CreateAsyncStatement(
   259     NS_LITERAL_CSTRING("CREATE TABLE asyncTable (id INTEGER PRIMARY KEY)"),
   260     getter_AddRefs(asyncStmt)
   261   );
   263   nsRefPtr<AsyncStatementSpinner> asyncSpin(new AsyncStatementSpinner());
   264   nsCOMPtr<mozIStoragePendingStatement> asyncPend;
   265   (void)asyncStmt->ExecuteAsync(asyncSpin, getter_AddRefs(asyncPend));
   266   do_check_true(asyncPend);
   267   asyncPend->Cancel();
   269   // - sync
   270   nsCOMPtr<mozIStorageStatement> syncStmt;
   271   db->CreateStatement(
   272     NS_LITERAL_CSTRING("CREATE TABLE syncTable (id INTEGER PRIMARY KEY)"),
   273     getter_AddRefs(syncStmt)
   274   );
   276   nsRefPtr<AsyncStatementSpinner> syncSpin(new AsyncStatementSpinner());
   277   nsCOMPtr<mozIStoragePendingStatement> syncPend;
   278   (void)syncStmt->ExecuteAsync(syncSpin, getter_AddRefs(syncPend));
   279   do_check_true(syncPend);
   280   syncPend->Cancel();
   282   // -- unwedge the async thread
   283   wedger->unwedge();
   285   // -- verify that both statements report they were canceled
   286   asyncSpin->SpinUntilCompleted();
   287   do_check_true(asyncSpin->completionReason ==
   288                 mozIStorageStatementCallback::REASON_CANCELED);
   290   syncSpin->SpinUntilCompleted();
   291   do_check_true(syncSpin->completionReason ==
   292                 mozIStorageStatementCallback::REASON_CANCELED);
   294   // -- verify that neither statement constructed their tables
   295   nsresult rv;
   296   bool exists;
   297   rv = db->TableExists(NS_LITERAL_CSTRING("asyncTable"), &exists);
   298   do_check_true(rv == NS_OK);
   299   do_check_false(exists);
   300   rv = db->TableExists(NS_LITERAL_CSTRING("syncTable"), &exists);
   301   do_check_true(rv == NS_OK);
   302   do_check_false(exists);
   304   // -- cleanup
   305   asyncStmt->Finalize();
   306   syncStmt->Finalize();
   307   blocking_async_close(db);
   308 }
   310 /**
   311  * Test that the destructor for an asynchronous statement which has a
   312  *  sqlite3_stmt will dispatch that statement to the async thread for
   313  *  finalization rather than trying to finalize it on the main thread
   314  *  (and thereby running afoul of our mutex use detector).
   315  */
   316 void test_AsyncDestructorFinalizesOnAsyncThread()
   317 {
   318   // test_TrueAsyncStatement called hook_sqlite_mutex() for us
   320   nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
   321   watch_for_mutex_use_on_this_thread();
   323   // -- create an async statement
   324   nsCOMPtr<mozIStorageAsyncStatement> stmt;
   325   db->CreateAsyncStatement(
   326     NS_LITERAL_CSTRING("CREATE TABLE test (id INTEGER PRIMARY KEY)"),
   327     getter_AddRefs(stmt)
   328   );
   330   // -- execute it so it gets a sqlite3_stmt that needs to be finalized
   331   blocking_async_execute(stmt);
   332   do_check_false(mutex_used_on_watched_thread);
   334   // -- forget our reference
   335   stmt = nullptr;
   337   // -- verify the mutex was not touched
   338   do_check_false(mutex_used_on_watched_thread);
   340   // -- make sure the statement actually gets finalized / cleanup
   341   // the close will assert if we failed to finalize!
   342   blocking_async_close(db);
   343 }
   345 void (*gTests[])(void) = {
   346   // this test must be first because it hooks the mutex mechanics
   347   test_TrueAsyncStatement,
   348   test_AsyncCancellation,
   349   test_AsyncDestructorFinalizesOnAsyncThread
   350 };
   352 const char *file = __FILE__;
   353 #define TEST_NAME "true async statement"
   354 #define TEST_FILE file
   355 #include "storage_test_harness_tail.h"

mercurial