storage/test/test_true_async.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial