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