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"