Bug 1166166 - Shrink storage cache off main thread r=mak

Per bug: it can take around 200ms to shrink memory for
Places.sqlite. This can end up janking the browser if we hit a
memory-pressure. This patch simply removes the #if DEBUG guards
around the mAsyncExecutionThreadIsAlive boolean which is already
being updated and exposes it so that we can shrink memory off
the main thread when possible.

MozReview-Commit-ID: LoDGKrOXs8u

--HG--
extra : rebase_source : adf03f187e7297835566f3bac92a8be8a6147131
This commit is contained in:
Doug Thayer 2017-06-01 14:46:53 -07:00
parent f13fa4478c
commit d65c88d3f0
5 changed files with 68 additions and 60 deletions

View File

@ -136,25 +136,27 @@ StorageBaseStatementInternal::destructorAsyncFinalize()
if (!mAsyncStatement)
return;
// If we reach this point, our owner has not finalized this
// statement, yet we are being destructed. If possible, we want to
// auto-finalize it early, to release the resources early.
nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
if (target) {
// If we can get the async execution target, we can indeed finalize
// the statement, as the connection is still open.
bool isAsyncThread = false;
(void)target->IsOnCurrentThread(&isAsyncThread);
nsCOMPtr<nsIRunnable> event =
new LastDitchSqliteStatementFinalizer(mDBConnection, mAsyncStatement);
if (isAsyncThread) {
(void)event->Run();
} else {
bool isOwningThread = false;
(void)mDBConnection->threadOpenedOn->IsOnCurrentThread(&isOwningThread);
if (isOwningThread) {
// If we are the owning thread (currently that means we're also the
// main thread), then we can get the async target and just dispatch
// to it.
nsIEventTarget *target = mDBConnection->getAsyncExecutionTarget();
if (target) {
nsCOMPtr<nsIRunnable> event =
new LastDitchSqliteStatementFinalizer(mDBConnection, mAsyncStatement);
(void)target->Dispatch(event, NS_DISPATCH_NORMAL);
}
} else {
// If we're not the owning thread, assume we're the async thread, and
// just run the statement.
nsCOMPtr<nsIRunnable> event =
new LastDitchSqliteStatementFinalizer(mDBConnection, mAsyncStatement);
(void)event->Run();
}
// We might not be able to dispatch to the background thread,
// presumably because it is being shutdown. Since said shutdown will
// finalize the statement, we just need to clean-up around here.

View File

@ -456,8 +456,7 @@ public:
}
NS_IMETHOD Run() override {
MOZ_ASSERT (NS_GetCurrentThread() == mConnection->getAsyncExecutionTarget());
MOZ_ASSERT(!NS_IsMainThread());
nsresult rv = mConnection->initializeClone(mClone, mReadOnly);
if (NS_FAILED(rv)) {
return Dispatch(rv, nullptr);
@ -582,7 +581,7 @@ Connection::getSqliteRuntimeStatus(int32_t aStatusOption, int32_t* aMaxValue)
nsIEventTarget *
Connection::getAsyncExecutionTarget()
{
MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
// If we are shutting down the asynchronous thread, don't hand out any more
// references to the thread.
@ -941,6 +940,13 @@ Connection::isClosed()
return mConnectionClosed;
}
bool
Connection::isAsyncExecutionThreadAvailable()
{
MOZ_ASSERT(NS_IsMainThread());
return !!mAsyncExecutionThread;
}
void
Connection::shutdownAsyncThread(nsIThread *aThread) {
MOZ_ASSERT(!mAsyncExecutionThread);
@ -1216,14 +1222,21 @@ Connection::Close()
if (!mDBConn)
return NS_ERROR_NOT_INITIALIZED;
{ // Make sure we have not executed any asynchronous statements.
// If this fails, the mDBConn will be left open, resulting in a leak.
// Ideally we'd schedule some code to destroy the mDBConn once all its
// async statements have finished executing; see bug 704030.
MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
bool asyncCloseWasCalled = !mAsyncExecutionThread;
NS_ENSURE_TRUE(asyncCloseWasCalled, NS_ERROR_UNEXPECTED);
}
#ifdef DEBUG
// Since we're accessing mAsyncExecutionThread, we need to be on the opener thread.
// We make this check outside of debug code below in setClosedState, but this is
// here to be explicit.
bool onOpenerThread = false;
(void)threadOpenedOn->IsOnCurrentThread(&onOpenerThread);
MOZ_ASSERT(onOpenerThread);
#endif // DEBUG
// Make sure we have not executed any asynchronous statements.
// If this fails, the mDBConn will be left open, resulting in a leak.
// Ideally we'd schedule some code to destroy the mDBConn once all its
// async statements have finished executing; see bug 704030.
bool asyncCloseWasCalled = !mAsyncExecutionThread;
NS_ENSURE_TRUE(asyncCloseWasCalled, NS_ERROR_UNEXPECTED);
// setClosedState nullifies our connection pointer, so we take a raw pointer
// off it, to pass it through the close procedure.
@ -1237,9 +1250,7 @@ Connection::Close()
NS_IMETHODIMP
Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
{
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
// The two relevant factors at this point are whether we have a database
// connection and whether we have an async execution thread. Here's what the
@ -1321,15 +1332,10 @@ Connection::AsyncClose(mozIStorageCompletionCallback *aCallback)
NS_ENSURE_SUCCESS(rv, rv);
// Create and dispatch our close event to the background thread.
nsCOMPtr<nsIRunnable> closeEvent;
{
// We need to lock because we're modifying mAsyncExecutionThread
MutexAutoLock lockedScope(sharedAsyncExecutionMutex);
closeEvent = new AsyncCloseConnection(this,
nativeConn,
completeEvent,
mAsyncExecutionThread.forget());
}
nsCOMPtr<nsIRunnable> closeEvent = new AsyncCloseConnection(this,
nativeConn,
completeEvent,
mAsyncExecutionThread.forget());
rv = asyncThread->Dispatch(closeEvent, NS_DISPATCH_NORMAL);
NS_ENSURE_SUCCESS(rv, rv);
@ -1344,9 +1350,7 @@ Connection::AsyncClone(bool aReadOnly,
PROFILER_LABEL("mozStorageConnection", "AsyncClone",
js::ProfileEntry::Category::STORAGE);
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
if (!mDBConn)
return NS_ERROR_NOT_INITIALIZED;
if (!mDatabaseFile)
@ -1679,9 +1683,7 @@ Connection::ExecuteSimpleSQLAsync(const nsACString &aSQLStatement,
mozIStorageStatementCallback *aCallback,
mozIStoragePendingStatement **_handle)
{
if (!NS_IsMainThread()) {
return NS_ERROR_NOT_SAME_THREAD;
}
NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
nsCOMPtr<mozIStorageAsyncStatement> stmt;
nsresult rv = CreateAsyncStatement(aSQLStatement, getter_AddRefs(stmt));

View File

@ -139,6 +139,8 @@ public:
* the thread may be re-claimed if left idle, so you should call this
* method just before you dispatch and not save the reference.
*
* This must be called from the main thread.
*
* @returns an event target suitable for asynchronous statement execution.
*/
nsIEventTarget *getAsyncExecutionTarget();
@ -149,7 +151,6 @@ public:
* asynchronous statements (they are serialized on mAsyncExecutionThread).
* Currently protects:
* - Connection.mAsyncExecutionThreadShuttingDown
* - Connection.mAsyncExecutionThread
* - Connection.mConnectionClosed
* - AsyncExecuteStatements.mCancelRequested
*/
@ -234,6 +235,14 @@ public:
*/
bool isClosed();
/**
* True if the async execution thread is alive and able to be used (i.e., it
* is not in the process of shutting down.)
*
* This must be called from the main thread.
*/
bool isAsyncExecutionThreadAvailable();
nsresult initializeClone(Connection *aClone, bool aReadOnly);
private:
@ -306,6 +315,8 @@ private:
* Lazily created thread for asynchronous statement execution. Consumers
* should use getAsyncExecutionTarget rather than directly accessing this
* field.
*
* This must be accessed only on the main thread.
*/
nsCOMPtr<nsIThread> mAsyncExecutionThread;

View File

@ -365,8 +365,14 @@ Service::minimizeMemory()
MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
} else if (NS_SUCCEEDED(conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) &&
onOpenedThread) {
// We are on the opener thread, so we can just proceed.
conn->ExecuteSimpleSQL(shrinkPragma);
if (conn->isAsyncExecutionThreadAvailable()) {
nsCOMPtr<mozIStoragePendingStatement> ps;
DebugOnly<nsresult> rv =
conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps));
MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
} else {
conn->ExecuteSimpleSQL(shrinkPragma);
}
} else {
// We are on the wrong thread, the query should be executed on the
// opener thread, so we must dispatch to it.

View File

@ -78,19 +78,6 @@ public:
inline void reset()
{
NS_PRECONDITION(mStatementOwner, "Must have a statement owner!");
#ifdef DEBUG
{
nsCOMPtr<nsIEventTarget> asyncThread =
mStatementOwner->getOwner()->getAsyncExecutionTarget();
// It's possible that we are shutting down the async thread, and this
// method would return nullptr as a result.
if (asyncThread) {
bool onAsyncThread;
NS_ASSERTION(NS_SUCCEEDED(asyncThread->IsOnCurrentThread(&onAsyncThread)) && onAsyncThread,
"This should only be running on the async thread!");
}
}
#endif
// In the AsyncStatement case we may never have populated mStatement if the
// AsyncExecuteStatements got canceled or a failure occurred in constructing
// the statement.