Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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"