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 "TestHarness.h"
11 #include "nsThreadUtils.h"
12 #include "nsDirectoryServiceDefs.h"
13 #include "mozilla/ReentrantMonitor.h"
15 #include "mozIStorageService.h"
16 #include "mozIStorageConnection.h"
17 #include "mozIStorageStatementCallback.h"
18 #include "mozIStorageCompletionCallback.h"
19 #include "mozIStorageBindingParamsArray.h"
20 #include "mozIStorageBindingParams.h"
21 #include "mozIStorageAsyncStatement.h"
22 #include "mozIStorageStatement.h"
23 #include "mozIStoragePendingStatement.h"
24 #include "mozIStorageError.h"
25 #include "nsIInterfaceRequestorUtils.h"
26 #include "nsIEventTarget.h"
30 static int gTotalTests
= 0;
31 static int gPassedTests
= 0;
33 #define do_check_true(aCondition) \
39 fail("%s | Expected true, got false at line %d", __FILE__, __LINE__); \
43 #define do_check_false(aCondition) \
49 fail("%s | Expected false, got true at line %d", __FILE__, __LINE__); \
53 #define do_check_success(aResult) \
54 do_check_true(NS_SUCCEEDED(aResult))
57 // XXX Linux opt builds on tinderbox are orange due to linking with stdlib.
58 // This is sad and annoying, but it's a workaround that works.
59 #define do_check_eq(aExpected, aActual) \
60 do_check_true(aExpected == aActual)
63 // Print nsresult as uint32_t
64 std::ostream
& operator<<(std::ostream
& aStream
, const nsresult aInput
)
66 return aStream
<< static_cast<uint32_t>(aInput
);
68 #define do_check_eq(aExpected, aActual) \
71 if (aExpected == aActual) { \
74 std::ostringstream temp; \
75 temp << __FILE__ << " | Expected '" << aExpected << "', got '"; \
76 temp << aActual <<"' at line " << __LINE__; \
77 fail(temp.str().c_str()); \
82 #define do_check_ok(aInvoc) do_check_true((aInvoc) == SQLITE_OK)
84 already_AddRefed
<mozIStorageService
>
87 nsCOMPtr
<mozIStorageService
> ss
=
88 do_GetService("@mozilla.org/storage/service;1");
93 already_AddRefed
<mozIStorageConnection
>
96 nsCOMPtr
<mozIStorageService
> ss
= getService();
97 nsCOMPtr
<mozIStorageConnection
> conn
;
98 nsresult rv
= ss
->OpenSpecialDatabase("memory", getter_AddRefs(conn
));
100 return conn
.forget();
103 already_AddRefed
<mozIStorageConnection
>
106 nsCOMPtr
<nsIFile
> dbFile
;
107 (void)NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR
,
108 getter_AddRefs(dbFile
));
109 NS_ASSERTION(dbFile
, "The directory doesn't exists?!");
111 nsresult rv
= dbFile
->Append(NS_LITERAL_STRING("storage_test_db.sqlite"));
112 do_check_success(rv
);
114 nsCOMPtr
<mozIStorageService
> ss
= getService();
115 nsCOMPtr
<mozIStorageConnection
> conn
;
116 rv
= ss
->OpenDatabase(dbFile
, getter_AddRefs(conn
));
117 do_check_success(rv
);
118 return conn
.forget();
122 class AsyncStatementSpinner
: public mozIStorageStatementCallback
123 , public mozIStorageCompletionCallback
127 NS_DECL_MOZISTORAGESTATEMENTCALLBACK
128 NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
130 AsyncStatementSpinner();
132 void SpinUntilCompleted();
134 uint16_t completionReason
;
137 virtual ~AsyncStatementSpinner() {}
138 volatile bool mCompleted
;
141 NS_IMPL_ISUPPORTS(AsyncStatementSpinner
,
142 mozIStorageStatementCallback
,
143 mozIStorageCompletionCallback
)
145 AsyncStatementSpinner::AsyncStatementSpinner()
146 : completionReason(0)
152 AsyncStatementSpinner::HandleResult(mozIStorageResultSet
*aResultSet
)
158 AsyncStatementSpinner::HandleError(mozIStorageError
*aError
)
161 nsresult rv
= aError
->GetResult(&result
);
162 NS_ENSURE_SUCCESS(rv
, rv
);
163 nsAutoCString message
;
164 rv
= aError
->GetMessage(message
);
165 NS_ENSURE_SUCCESS(rv
, rv
);
167 nsAutoCString warnMsg
;
168 warnMsg
.AppendLiteral("An error occurred while executing an async statement: ");
169 warnMsg
.AppendInt(result
);
171 warnMsg
.Append(message
);
172 NS_WARNING(warnMsg
.get());
178 AsyncStatementSpinner::HandleCompletion(uint16_t aReason
)
180 completionReason
= aReason
;
186 AsyncStatementSpinner::Complete(nsresult
, nsISupports
*)
192 void AsyncStatementSpinner::SpinUntilCompleted()
194 nsCOMPtr
<nsIThread
> thread(::do_GetCurrentThread());
196 bool processed
= true;
197 while (!mCompleted
&& NS_SUCCEEDED(rv
)) {
198 rv
= thread
->ProcessNextEvent(true, &processed
);
202 #define NS_DECL_ASYNCSTATEMENTSPINNER \
203 NS_IMETHOD HandleResult(mozIStorageResultSet *aResultSet);
205 ////////////////////////////////////////////////////////////////////////////////
209 * Execute an async statement, blocking the main thread until we get the
210 * callback completion notification.
213 blocking_async_execute(mozIStorageBaseStatement
*stmt
)
215 nsRefPtr
<AsyncStatementSpinner
> spinner(new AsyncStatementSpinner());
217 nsCOMPtr
<mozIStoragePendingStatement
> pendy
;
218 (void)stmt
->ExecuteAsync(spinner
, getter_AddRefs(pendy
));
219 spinner
->SpinUntilCompleted();
223 * Invoke AsyncClose on the given connection, blocking the main thread until we
224 * get the completion notification.
227 blocking_async_close(mozIStorageConnection
*db
)
229 nsRefPtr
<AsyncStatementSpinner
> spinner(new AsyncStatementSpinner());
231 db
->AsyncClose(spinner
);
232 spinner
->SpinUntilCompleted();
235 ////////////////////////////////////////////////////////////////////////////////
239 * Verify that mozIStorageAsyncStatement's life-cycle never triggers a mutex on
240 * the caller (generally main) thread. We do this by decorating the sqlite
241 * mutex logic with our own code that checks what thread it is being invoked on
242 * and sets a flag if it is invoked on the main thread. We are able to easily
243 * decorate the SQLite mutex logic because SQLite allows us to retrieve the
244 * current function pointers being used and then provide a new set.
247 sqlite3_mutex_methods orig_mutex_methods
;
248 sqlite3_mutex_methods wrapped_mutex_methods
;
250 bool mutex_used_on_watched_thread
= false;
251 PRThread
*watched_thread
= nullptr;
253 * Ugly hack to let us figure out what a connection's async thread is. If we
254 * were MOZILLA_INTERNAL_API and linked as such we could just include
255 * mozStorageConnection.h and just ask Connection directly. But that turns out
258 * When the thread a mutex is invoked on isn't watched_thread we save it to this
261 PRThread
*last_non_watched_thread
= nullptr;
264 * Set a flag if the mutex is used on the thread we are watching, but always
265 * call the real mutex function.
267 extern "C" void wrapped_MutexEnter(sqlite3_mutex
*mutex
)
269 PRThread
*curThread
= ::PR_GetCurrentThread();
270 if (curThread
== watched_thread
)
271 mutex_used_on_watched_thread
= true;
273 last_non_watched_thread
= curThread
;
274 orig_mutex_methods
.xMutexEnter(mutex
);
277 extern "C" int wrapped_MutexTry(sqlite3_mutex
*mutex
)
279 if (::PR_GetCurrentThread() == watched_thread
)
280 mutex_used_on_watched_thread
= true;
281 return orig_mutex_methods
.xMutexTry(mutex
);
284 void hook_sqlite_mutex()
286 // We need to initialize and teardown SQLite to get it to set up the
287 // default mutex handlers for us so we can steal them and wrap them.
288 do_check_ok(sqlite3_initialize());
289 do_check_ok(sqlite3_shutdown());
290 do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX
, &orig_mutex_methods
));
291 do_check_ok(::sqlite3_config(SQLITE_CONFIG_GETMUTEX
, &wrapped_mutex_methods
));
292 wrapped_mutex_methods
.xMutexEnter
= wrapped_MutexEnter
;
293 wrapped_mutex_methods
.xMutexTry
= wrapped_MutexTry
;
294 do_check_ok(::sqlite3_config(SQLITE_CONFIG_MUTEX
, &wrapped_mutex_methods
));
298 * Call to clear the watch state and to set the watching against this thread.
300 * Check |mutex_used_on_watched_thread| to see if the mutex has fired since
301 * this method was last called. Since we're talking about the current thread,
302 * there are no race issues to be concerned about
304 void watch_for_mutex_use_on_this_thread()
306 watched_thread
= ::PR_GetCurrentThread();
307 mutex_used_on_watched_thread
= false;
311 ////////////////////////////////////////////////////////////////////////////////
315 * A runnable that blocks until code on another thread invokes its unwedge
316 * method. By dispatching this to a thread you can ensure that no subsequent
317 * runnables dispatched to the thread will execute until you invoke unwedge.
319 * The wedger is self-dispatching, just construct it with its target.
321 class ThreadWedger
: public nsRunnable
324 explicit ThreadWedger(nsIEventTarget
*aTarget
)
325 : mReentrantMonitor("thread wedger")
328 aTarget
->Dispatch(this, aTarget
->NS_DISPATCH_NORMAL
);
333 mozilla::ReentrantMonitorAutoEnter
automon(mReentrantMonitor
);
343 mozilla::ReentrantMonitorAutoEnter
automon(mReentrantMonitor
);
349 mozilla::ReentrantMonitor mReentrantMonitor
;
353 ////////////////////////////////////////////////////////////////////////////////
357 * A horrible hack to figure out what the connection's async thread is. By
358 * creating a statement and async dispatching we can tell from the mutex who
359 * is the async thread, PRThread style. Then we map that to an nsIThread.
361 already_AddRefed
<nsIThread
>
362 get_conn_async_thread(mozIStorageConnection
*db
)
364 // Make sure we are tracking the current thread as the watched thread
365 watch_for_mutex_use_on_this_thread();
367 // - statement with nothing to bind
368 nsCOMPtr
<mozIStorageAsyncStatement
> stmt
;
369 db
->CreateAsyncStatement(
370 NS_LITERAL_CSTRING("SELECT 1"),
371 getter_AddRefs(stmt
));
372 blocking_async_execute(stmt
);
375 nsCOMPtr
<nsIThreadManager
> threadMan
=
376 do_GetService("@mozilla.org/thread-manager;1");
377 nsCOMPtr
<nsIThread
> asyncThread
;
378 threadMan
->GetThreadFromPRThread(last_non_watched_thread
,
379 getter_AddRefs(asyncThread
));
381 // Additionally, check that the thread we get as the background thread is the
382 // same one as the one we report from getInterface.
383 nsCOMPtr
<nsIEventTarget
> target
= do_GetInterface(db
);
384 nsCOMPtr
<nsIThread
> allegedAsyncThread
= do_QueryInterface(target
);
385 PRThread
*allegedPRThread
;
386 (void)allegedAsyncThread
->GetPRThread(&allegedPRThread
);
387 do_check_eq(allegedPRThread
, last_non_watched_thread
);
388 return asyncThread
.forget();