Commit 1d520a9c authored by wibling@chromium.org's avatar wibling@chromium.org

[oilpan]: Add SafePointAwareMutexLocker to allow GC when waiting for lock.

This change add a mutex locker which enters a safepoint while trying to acquire the mutex lock. This means that threads waiting to acquire a lock would be parked allowing a GC.
In addition to entering a safepoint the locker will also ensure that if we block while leaving the safepoint, due to another thread having scheduled a GC, the lock will be released and the safepoint reentered. This allows for the lock to be acquired during sweeping, specifically in weak processing and finalization which could otherwise deadlock and cause the other scheduled GC to time out.

This change does however not fix the issue where the lock has been acquired and another safepoint is entered while having the lock. This could still cause a timeout. There is an instance of this scenario in the webdatabase's DatabaseBackendBase.cpp::performOpenAndVerify where we get the guidMutex and while commiting the transaction enter a safepoint in the SQLiteStatement::prepare and SQLiteStatement::step calls.
To fix the above we have to restructure the webdatabase code to not hold the global guidMutex lock while doing sync operations.

This change does make it less likely that we hit the empty assert in the terminate-during-sync-websql.html test, but due to the above issue it can still happen. We should consider still doing the restructuring in https://codereview.chromium.org/335823002/ to completely get rid of the flakiness.


R=ager@chromium.org, erik.corry@gmail.com, haraken@chromium.org, oilpan-reviews@chromium.org, tkent@chromium.org, vegorov@chromium.org, zerny@chromium.org
BUG=375671

Review URL: https://codereview.chromium.org/332393002

