storage/test/test_true_async.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/storage/test/test_true_async.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,355 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "storage_test_harness.h"
    1.11 +#include "prthread.h"
    1.12 +#include "nsIEventTarget.h"
    1.13 +#include "nsIInterfaceRequestorUtils.h"
    1.14 +
    1.15 +#include "sqlite3.h"
    1.16 +
    1.17 +#include "mozilla/ReentrantMonitor.h"
    1.18 +
    1.19 +using mozilla::ReentrantMonitor;
    1.20 +using mozilla::ReentrantMonitorAutoEnter;
    1.21 +
    1.22 +/**
    1.23 + * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on
    1.24 + * the caller (generally main) thread.  We do this by decorating the sqlite
    1.25 + * mutex logic with our own code that checks what thread it is being invoked on
    1.26 + * and sets a flag if it is invoked on the main thread.  We are able to easily
    1.27 + * decorate the SQLite mutex logic because SQLite allows us to retrieve the
    1.28 + * current function pointers being used and then provide a new set.
    1.29 + */
    1.30 +
    1.31 +/* ===== Mutex Watching ===== */
    1.32 +
    1.33 +sqlite3_mutex_methods orig_mutex_methods;
    1.34 +sqlite3_mutex_methods wrapped_mutex_methods;
    1.35 +
    1.36 +bool mutex_used_on_watched_thread = false;
    1.37 +PRThread *watched_thread = nullptr;
    1.38 +/**
    1.39 + * Ugly hack to let us figure out what a connection's async thread is.  If we
    1.40 + * were MOZILLA_INTERNAL_API and linked as such we could just include
    1.41 + * mozStorageConnection.h and just ask Connection directly.  But that turns out
    1.42 + * poorly.
    1.43 + *
    1.44 + * When the thread a mutex is invoked on isn't watched_thread we save it to this
    1.45 + * variable.
    1.46 + */
    1.47 +PRThread *last_non_watched_thread = nullptr;
    1.48 +
    1.49 +/**
    1.50 + * Set a flag if the mutex is used on the thread we are watching, but always
    1.51 + * call the real mutex function.
    1.52 + */
    1.53 +extern "C" void wrapped_MutexEnter(sqlite3_mutex *mutex)
    1.54 +{
    1.55 +  PRThread *curThread = ::PR_GetCurrentThread();
    1.56 +  if (curThread == watched_thread)
    1.57 +    mutex_used_on_watched_thread = true;
    1.58 +  else
    1.59 +    last_non_watched_thread = curThread;
    1.60 +  orig_mutex_methods.xMutexEnter(mutex);
    1.61 +}
    1.62 +
    1.63 +extern "C" int wrapped_MutexTry(sqlite3_mutex *mutex)
    1.64 +{
    1.65 +  if (::PR_GetCurrentThread() == watched_thread)
    1.66 +    mutex_used_on_watched_thread = true;
    1.67 +  return orig_mutex_methods.xMutexTry(mutex);
    1.68 +}
    1.69 +
    1.70 +
    1.71 +#define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
    1.72 +
    1.73 +void hook_sqlite_mutex()
    1.74 +{
    1.75 +  // We need to initialize and teardown SQLite to get it to set up the
    1.76 +  // default mutex handlers for us so we can steal them and wrap them.
    1.77 +  do_check_ok(sqlite3_initialize());
    1.78 +  do_check_ok(sqlite3_shutdown());
    1.79 +  do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &orig_mutex_methods));
    1.80 +  do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX, &wrapped_mutex_methods));
    1.81 +  wrapped_mutex_methods.xMutexEnter = wrapped_MutexEnter;
    1.82 +  wrapped_mutex_methods.xMutexTry = wrapped_MutexTry;
    1.83 +  do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX, &wrapped_mutex_methods));
    1.84 +}
    1.85 +
    1.86 +/**
    1.87 + * Call to clear the watch state and to set the watching against this thread.
    1.88 + *
    1.89 + * Check |mutex_used_on_watched_thread| to see if the mutex has fired since
    1.90 + * this method was last called.  Since we're talking about the current thread,
    1.91 + * there are no race issues to be concerned about
    1.92 + */
    1.93 +void watch_for_mutex_use_on_this_thread()
    1.94 +{
    1.95 +  watched_thread = ::PR_GetCurrentThread();
    1.96 +  mutex_used_on_watched_thread = false;
    1.97 +}
    1.98 +
    1.99 +
   1.100 +////////////////////////////////////////////////////////////////////////////////
   1.101 +//// Thread Wedgers
   1.102 +
   1.103 +/**
   1.104 + * A runnable that blocks until code on another thread invokes its unwedge
   1.105 + * method.  By dispatching this to a thread you can ensure that no subsequent
   1.106 + * runnables dispatched to the thread will execute until you invoke unwedge.
   1.107 + *
   1.108 + * The wedger is self-dispatching, just construct it with its target.
   1.109 + */
   1.110 +class ThreadWedger : public nsRunnable
   1.111 +{
   1.112 +public:
   1.113 +  ThreadWedger(nsIEventTarget *aTarget)
   1.114 +  : mReentrantMonitor("thread wedger")
   1.115 +  , unwedged(false)
   1.116 +  {
   1.117 +    aTarget->Dispatch(this, aTarget->NS_DISPATCH_NORMAL);
   1.118 +  }
   1.119 +
   1.120 +  NS_IMETHOD Run()
   1.121 +  {
   1.122 +    ReentrantMonitorAutoEnter automon(mReentrantMonitor);
   1.123 +
   1.124 +    if (!unwedged)
   1.125 +      automon.Wait();
   1.126 +
   1.127 +    return NS_OK;
   1.128 +  }
   1.129 +
   1.130 +  void unwedge()
   1.131 +  {
   1.132 +    ReentrantMonitorAutoEnter automon(mReentrantMonitor);
   1.133 +    unwedged = true;
   1.134 +    automon.Notify();
   1.135 +  }
   1.136 +
   1.137 +private:
   1.138 +  ReentrantMonitor mReentrantMonitor;
   1.139 +  bool unwedged;
   1.140 +};
   1.141 +
   1.142 +////////////////////////////////////////////////////////////////////////////////
   1.143 +//// Async Helpers
   1.144 +
   1.145 +/**
   1.146 + * A horrible hack to figure out what the connection's async thread is.  By
   1.147 + * creating a statement and async dispatching we can tell from the mutex who
   1.148 + * is the async thread, PRThread style.  Then we map that to an nsIThread.
   1.149 + */
   1.150 +already_AddRefed<nsIThread>
   1.151 +get_conn_async_thread(mozIStorageConnection *db)
   1.152 +{
   1.153 +  // Make sure we are tracking the current thread as the watched thread
   1.154 +  watch_for_mutex_use_on_this_thread();
   1.155 +
   1.156 +  // - statement with nothing to bind
   1.157 +  nsCOMPtr<mozIStorageAsyncStatement> stmt;
   1.158 +  db->CreateAsyncStatement(
   1.159 +    NS_LITERAL_CSTRING("SELECT 1"),
   1.160 +    getter_AddRefs(stmt));
   1.161 +  blocking_async_execute(stmt);
   1.162 +  stmt->Finalize();
   1.163 +
   1.164 +  nsCOMPtr<nsIThreadManager> threadMan =
   1.165 +    do_GetService("@mozilla.org/thread-manager;1");
   1.166 +  nsCOMPtr<nsIThread> asyncThread;
   1.167 +  threadMan->GetThreadFromPRThread(last_non_watched_thread,
   1.168 +                                   getter_AddRefs(asyncThread));
   1.169 +
   1.170 +  // Additionally, check that the thread we get as the background thread is the
   1.171 +  // same one as the one we report from getInterface.
   1.172 +  nsCOMPtr<nsIEventTarget> target = do_GetInterface(db);
   1.173 +  nsCOMPtr<nsIThread> allegedAsyncThread = do_QueryInterface(target);
   1.174 +  PRThread *allegedPRThread;
   1.175 +  (void)allegedAsyncThread->GetPRThread(&allegedPRThread);
   1.176 +  do_check_eq(allegedPRThread, last_non_watched_thread);
   1.177 +  return asyncThread.forget();
   1.178 +}
   1.179 +
   1.180 +
   1.181 +////////////////////////////////////////////////////////////////////////////////
   1.182 +//// Tests
   1.183 +
   1.184 +void
   1.185 +test_TrueAsyncStatement()
   1.186 +{
   1.187 +  // (only the first test needs to call this)
   1.188 +  hook_sqlite_mutex();
   1.189 +
   1.190 +  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
   1.191 +
   1.192 +  // Start watching for forbidden mutex usage.
   1.193 +  watch_for_mutex_use_on_this_thread();
   1.194 +
   1.195 +  // - statement with nothing to bind
   1.196 +  nsCOMPtr<mozIStorageAsyncStatement> stmt;
   1.197 +  db->CreateAsyncStatement(
   1.198 +    NS_LITERAL_CSTRING("CREATE TABLE test (id INTEGER PRIMARY KEY)"),
   1.199 +    getter_AddRefs(stmt)
   1.200 +  );
   1.201 +  blocking_async_execute(stmt);
   1.202 +  stmt->Finalize();
   1.203 +  do_check_false(mutex_used_on_watched_thread);
   1.204 +
   1.205 +  // - statement with something to bind ordinally
   1.206 +  db->CreateAsyncStatement(
   1.207 +    NS_LITERAL_CSTRING("INSERT INTO test (id) VALUES (?)"),
   1.208 +    getter_AddRefs(stmt)
   1.209 +  );
   1.210 +  stmt->BindInt32ByIndex(0, 1);
   1.211 +  blocking_async_execute(stmt);
   1.212 +  stmt->Finalize();
   1.213 +  do_check_false(mutex_used_on_watched_thread);
   1.214 +  
   1.215 +  // - statement with something to bind by name
   1.216 +  db->CreateAsyncStatement(
   1.217 +    NS_LITERAL_CSTRING("INSERT INTO test (id) VALUES (:id)"),
   1.218 +    getter_AddRefs(stmt)
   1.219 +  );
   1.220 +  nsCOMPtr<mozIStorageBindingParamsArray> paramsArray;
   1.221 +  stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
   1.222 +  nsCOMPtr<mozIStorageBindingParams> params;
   1.223 +  paramsArray->NewBindingParams(getter_AddRefs(params));
   1.224 +  params->BindInt32ByName(NS_LITERAL_CSTRING("id"), 2);
   1.225 +  paramsArray->AddParams(params);
   1.226 +  params = nullptr;
   1.227 +  stmt->BindParameters(paramsArray);
   1.228 +  paramsArray = nullptr;
   1.229 +  blocking_async_execute(stmt);
   1.230 +  stmt->Finalize();
   1.231 +  do_check_false(mutex_used_on_watched_thread);
   1.232 +
   1.233 +  // - now, make sure creating a sync statement does trigger our guard.
   1.234 +  // (If this doesn't happen, our test is bunk and it's important to know that.)
   1.235 +  nsCOMPtr<mozIStorageStatement> syncStmt;
   1.236 +  db->CreateStatement(NS_LITERAL_CSTRING("SELECT * FROM test"),
   1.237 +                      getter_AddRefs(syncStmt));
   1.238 +  syncStmt->Finalize();
   1.239 +  do_check_true(mutex_used_on_watched_thread);
   1.240 +
   1.241 +  blocking_async_close(db);
   1.242 +}
   1.243 +
   1.244 +/**
   1.245 + * Test that cancellation before a statement is run successfully stops the
   1.246 + * statement from executing.
   1.247 + */
   1.248 +void
   1.249 +test_AsyncCancellation()
   1.250 +{
   1.251 +  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
   1.252 +
   1.253 +  // -- wedge the thread
   1.254 +  nsCOMPtr<nsIThread> target(get_conn_async_thread(db));
   1.255 +  do_check_true(target);
   1.256 +  nsRefPtr<ThreadWedger> wedger (new ThreadWedger(target));
   1.257 +
   1.258 +  // -- create statements and cancel them
   1.259 +  // - async
   1.260 +  nsCOMPtr<mozIStorageAsyncStatement> asyncStmt;
   1.261 +  db->CreateAsyncStatement(
   1.262 +    NS_LITERAL_CSTRING("CREATE TABLE asyncTable (id INTEGER PRIMARY KEY)"),
   1.263 +    getter_AddRefs(asyncStmt)
   1.264 +  );
   1.265 +
   1.266 +  nsRefPtr<AsyncStatementSpinner> asyncSpin(new AsyncStatementSpinner());
   1.267 +  nsCOMPtr<mozIStoragePendingStatement> asyncPend;
   1.268 +  (void)asyncStmt->ExecuteAsync(asyncSpin, getter_AddRefs(asyncPend));
   1.269 +  do_check_true(asyncPend);
   1.270 +  asyncPend->Cancel();
   1.271 +
   1.272 +  // - sync
   1.273 +  nsCOMPtr<mozIStorageStatement> syncStmt;
   1.274 +  db->CreateStatement(
   1.275 +    NS_LITERAL_CSTRING("CREATE TABLE syncTable (id INTEGER PRIMARY KEY)"),
   1.276 +    getter_AddRefs(syncStmt)
   1.277 +  );
   1.278 +
   1.279 +  nsRefPtr<AsyncStatementSpinner> syncSpin(new AsyncStatementSpinner());
   1.280 +  nsCOMPtr<mozIStoragePendingStatement> syncPend;
   1.281 +  (void)syncStmt->ExecuteAsync(syncSpin, getter_AddRefs(syncPend));
   1.282 +  do_check_true(syncPend);
   1.283 +  syncPend->Cancel();
   1.284 +
   1.285 +  // -- unwedge the async thread
   1.286 +  wedger->unwedge();
   1.287 +
   1.288 +  // -- verify that both statements report they were canceled
   1.289 +  asyncSpin->SpinUntilCompleted();
   1.290 +  do_check_true(asyncSpin->completionReason ==
   1.291 +                mozIStorageStatementCallback::REASON_CANCELED);
   1.292 +
   1.293 +  syncSpin->SpinUntilCompleted();
   1.294 +  do_check_true(syncSpin->completionReason ==
   1.295 +                mozIStorageStatementCallback::REASON_CANCELED);
   1.296 +
   1.297 +  // -- verify that neither statement constructed their tables
   1.298 +  nsresult rv;
   1.299 +  bool exists;
   1.300 +  rv = db->TableExists(NS_LITERAL_CSTRING("asyncTable"), &exists);
   1.301 +  do_check_true(rv == NS_OK);
   1.302 +  do_check_false(exists);
   1.303 +  rv = db->TableExists(NS_LITERAL_CSTRING("syncTable"), &exists);
   1.304 +  do_check_true(rv == NS_OK);
   1.305 +  do_check_false(exists);
   1.306 +
   1.307 +  // -- cleanup
   1.308 +  asyncStmt->Finalize();
   1.309 +  syncStmt->Finalize();
   1.310 +  blocking_async_close(db);
   1.311 +}
   1.312 +
   1.313 +/**
   1.314 + * Test that the destructor for an asynchronous statement which has a
   1.315 + *  sqlite3_stmt will dispatch that statement to the async thread for
   1.316 + *  finalization rather than trying to finalize it on the main thread
   1.317 + *  (and thereby running afoul of our mutex use detector).
   1.318 + */
   1.319 +void test_AsyncDestructorFinalizesOnAsyncThread()
   1.320 +{
   1.321 +  // test_TrueAsyncStatement called hook_sqlite_mutex() for us
   1.322 +
   1.323 +  nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase());
   1.324 +  watch_for_mutex_use_on_this_thread();
   1.325 +
   1.326 +  // -- create an async statement
   1.327 +  nsCOMPtr<mozIStorageAsyncStatement> stmt;
   1.328 +  db->CreateAsyncStatement(
   1.329 +    NS_LITERAL_CSTRING("CREATE TABLE test (id INTEGER PRIMARY KEY)"),
   1.330 +    getter_AddRefs(stmt)
   1.331 +  );
   1.332 +
   1.333 +  // -- execute it so it gets a sqlite3_stmt that needs to be finalized
   1.334 +  blocking_async_execute(stmt);
   1.335 +  do_check_false(mutex_used_on_watched_thread);
   1.336 +
   1.337 +  // -- forget our reference
   1.338 +  stmt = nullptr;
   1.339 +
   1.340 +  // -- verify the mutex was not touched
   1.341 +  do_check_false(mutex_used_on_watched_thread);
   1.342 +
   1.343 +  // -- make sure the statement actually gets finalized / cleanup
   1.344 +  // the close will assert if we failed to finalize!
   1.345 +  blocking_async_close(db);
   1.346 +}
   1.347 +
   1.348 +void (*gTests[])(void) = {
   1.349 +  // this test must be first because it hooks the mutex mechanics
   1.350 +  test_TrueAsyncStatement,
   1.351 +  test_AsyncCancellation,
   1.352 +  test_AsyncDestructorFinalizesOnAsyncThread
   1.353 +};
   1.354 +
   1.355 +const char *file = __FILE__;
   1.356 +#define TEST_NAME "true async statement"
   1.357 +#define TEST_FILE file
   1.358 +#include "storage_test_harness_tail.h"

mercurial