|
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/. */ |
|
6 |
|
7 #include "storage_test_harness.h" |
|
8 #include "prthread.h" |
|
9 #include "nsIEventTarget.h" |
|
10 #include "nsIInterfaceRequestorUtils.h" |
|
11 |
|
12 #include "sqlite3.h" |
|
13 |
|
14 #include "mozilla/ReentrantMonitor.h" |
|
15 |
|
16 using mozilla::ReentrantMonitor; |
|
17 using mozilla::ReentrantMonitorAutoEnter; |
|
18 |
|
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 */ |
|
27 |
|
28 /* ===== Mutex Watching ===== */ |
|
29 |
|
30 sqlite3_mutex_methods orig_mutex_methods; |
|
31 sqlite3_mutex_methods wrapped_mutex_methods; |
|
32 |
|
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; |
|
45 |
|
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 } |
|
59 |
|
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 } |
|
66 |
|
67 |
|
68 #define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK) |
|
69 |
|
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 } |
|
82 |
|
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 } |
|
95 |
|
96 |
|
97 //////////////////////////////////////////////////////////////////////////////// |
|
98 //// Thread Wedgers |
|
99 |
|
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 } |
|
116 |
|
117 NS_IMETHOD Run() |
|
118 { |
|
119 ReentrantMonitorAutoEnter automon(mReentrantMonitor); |
|
120 |
|
121 if (!unwedged) |
|
122 automon.Wait(); |
|
123 |
|
124 return NS_OK; |
|
125 } |
|
126 |
|
127 void unwedge() |
|
128 { |
|
129 ReentrantMonitorAutoEnter automon(mReentrantMonitor); |
|
130 unwedged = true; |
|
131 automon.Notify(); |
|
132 } |
|
133 |
|
134 private: |
|
135 ReentrantMonitor mReentrantMonitor; |
|
136 bool unwedged; |
|
137 }; |
|
138 |
|
139 //////////////////////////////////////////////////////////////////////////////// |
|
140 //// Async Helpers |
|
141 |
|
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(); |
|
152 |
|
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(); |
|
160 |
|
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)); |
|
166 |
|
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 } |
|
176 |
|
177 |
|
178 //////////////////////////////////////////////////////////////////////////////// |
|
179 //// Tests |
|
180 |
|
181 void |
|
182 test_TrueAsyncStatement() |
|
183 { |
|
184 // (only the first test needs to call this) |
|
185 hook_sqlite_mutex(); |
|
186 |
|
187 nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase()); |
|
188 |
|
189 // Start watching for forbidden mutex usage. |
|
190 watch_for_mutex_use_on_this_thread(); |
|
191 |
|
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); |
|
201 |
|
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); |
|
211 |
|
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); |
|
229 |
|
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); |
|
237 |
|
238 blocking_async_close(db); |
|
239 } |
|
240 |
|
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()); |
|
249 |
|
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)); |
|
254 |
|
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 ); |
|
262 |
|
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(); |
|
268 |
|
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 ); |
|
275 |
|
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(); |
|
281 |
|
282 // -- unwedge the async thread |
|
283 wedger->unwedge(); |
|
284 |
|
285 // -- verify that both statements report they were canceled |
|
286 asyncSpin->SpinUntilCompleted(); |
|
287 do_check_true(asyncSpin->completionReason == |
|
288 mozIStorageStatementCallback::REASON_CANCELED); |
|
289 |
|
290 syncSpin->SpinUntilCompleted(); |
|
291 do_check_true(syncSpin->completionReason == |
|
292 mozIStorageStatementCallback::REASON_CANCELED); |
|
293 |
|
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); |
|
303 |
|
304 // -- cleanup |
|
305 asyncStmt->Finalize(); |
|
306 syncStmt->Finalize(); |
|
307 blocking_async_close(db); |
|
308 } |
|
309 |
|
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 |
|
319 |
|
320 nsCOMPtr<mozIStorageConnection> db(getMemoryDatabase()); |
|
321 watch_for_mutex_use_on_this_thread(); |
|
322 |
|
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 ); |
|
329 |
|
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); |
|
333 |
|
334 // -- forget our reference |
|
335 stmt = nullptr; |
|
336 |
|
337 // -- verify the mutex was not touched |
|
338 do_check_false(mutex_used_on_watched_thread); |
|
339 |
|
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 } |
|
344 |
|
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 }; |
|
351 |
|
352 const char *file = __FILE__; |
|
353 #define TEST_NAME "true async statement" |
|
354 #define TEST_FILE file |
|
355 #include "storage_test_harness_tail.h" |