git-svn-id: svn://svn.chromium.org/blink/trunk@176347 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 55c812ec
...@@ -220,7 +220,7 @@ DatabaseBackendBase::DatabaseBackendBase(DatabaseContext* databaseContext, const ...@@ -220,7 +220,7 @@ DatabaseBackendBase::DatabaseBackendBase(DatabaseContext* databaseContext, const
m_name = ""; m_name = "";
{ {
MutexLocker locker(guidMutex()); SafePointAwareMutexLocker locker(guidMutex());
m_guid = guidForOriginAndName(securityOrigin()->toString(), name); m_guid = guidForOriginAndName(securityOrigin()->toString(), name);
HashSet<DatabaseBackendBase*>* hashSet = guidToDatabaseMap().get(m_guid); HashSet<DatabaseBackendBase*>* hashSet = guidToDatabaseMap().get(m_guid);
if (!hashSet) { if (!hashSet) {
...@@ -266,7 +266,7 @@ void DatabaseBackendBase::closeDatabase() ...@@ -266,7 +266,7 @@ void DatabaseBackendBase::closeDatabase()
// See comment at the top this file regarding calling removeOpenDatabase(). // See comment at the top this file regarding calling removeOpenDatabase().
DatabaseTracker::tracker().removeOpenDatabase(this); DatabaseTracker::tracker().removeOpenDatabase(this);
{ {
MutexLocker locker(guidMutex()); SafePointAwareMutexLocker locker(guidMutex());
HashSet<DatabaseBackendBase*>* hashSet = guidToDatabaseMap().get(m_guid); HashSet<DatabaseBackendBase*>* hashSet = guidToDatabaseMap().get(m_guid);
ASSERT(hashSet); ASSERT(hashSet);
...@@ -329,7 +329,7 @@ bool DatabaseBackendBase::performOpenAndVerify(bool shouldSetVersionInNewDatabas ...@@ -329,7 +329,7 @@ bool DatabaseBackendBase::performOpenAndVerify(bool shouldSetVersionInNewDatabas
String currentVersion; String currentVersion;
{ {
MutexLocker locker(guidMutex()); SafePointAwareMutexLocker locker(guidMutex());
GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid); GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid);
if (entry != guidToVersionMap().end()) { if (entry != guidToVersionMap().end()) {
...@@ -506,14 +506,14 @@ void DatabaseBackendBase::setExpectedVersion(const String& version) ...@@ -506,14 +506,14 @@ void DatabaseBackendBase::setExpectedVersion(const String& version)
String DatabaseBackendBase::getCachedVersion() const String DatabaseBackendBase::getCachedVersion() const
{ {
MutexLocker locker(guidMutex()); SafePointAwareMutexLocker locker(guidMutex());
return guidToVersionMap().get(m_guid).isolatedCopy(); return guidToVersionMap().get(m_guid).isolatedCopy();
} }
void DatabaseBackendBase::setCachedVersion(const String& actualVersion) void DatabaseBackendBase::setCachedVersion(const String& actualVersion)
{ {
// Update the in memory database version map. // Update the in memory database version map.
MutexLocker locker(guidMutex()); SafePointAwareMutexLocker locker(guidMutex());
updateGuidVersionMap(m_guid, actualVersion); updateGuidVersionMap(m_guid, actualVersion);
} }
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "config.h" #include "config.h"
#include "modules/webdatabase/sqlite/SQLiteFileSystem.h" #include "modules/webdatabase/sqlite/SQLiteFileSystem.h"
#include "platform/heap/Handle.h"
#include <sqlite3.h> #include <sqlite3.h>
#include "wtf/text/CString.h" #include "wtf/text/CString.h"
...@@ -44,6 +45,7 @@ SQLiteFileSystem::SQLiteFileSystem() ...@@ -44,6 +45,7 @@ SQLiteFileSystem::SQLiteFileSystem()
int SQLiteFileSystem::openDatabase(const String& filename, sqlite3** database, bool forWebSQLDatabase) int SQLiteFileSystem::openDatabase(const String& filename, sqlite3** database, bool forWebSQLDatabase)
{ {
ThreadState::SafePointScope scope(ThreadState::HeapPointersOnStack);
if (!forWebSQLDatabase) if (!forWebSQLDatabase)
return sqlite3_open(filename.utf8().data(), database); return sqlite3_open(filename.utf8().data(), database);
......
...@@ -59,12 +59,13 @@ int SQLiteStatement::prepare() ...@@ -59,12 +59,13 @@ int SQLiteStatement::prepare()
{ {
ASSERT(!m_isPrepared); ASSERT(!m_isPrepared);
CString query = m_query.stripWhiteSpace().utf8();
ThreadState::SafePointScope scope(ThreadState::HeapPointersOnStack);
MutexLocker databaseLock(m_database.databaseMutex()); MutexLocker databaseLock(m_database.databaseMutex());
if (m_database.isInterrupted()) if (m_database.isInterrupted())
return SQLITE_INTERRUPT; return SQLITE_INTERRUPT;
CString query = m_query.stripWhiteSpace().utf8();
WTF_LOG(SQLDatabase, "SQL - prepare - %s", query.data()); WTF_LOG(SQLDatabase, "SQL - prepare - %s", query.data());
// Pass the length of the string including the null character to sqlite3_prepare_v2; // Pass the length of the string including the null character to sqlite3_prepare_v2;
...@@ -87,6 +88,7 @@ int SQLiteStatement::prepare() ...@@ -87,6 +88,7 @@ int SQLiteStatement::prepare()
int SQLiteStatement::step() int SQLiteStatement::step()
{ {
ThreadState::SafePointScope scope(ThreadState::HeapPointersOnStack);
MutexLocker databaseLock(m_database.databaseMutex()); MutexLocker databaseLock(m_database.databaseMutex());
if (m_database.isInterrupted()) if (m_database.isInterrupted())
return SQLITE_INTERRUPT; return SQLITE_INTERRUPT;
......
...@@ -104,7 +104,7 @@ static Mutex& threadAttachMutex() ...@@ -104,7 +104,7 @@ static Mutex& threadAttachMutex()
static double lockingTimeout() static double lockingTimeout()
{ {
// Wait time for parking all threads is at most 500 MS. // Wait time for parking all threads is at most 100 MS.
return 0.100; return 0.100;
} }
...@@ -186,10 +186,17 @@ public: ...@@ -186,10 +186,17 @@ public:
ASSERT(ThreadState::current()->isAtSafePoint()); ASSERT(ThreadState::current()->isAtSafePoint());
} }
void checkAndPark(ThreadState* state) void checkAndPark(ThreadState* state, SafePointAwareMutexLocker* locker = 0)
{ {
ASSERT(!state->isSweepInProgress()); ASSERT(!state->isSweepInProgress());
if (!acquireLoad(&m_canResume)) { if (!acquireLoad(&m_canResume)) {
// If we are leaving the safepoint from a SafePointAwareMutexLocker
// call out to release the lock before going to sleep. This enables the
// lock to be acquired in the sweep phase, e.g. during weak processing
// or finalization. The SafePointAwareLocker will reenter the safepoint
// and reacquire the lock after leaving this safepoint.
if (locker)
locker->reset();
pushAllRegisters(this, state, parkAfterPushRegisters); pushAllRegisters(this, state, parkAfterPushRegisters);
state->performPendingSweep(); state->performPendingSweep();
} }
...@@ -201,10 +208,10 @@ public: ...@@ -201,10 +208,10 @@ public:
pushAllRegisters(this, state, enterSafePointAfterPushRegisters); pushAllRegisters(this, state, enterSafePointAfterPushRegisters);
} }
void leaveSafePoint(ThreadState* state) void leaveSafePoint(ThreadState* state, SafePointAwareMutexLocker* locker = 0)
{ {
if (atomicIncrement(&m_unparkedThreadCount) > 0) if (atomicIncrement(&m_unparkedThreadCount) > 0)
checkAndPark(state); checkAndPark(state, locker);
} }
private: private:
...@@ -745,8 +752,11 @@ void ThreadState::safePoint(StackState stackState) ...@@ -745,8 +752,11 @@ void ThreadState::safePoint(StackState stackState)
{ {
checkThread(); checkThread();
performPendingGC(stackState); performPendingGC(stackState);
ASSERT(!m_atSafePoint);
m_stackState = stackState; m_stackState = stackState;
m_atSafePoint = true;
s_safePointBarrier->checkAndPark(this); s_safePointBarrier->checkAndPark(this);
m_atSafePoint = false;
m_stackState = HeapPointersOnStack; m_stackState = HeapPointersOnStack;
} }
...@@ -791,11 +801,11 @@ void ThreadState::enterSafePoint(StackState stackState, void* scopeMarker) ...@@ -791,11 +801,11 @@ void ThreadState::enterSafePoint(StackState stackState, void* scopeMarker)
s_safePointBarrier->enterSafePoint(this); s_safePointBarrier->enterSafePoint(this);
} }
void ThreadState::leaveSafePoint() void ThreadState::leaveSafePoint(SafePointAwareMutexLocker* locker)
{ {
checkThread(); checkThread();
ASSERT(m_atSafePoint); ASSERT(m_atSafePoint);
s_safePointBarrier->leaveSafePoint(this); s_safePointBarrier->leaveSafePoint(this, locker);
m_atSafePoint = false; m_atSafePoint = false;
m_stackState = HeapPointersOnStack; m_stackState = HeapPointersOnStack;
clearSafePointScopeMarker(); clearSafePointScopeMarker();
......
...@@ -52,6 +52,7 @@ class HeapObjectHeader; ...@@ -52,6 +52,7 @@ class HeapObjectHeader;
class PersistentNode; class PersistentNode;
class Visitor; class Visitor;
class SafePointBarrier; class SafePointBarrier;
class SafePointAwareMutexLocker;
template<typename Header> class ThreadHeap; template<typename Header> class ThreadHeap;
class CallbackStack; class CallbackStack;
...@@ -369,7 +370,7 @@ public: ...@@ -369,7 +370,7 @@ public:
// Mark current thread as running inside safepoint. // Mark current thread as running inside safepoint.
void enterSafePointWithoutPointers() { enterSafePoint(NoHeapPointersOnStack, 0); } void enterSafePointWithoutPointers() { enterSafePoint(NoHeapPointersOnStack, 0); }
void enterSafePointWithPointers(void* scopeMarker) { enterSafePoint(HeapPointersOnStack, scopeMarker); } void enterSafePointWithPointers(void* scopeMarker) { enterSafePoint(HeapPointersOnStack, scopeMarker); }
void leaveSafePoint(); void leaveSafePoint(SafePointAwareMutexLocker* = 0);
bool isAtSafePoint() const { return m_atSafePoint; } bool isAtSafePoint() const { return m_atSafePoint; }
class SafePointScope { class SafePointScope {
...@@ -510,6 +511,7 @@ private: ...@@ -510,6 +511,7 @@ private:
~ThreadState(); ~ThreadState();
friend class SafePointBarrier; friend class SafePointBarrier;
friend class SafePointAwareMutexLocker;
void enterSafePoint(StackState, void*); void enterSafePoint(StackState, void*);
NO_SANITIZE_ADDRESS void copyStackUntilSafePointScope(); NO_SANITIZE_ADDRESS void copyStackUntilSafePointScope();
...@@ -601,6 +603,55 @@ public: ...@@ -601,6 +603,55 @@ public:
static ThreadState* state() { return ThreadState::current(); } static ThreadState* state() { return ThreadState::current(); }
}; };
// The SafePointAwareMutexLocker is used to enter a safepoint while waiting for
// a mutex lock. It also ensures that the lock is not held while waiting for a GC
// to complete in the leaveSafePoint method, by releasing the lock if the
// leaveSafePoint method cannot complete without blocking, see
// SafePointBarrier::checkAndPark.
class SafePointAwareMutexLocker {
WTF_MAKE_NONCOPYABLE(SafePointAwareMutexLocker);
public:
explicit SafePointAwareMutexLocker(Mutex& mutex) : m_mutex(mutex), m_locked(false)
{
ThreadState* state = ThreadState::current();
do {
bool leaveSafePoint = false;
if (!state->isAtSafePoint()) {
state->enterSafePoint(ThreadState::HeapPointersOnStack, this);
leaveSafePoint = true;
}
m_mutex.lock();
m_locked = true;
if (leaveSafePoint) {
// When leaving the safepoint we might end up release the mutex
// if another thread is requesting a GC, see
// SafePointBarrier::checkAndPark. This is the case where we
// loop around to reacquire the lock.
state->leaveSafePoint(this);
}
} while (!m_locked);
}
~SafePointAwareMutexLocker()
{
ASSERT(m_locked);
m_mutex.unlock();
}
private:
friend class SafePointBarrier;
void reset()
{
ASSERT(m_locked);
m_mutex.unlock();
m_locked = false;
}
Mutex& m_mutex;
bool m_locked;
};
} }
#endif // ThreadState_h #endif // ThreadState_h
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment