mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-11 12:25:53 +00:00
Bug 697247: Part 2 - Rework IDB synchronization mechanisms. r=bent
This commit is contained in:
parent
9951eda078
commit
3140fc9fd4
@ -559,9 +559,7 @@ TransactionPoolEventTarget::Dispatch(nsIRunnable* aRunnable,
|
||||
NS_ASSERTION(aRunnable, "Null pointer!");
|
||||
NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "Unsupported!");
|
||||
|
||||
TransactionThreadPool* pool = TransactionThreadPool::Get();
|
||||
NS_ASSERTION(pool, "This should never be null!");
|
||||
|
||||
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
|
||||
return pool->Dispatch(mTransaction, aRunnable, false, nsnull);
|
||||
}
|
||||
|
||||
|
@ -206,7 +206,7 @@ CheckPermissionsHelper::Observe(nsISupports* aSubject,
|
||||
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
|
||||
NS_ASSERTION(mgr, "This should never be null!");
|
||||
|
||||
rv = mgr->WaitForOpenAllowed(mName, mASCIIOrigin, this);
|
||||
rv = NS_DispatchToCurrentThread(this);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
|
@ -64,25 +64,21 @@ public:
|
||||
|
||||
CheckPermissionsHelper(OpenDatabaseHelper* aHelper,
|
||||
nsIDOMWindow* aWindow,
|
||||
const nsAString& aName,
|
||||
const nsACString& aASCIIOrigin)
|
||||
: mHelper(aHelper),
|
||||
mWindow(aWindow),
|
||||
mName(aName),
|
||||
mASCIIOrigin(aASCIIOrigin),
|
||||
mHasPrompted(false),
|
||||
mPromptResult(0)
|
||||
{
|
||||
NS_ASSERTION(aHelper, "Null pointer!");
|
||||
NS_ASSERTION(aWindow, "Null pointer!");
|
||||
NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
|
||||
NS_ASSERTION(!aASCIIOrigin.IsEmpty(), "Empty origin!");
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<OpenDatabaseHelper> mHelper;
|
||||
nsCOMPtr<nsIDOMWindow> mWindow;
|
||||
nsString mName;
|
||||
nsCString mASCIIOrigin;
|
||||
bool mHasPrompted;
|
||||
PRUint32 mPromptResult;
|
||||
|
@ -450,10 +450,6 @@ IDBDatabase::ExitSetVersionTransaction()
|
||||
|
||||
NS_ASSERTION(dbInfo->runningVersionChange, "How did that happen?");
|
||||
dbInfo->runningVersionChange = false;
|
||||
|
||||
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
|
||||
NS_ASSERTION(manager, "We should always have a manager here");
|
||||
manager->UnblockSetVersionRunnable(this);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -440,12 +440,12 @@ IDBFactory::Open(const nsAString& aName,
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
|
||||
nsRefPtr<CheckPermissionsHelper> permissionHelper =
|
||||
new CheckPermissionsHelper(openHelper, window, aName, origin);
|
||||
new CheckPermissionsHelper(openHelper, window, origin);
|
||||
|
||||
nsRefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::GetOrCreate();
|
||||
NS_ENSURE_TRUE(mgr, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
|
||||
rv = mgr->WaitForOpenAllowed(aName, origin, permissionHelper);
|
||||
rv = mgr->WaitForOpenAllowed(origin, openHelper->Id(), permissionHelper);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
|
||||
request.forget(_retval);
|
||||
|
@ -143,134 +143,6 @@ EnumerateToTArray(const nsACString& aKey,
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
// Responsible for calling IDBDatabase.setVersion after a pending version change
|
||||
// transaction has completed.
|
||||
class DelayedSetVersion : public nsRunnable
|
||||
{
|
||||
public:
|
||||
DelayedSetVersion(IDBDatabase* aDatabase,
|
||||
IDBOpenDBRequest* aRequest,
|
||||
PRInt64 aOldVersion,
|
||||
PRInt64 aNewVersion,
|
||||
AsyncConnectionHelper* aHelper)
|
||||
: mDatabase(aDatabase),
|
||||
mRequest(aRequest),
|
||||
mOldVersion(aOldVersion),
|
||||
mNewVersion(aNewVersion),
|
||||
mHelper(aHelper)
|
||||
{
|
||||
NS_ASSERTION(aDatabase, "Null database!");
|
||||
NS_ASSERTION(aRequest, "Null request!");
|
||||
NS_ASSERTION(aHelper, "Null helper!");
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
|
||||
NS_ASSERTION(mgr, "This should never be null!");
|
||||
|
||||
nsresult rv = mgr->SetDatabaseVersion(mDatabase, mRequest,
|
||||
mOldVersion, mNewVersion,
|
||||
mHelper);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<IDBDatabase> mDatabase;
|
||||
nsRefPtr<IDBOpenDBRequest> mRequest;
|
||||
PRInt64 mOldVersion;
|
||||
PRInt64 mNewVersion;
|
||||
nsRefPtr<AsyncConnectionHelper> mHelper;
|
||||
};
|
||||
|
||||
// Responsible for firing "versionchange" events at all live and non-closed
|
||||
// databases, and for firing a "blocked" event at the requesting database if any
|
||||
// databases fail to close.
|
||||
class VersionChangeEventsRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
VersionChangeEventsRunnable(
|
||||
IDBDatabase* aRequestingDatabase,
|
||||
IDBOpenDBRequest* aRequest,
|
||||
nsTArray<nsRefPtr<IDBDatabase> >& aWaitingDatabases,
|
||||
PRInt64 aOldVersion,
|
||||
PRInt64 aNewVersion)
|
||||
: mRequestingDatabase(aRequestingDatabase),
|
||||
mRequest(aRequest),
|
||||
mOldVersion(aOldVersion),
|
||||
mNewVersion(aNewVersion)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(aRequestingDatabase, "Null pointer!");
|
||||
NS_ASSERTION(aRequest, "Null pointer!");
|
||||
|
||||
if (!mWaitingDatabases.SwapElements(aWaitingDatabases)) {
|
||||
NS_ERROR("This should never fail!");
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
// Fire version change events at all of the databases that are not already
|
||||
// closed. Also kick bfcached documents out of bfcache.
|
||||
for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
|
||||
nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
|
||||
|
||||
if (database->IsClosed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// First check if the document the IDBDatabase is part of is bfcached
|
||||
nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
|
||||
nsIBFCacheEntry* bfCacheEntry;
|
||||
if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
|
||||
bfCacheEntry->RemoveFromBFCacheSync();
|
||||
NS_ASSERTION(database->IsClosed(),
|
||||
"Kicking doc out of bfcache should have closed database");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise fire a versionchange event.
|
||||
nsRefPtr<nsDOMEvent> event =
|
||||
IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
|
||||
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
|
||||
|
||||
bool dummy;
|
||||
database->DispatchEvent(event, &dummy);
|
||||
}
|
||||
|
||||
// Now check to see if any didn't close. If there are some running still
|
||||
// then fire the blocked event.
|
||||
for (PRUint32 index = 0; index < mWaitingDatabases.Length(); index++) {
|
||||
if (!mWaitingDatabases[index]->IsClosed()) {
|
||||
nsRefPtr<nsDOMEvent> event =
|
||||
IDBVersionChangeEvent::CreateBlocked(mOldVersion, mNewVersion);
|
||||
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
|
||||
|
||||
bool dummy;
|
||||
mRequest->DispatchEvent(event, &dummy);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<IDBDatabase> mRequestingDatabase;
|
||||
nsRefPtr<IDBOpenDBRequest> mRequest;
|
||||
nsTArray<nsRefPtr<IDBDatabase> > mWaitingDatabases;
|
||||
PRInt64 mOldVersion;
|
||||
PRInt64 mNewVersion;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
IndexedDatabaseManager::IndexedDatabaseManager()
|
||||
@ -425,20 +297,6 @@ IndexedDatabaseManager::UnregisterDatabase(IDBDatabase* aDatabase)
|
||||
NS_ERROR("Didn't know anything about this database!");
|
||||
}
|
||||
|
||||
void
|
||||
IndexedDatabaseManager::OnOriginClearComplete(OriginClearRunnable* aRunnable)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(aRunnable, "Null pointer!");
|
||||
NS_ASSERTION(!aRunnable->mThread, "Thread should be null!");
|
||||
NS_ASSERTION(aRunnable->mDelayedRunnables.IsEmpty(),
|
||||
"Delayed runnables should have been dispatched already!");
|
||||
|
||||
if (!mOriginClearRunnables.RemoveElement(aRunnable)) {
|
||||
NS_ERROR("Don't know anything about this runnable!");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IndexedDatabaseManager::OnUsageCheckComplete(AsyncUsageRunnable* aRunnable)
|
||||
{
|
||||
@ -452,64 +310,136 @@ IndexedDatabaseManager::OnUsageCheckComplete(AsyncUsageRunnable* aRunnable)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IndexedDatabaseManager::OnSetVersionRunnableComplete(
|
||||
SetVersionRunnable* aRunnable)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(aRunnable, "Null pointer!");
|
||||
NS_ASSERTION(aRunnable->mDelayedRunnables.IsEmpty(),
|
||||
"Delayed runnables should have been dispatched already!");
|
||||
|
||||
// Remove this runnable from the list. This will allow other databases to
|
||||
// begin to request version changes.
|
||||
if (!mSetVersionRunnables.RemoveElement(aRunnable)) {
|
||||
NS_ERROR("Don't know anything about this runnable!");
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
IndexedDatabaseManager::WaitForOpenAllowed(const nsAString& aName,
|
||||
const nsACString& aOrigin,
|
||||
IndexedDatabaseManager::WaitForOpenAllowed(const nsACString& aOrigin,
|
||||
nsIAtom* aId,
|
||||
nsIRunnable* aRunnable)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(!aName.IsEmpty(), "Empty name!");
|
||||
NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
|
||||
NS_ASSERTION(aRunnable, "Null pointer!");
|
||||
|
||||
// See if we're currently clearing database files for this origin. If so then
|
||||
// queue the runnable for later dispatch after we're done clearing.
|
||||
PRUint32 count = mOriginClearRunnables.Length();
|
||||
nsAutoPtr<SynchronizedOp> op(new SynchronizedOp(aOrigin, aId));
|
||||
|
||||
// See if this runnable needs to wait.
|
||||
bool delayed = false;
|
||||
for (PRUint32 index = mSynchronizedOps.Length(); index > 0; index--) {
|
||||
nsAutoPtr<SynchronizedOp>& existingOp = mSynchronizedOps[index - 1];
|
||||
if (op->MustWaitFor(*existingOp)) {
|
||||
existingOp->DelayRunnable(aRunnable);
|
||||
delayed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, dispatch it immediately.
|
||||
if (!delayed) {
|
||||
nsresult rv = NS_DispatchToCurrentThread(aRunnable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Adding this to the synchronized ops list will block any additional
|
||||
// ops from proceeding until this one is done.
|
||||
mSynchronizedOps.AppendElement(op.forget());
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
IndexedDatabaseManager::AllowNextSynchronizedOp(const nsACString& aOrigin,
|
||||
nsIAtom* aId)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(!aOrigin.IsEmpty(), "Empty origin!");
|
||||
|
||||
PRUint32 count = mSynchronizedOps.Length();
|
||||
for (PRUint32 index = 0; index < count; index++) {
|
||||
nsRefPtr<OriginClearRunnable>& data = mOriginClearRunnables[index];
|
||||
if (data->mOrigin == aOrigin) {
|
||||
nsCOMPtr<nsIRunnable>* newPtr =
|
||||
data->mDelayedRunnables.AppendElement(aRunnable);
|
||||
NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
|
||||
nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
|
||||
if (op->mOrigin.Equals(aOrigin)) {
|
||||
if (op->mId == aId) {
|
||||
NS_ASSERTION(op->mDatabases.IsEmpty(), "How did this happen?");
|
||||
|
||||
return NS_OK;
|
||||
op->DispatchDelayedRunnables();
|
||||
|
||||
mSynchronizedOps.RemoveElementAt(index);
|
||||
return;
|
||||
}
|
||||
|
||||
// If one or the other is for an origin clear, we should have matched
|
||||
// solely on origin.
|
||||
NS_ASSERTION(op->mId && aId, "Why didn't we match earlier?");
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if we're currently doing a SetVersion transaction for this
|
||||
// database. If so then we delay this runnable for later.
|
||||
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
|
||||
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
|
||||
if (runnable->mRequestingDatabase->Name() == aName &&
|
||||
runnable->mRequestingDatabase->Origin() == aOrigin) {
|
||||
nsCOMPtr<nsIRunnable>* newPtr =
|
||||
runnable->mDelayedRunnables.AppendElement(aRunnable);
|
||||
NS_ENSURE_TRUE(newPtr, NS_ERROR_OUT_OF_MEMORY);
|
||||
NS_NOTREACHED("Why didn't we find a SynchronizedOp?");
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
nsresult
|
||||
IndexedDatabaseManager::AcquireExclusiveAccess(const nsACString& aOrigin,
|
||||
IDBDatabase* aDatabase,
|
||||
AsyncConnectionHelper* aHelper,
|
||||
WaitingOnDatabasesCallback aCallback,
|
||||
void* aClosure)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(aHelper, "Why are you talking to me?");
|
||||
|
||||
// Find the right SynchronizedOp.
|
||||
SynchronizedOp* op = nsnull;
|
||||
PRUint32 count = mSynchronizedOps.Length();
|
||||
for (PRUint32 index = 0; index < count; index++) {
|
||||
SynchronizedOp* currentop = mSynchronizedOps[index].get();
|
||||
if (currentop->mOrigin.Equals(aOrigin)) {
|
||||
if (!currentop->mId ||
|
||||
(aDatabase && currentop->mId == aDatabase->Id())) {
|
||||
// We've found the right one.
|
||||
NS_ASSERTION(!currentop->mHelper,
|
||||
"SynchronizedOp already has a helper?!?");
|
||||
op = currentop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We aren't currently clearing databases for this origin and we're not
|
||||
// running a SetVersion transaction for this database so dispatch the runnable
|
||||
// immediately.
|
||||
return NS_DispatchToCurrentThread(aRunnable);
|
||||
NS_ASSERTION(op, "We didn't find a SynchronizedOp?");
|
||||
|
||||
nsTArray<IDBDatabase*>* array;
|
||||
mLiveDatabases.Get(aOrigin, &array);
|
||||
|
||||
// We need to wait for the databases to go away.
|
||||
// Hold on to all database objects that represent the same database file
|
||||
// (except the one that is requesting this version change).
|
||||
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
|
||||
|
||||
if (array) {
|
||||
PRUint32 count = array->Length();
|
||||
for (PRUint32 index = 0; index < count; index++) {
|
||||
IDBDatabase*& database = array->ElementAt(index);
|
||||
if (!database->IsClosed() &&
|
||||
(!aDatabase ||
|
||||
(aDatabase &&
|
||||
database != aDatabase &&
|
||||
database->Id() == aDatabase->Id()))) {
|
||||
liveDatabases.AppendElement(database);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (liveDatabases.IsEmpty()) {
|
||||
IndexedDatabaseManager::DispatchHelper(aHelper);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ASSERTION(op->mDatabases.IsEmpty(), "How do we already have databases here?");
|
||||
op->mDatabases.AppendElements(liveDatabases);
|
||||
op->mHelper = aHelper;
|
||||
|
||||
// Give our callback the databases so it can decide what to do with them.
|
||||
aCallback(liveDatabases, aClosure);
|
||||
|
||||
NS_ASSERTION(liveDatabases.IsEmpty(),
|
||||
"Should have done something with the array!");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
@ -519,107 +449,6 @@ IndexedDatabaseManager::IsShuttingDown()
|
||||
return !!gShutdown;
|
||||
}
|
||||
|
||||
nsresult
|
||||
IndexedDatabaseManager::SetDatabaseVersion(IDBDatabase* aDatabase,
|
||||
IDBOpenDBRequest* aRequest,
|
||||
PRInt64 aOldVersion,
|
||||
PRInt64 aNewVersion,
|
||||
AsyncConnectionHelper* aHelper)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(aDatabase, "Null pointer!");
|
||||
NS_ASSERTION(aHelper, "Null pointer!");
|
||||
|
||||
nsresult rv;
|
||||
|
||||
// See if another database has already asked to change the version.
|
||||
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
|
||||
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
|
||||
if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
|
||||
if (runnable->mRequestingDatabase == aDatabase) {
|
||||
// Same database, just queue this call to run after the current
|
||||
// SetVersion transaction completes.
|
||||
nsRefPtr<DelayedSetVersion> delayed =
|
||||
new DelayedSetVersion(aDatabase, aRequest, aOldVersion, aNewVersion,
|
||||
aHelper);
|
||||
if (!runnable->mDelayedRunnables.AppendElement(delayed)) {
|
||||
NS_WARNING("Out of memory!");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Different database, we can't let this one succeed.
|
||||
aHelper->SetError(NS_ERROR_DOM_INDEXEDDB_DEADLOCK_ERR);
|
||||
|
||||
rv = NS_DispatchToCurrentThread(aHelper);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Grab all live databases for the same origin.
|
||||
nsTArray<IDBDatabase*>* array;
|
||||
if (!mLiveDatabases.Get(aDatabase->Origin(), &array)) {
|
||||
NS_ERROR("Must have some alive if we've got a live argument!");
|
||||
}
|
||||
|
||||
// Hold on to all database objects that represent the same database file
|
||||
// (except the one that is requesting this version change).
|
||||
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
|
||||
|
||||
for (PRUint32 index = 0; index < array->Length(); index++) {
|
||||
IDBDatabase*& database = array->ElementAt(index);
|
||||
if (database != aDatabase &&
|
||||
database->Id() == aDatabase->Id() &&
|
||||
!database->IsClosed() &&
|
||||
!liveDatabases.AppendElement(database)) {
|
||||
NS_WARNING("Out of memory?");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
// Adding an element to this array here will keep other databases from
|
||||
// requesting a version change.
|
||||
nsRefPtr<SetVersionRunnable> runnable =
|
||||
new SetVersionRunnable(aDatabase, liveDatabases);
|
||||
if (!mSetVersionRunnables.AppendElement(runnable)) {
|
||||
NS_WARNING("Out of memory!");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
|
||||
|
||||
// When all databases are closed we want to dispatch the SetVersion
|
||||
// transaction to the transaction pool.
|
||||
runnable->mHelper = aHelper;
|
||||
|
||||
if (runnable->mDatabases.IsEmpty()) {
|
||||
// There are no other databases that need to be closed. Go ahead and run
|
||||
// the transaction now.
|
||||
RunSetVersionTransaction(aDatabase);
|
||||
}
|
||||
else {
|
||||
// Otherwise we need to wait for all the other databases to complete.
|
||||
// Schedule a version change events runnable .
|
||||
nsTArray<nsRefPtr<IDBDatabase> > waitingDatabases;
|
||||
if (!waitingDatabases.AppendElements(runnable->mDatabases)) {
|
||||
NS_WARNING("Out of memory!");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nsRefPtr<VersionChangeEventsRunnable> eventsRunnable =
|
||||
new VersionChangeEventsRunnable(aDatabase, aRequest, waitingDatabases,
|
||||
aOldVersion, aNewVersion);
|
||||
|
||||
rv = NS_DispatchToCurrentThread(eventsRunnable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow)
|
||||
{
|
||||
@ -676,71 +505,49 @@ IndexedDatabaseManager::OnDatabaseClosed(IDBDatabase* aDatabase)
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(aDatabase, "Null pointer!");
|
||||
|
||||
// Check through the list of SetVersionRunnables we have amassed to see if
|
||||
// this database is part of a SetVersion callback.
|
||||
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
|
||||
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
|
||||
// Check through the list of SynchronizedOps to see if any are waiting for
|
||||
// this database to close before proceeding.
|
||||
PRUint32 count = mSynchronizedOps.Length();
|
||||
for (PRUint32 index = 0; index < count; index++) {
|
||||
nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
|
||||
|
||||
if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
|
||||
// This is the SetVersionRunnable for the given database file. Remove the
|
||||
// database from the list of databases that need to be closed. Since we
|
||||
// use this hook for SetVersion requests that don't actually need to wait
|
||||
// for other databases the mDatabases array may be empty.
|
||||
if (!runnable->mDatabases.IsEmpty() &&
|
||||
!runnable->mDatabases.RemoveElement(aDatabase)) {
|
||||
NS_ERROR("Didn't have this database in our list!");
|
||||
}
|
||||
if (op->mOrigin == aDatabase->Origin() &&
|
||||
(op->mId == aDatabase->Id() || !op->mId)) {
|
||||
// This database is in the scope of this SynchronizedOp. Remove it
|
||||
// from the list if necessary.
|
||||
if (op->mDatabases.RemoveElement(aDatabase)) {
|
||||
// Now set up the helper if there are no more live databases.
|
||||
NS_ASSERTION(op->mHelper, "How did we get rid of the helper before "
|
||||
"removing the last database?");
|
||||
if (op->mDatabases.IsEmpty()) {
|
||||
// At this point, all databases are closed, so no new transactions
|
||||
// can be started. There may, however, still be outstanding
|
||||
// transactions that have not completed. We need to wait for those
|
||||
// before we dispatch the helper.
|
||||
|
||||
// Now run the helper if there are no more live databases.
|
||||
if (runnable->mHelper && runnable->mDatabases.IsEmpty()) {
|
||||
// At this point, all databases are closed, so no new transactions can
|
||||
// be started. There may, however, still be outstanding transactions
|
||||
// that have not completed. We need to wait for those before we
|
||||
// dispatch the helper.
|
||||
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
|
||||
if (!pool) {
|
||||
NS_ERROR("IndexedDB is totally broken.");
|
||||
return;
|
||||
}
|
||||
|
||||
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
|
||||
nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
|
||||
new WaitForTransactionsToFinishRunnable(op);
|
||||
|
||||
nsRefPtr<WaitForTransactionsToFinishRunnable> waitRunnable =
|
||||
new WaitForTransactionsToFinishRunnable(runnable);
|
||||
nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
|
||||
array.AppendElement(aDatabase);
|
||||
|
||||
// All other databases should be closed, so we only need to wait on this
|
||||
// one.
|
||||
nsAutoTArray<nsRefPtr<IDBDatabase>, 1> array;
|
||||
if (!array.AppendElement(aDatabase)) {
|
||||
NS_ERROR("This should never fail!");
|
||||
}
|
||||
|
||||
// Use the WaitForTransactionsToFinishRunnable as the callback.
|
||||
if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
|
||||
NS_WARNING("Failed to wait for transaction to complete!");
|
||||
// Use the WaitForTransactionsToFinishRunnable as the callback.
|
||||
if (!pool->WaitForAllDatabasesToComplete(array, waitRunnable)) {
|
||||
NS_WARNING("Failed to wait for transaction to complete!");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IndexedDatabaseManager::UnblockSetVersionRunnable(IDBDatabase* aDatabase)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(aDatabase, "Null pointer!");
|
||||
|
||||
// Check through the list of SetVersionRunnables to find the one we're seeking.
|
||||
for (PRUint32 index = 0; index < mSetVersionRunnables.Length(); index++) {
|
||||
nsRefPtr<SetVersionRunnable>& runnable = mSetVersionRunnables[index];
|
||||
|
||||
if (runnable->mRequestingDatabase->Id() == aDatabase->Id()) {
|
||||
NS_ASSERTION(!runnable->mHelper,
|
||||
"Why are we unblocking a runnable if the helper didn't run?");
|
||||
NS_DispatchToCurrentThread(runnable);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NS_NOTREACHED("How did we get here!");
|
||||
}
|
||||
|
||||
// static
|
||||
bool
|
||||
IndexedDatabaseManager::SetCurrentDatabase(IDBDatabase* aDatabase)
|
||||
@ -855,6 +662,45 @@ IndexedDatabaseManager::EnsureQuotaManagementForDirectory(nsIFile* aDirectory)
|
||||
return rv;
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
IndexedDatabaseManager::DispatchHelper(AsyncConnectionHelper* aHelper)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
// If the helper has a transaction, dispatch it to the transaction
|
||||
// threadpool.
|
||||
if (aHelper->HasTransaction()) {
|
||||
rv = aHelper->DispatchToTransactionPool();
|
||||
}
|
||||
else {
|
||||
// Otherwise, dispatch it to the IO thread.
|
||||
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
|
||||
NS_ASSERTION(manager, "We should definitely have a manager here");
|
||||
|
||||
rv = aHelper->Dispatch(manager->IOThread());
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool
|
||||
IndexedDatabaseManager::IsClearOriginPending(const nsACString& origin)
|
||||
{
|
||||
// Iterate through our SynchronizedOps to see if we have an entry that matches
|
||||
// this origin and has no id.
|
||||
PRUint32 count = mSynchronizedOps.Length();
|
||||
for (PRUint32 index = 0; index < count; index++) {
|
||||
nsAutoPtr<SynchronizedOp>& op = mSynchronizedOps[index];
|
||||
if (op->mOrigin.Equals(origin) && !op->mId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS2(IndexedDatabaseManager, nsIIndexedDatabaseManager,
|
||||
nsIObserver)
|
||||
|
||||
@ -890,12 +736,10 @@ IndexedDatabaseManager::GetUsageForURI(
|
||||
|
||||
// See if we're currently clearing the databases for this origin. If so then
|
||||
// we pretend that we've already deleted everything.
|
||||
for (PRUint32 index = 0; index < mOriginClearRunnables.Length(); index++) {
|
||||
if (mOriginClearRunnables[index]->mOrigin == origin) {
|
||||
rv = NS_DispatchToCurrentThread(runnable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
if (IsClearOriginPending(origin)) {
|
||||
rv = NS_DispatchToCurrentThread(runnable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Otherwise dispatch to the IO thread to actually compute the usage.
|
||||
@ -949,15 +793,20 @@ IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If we're already clearing out files for this origin then return
|
||||
// If there is a pending or running clear operation for this origin, return
|
||||
// immediately.
|
||||
PRUint32 clearDataCount = mOriginClearRunnables.Length();
|
||||
for (PRUint32 index = 0; index < clearDataCount; index++) {
|
||||
if (mOriginClearRunnables[index]->mOrigin == origin) {
|
||||
return NS_OK;
|
||||
}
|
||||
if (IsClearOriginPending(origin)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Queue up the origin clear runnable.
|
||||
nsRefPtr<OriginClearRunnable> runnable =
|
||||
new OriginClearRunnable(origin, mIOThread);
|
||||
|
||||
rv = WaitForOpenAllowed(origin, nsnull, runnable);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Give the runnable some help by invalidating any databases in the way.
|
||||
// We need to grab references to any live databases here to prevent them from
|
||||
// dying while we invalidate them.
|
||||
nsTArray<nsRefPtr<IDBDatabase> > liveDatabases;
|
||||
@ -965,25 +814,7 @@ IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
|
||||
// Grab all live databases for this origin.
|
||||
nsTArray<IDBDatabase*>* array;
|
||||
if (mLiveDatabases.Get(origin, &array)) {
|
||||
if (!liveDatabases.AppendElements(*array)) {
|
||||
NS_WARNING("Out of memory?");
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
nsRefPtr<OriginClearRunnable> runnable =
|
||||
new OriginClearRunnable(origin, mIOThread);
|
||||
|
||||
// Make a new entry for this origin in mOriginClearRunnables.
|
||||
nsRefPtr<OriginClearRunnable>* newRunnable =
|
||||
mOriginClearRunnables.AppendElement(runnable);
|
||||
NS_ENSURE_TRUE(newRunnable, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
if (liveDatabases.IsEmpty()) {
|
||||
rv = runnable->Run();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
liveDatabases.AppendElements(*array);
|
||||
}
|
||||
|
||||
// Invalidate all the live databases first.
|
||||
@ -991,16 +822,8 @@ IndexedDatabaseManager::ClearDatabasesForURI(nsIURI* aURI)
|
||||
liveDatabases[index]->Invalidate();
|
||||
}
|
||||
|
||||
// Now set up our callback so that we know when they have finished.
|
||||
TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
|
||||
NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE);
|
||||
|
||||
if (!pool->WaitForAllDatabasesToComplete(liveDatabases, runnable)) {
|
||||
NS_WARNING("Can't wait on databases!");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
NS_ASSERTION(liveDatabases.IsEmpty(), "Should have swapped!");
|
||||
// After everything has been invalidated the helper should be dispatched to
|
||||
// the end of the event queue.
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1077,7 +900,7 @@ NS_IMETHODIMP
|
||||
IndexedDatabaseManager::OriginClearRunnable::Run()
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
// On the first time on the main thread we simply dispatch to the IO thread.
|
||||
// On the first time on the main thread we dispatch to the IO thread.
|
||||
if (mFirstCallback) {
|
||||
NS_ASSERTION(mThread, "Should have a thread here!");
|
||||
|
||||
@ -1097,19 +920,11 @@ IndexedDatabaseManager::OriginClearRunnable::Run()
|
||||
|
||||
NS_ASSERTION(!mThread, "Should have been cleared already!");
|
||||
|
||||
// Dispatch any queued runnables that we collected while we were waiting.
|
||||
for (PRUint32 index = 0; index < mDelayedRunnables.Length(); index++) {
|
||||
if (NS_FAILED(NS_DispatchToCurrentThread(mDelayedRunnables[index]))) {
|
||||
NS_WARNING("Failed to dispatch delayed runnable!");
|
||||
}
|
||||
}
|
||||
mDelayedRunnables.Clear();
|
||||
|
||||
// Tell the IndexedDatabaseManager that we're done.
|
||||
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
|
||||
if (mgr) {
|
||||
mgr->OnOriginClearComplete(this);
|
||||
}
|
||||
NS_ASSERTION(mgr, "This should never fail!");
|
||||
|
||||
mgr->AllowNextSynchronizedOp(mOrigin, nsnull);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -1257,50 +1072,6 @@ IndexedDatabaseManager::AsyncUsageRunnable::Run()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
IndexedDatabaseManager::SetVersionRunnable::SetVersionRunnable(
|
||||
IDBDatabase* aDatabase,
|
||||
nsTArray<nsRefPtr<IDBDatabase> >& aDatabases)
|
||||
: mRequestingDatabase(aDatabase)
|
||||
{
|
||||
NS_ASSERTION(aDatabase, "Null database!");
|
||||
if (!mDatabases.SwapElements(aDatabases)) {
|
||||
NS_ERROR("This should never fail!");
|
||||
}
|
||||
}
|
||||
|
||||
IndexedDatabaseManager::SetVersionRunnable::~SetVersionRunnable()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_ISUPPORTS1(IndexedDatabaseManager::SetVersionRunnable, nsIRunnable)
|
||||
|
||||
NS_IMETHODIMP
|
||||
IndexedDatabaseManager::SetVersionRunnable::Run()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(!mHelper, "Should have been cleared already!");
|
||||
|
||||
// Dispatch any queued runnables that we picked up while waiting for the
|
||||
// SetVersion transaction to complete.
|
||||
for (PRUint32 index = 0; index < mDelayedRunnables.Length(); index++) {
|
||||
if (NS_FAILED(NS_DispatchToCurrentThread(mDelayedRunnables[index]))) {
|
||||
NS_WARNING("Failed to dispatch delayed runnable!");
|
||||
}
|
||||
}
|
||||
|
||||
// No need to hold these alive any longer.
|
||||
mDelayedRunnables.Clear();
|
||||
|
||||
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
|
||||
NS_ASSERTION(mgr, "This should never be null!");
|
||||
|
||||
// Let the IndexedDatabaseManager know that the SetVersion transaction has
|
||||
// completed.
|
||||
mgr->OnSetVersionRunnableComplete(this);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::WaitForTransactionsToFinishRunnable,
|
||||
nsIRunnable)
|
||||
|
||||
@ -1308,31 +1079,84 @@ NS_IMETHODIMP
|
||||
IndexedDatabaseManager::WaitForTransactionsToFinishRunnable::Run()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(mOp && mOp->mHelper, "What?");
|
||||
|
||||
// Don't hold the callback alive longer than necessary.
|
||||
nsRefPtr<AsyncConnectionHelper> helper;
|
||||
helper.swap(mRunnable->mHelper);
|
||||
helper.swap(mOp->mHelper);
|
||||
|
||||
nsRefPtr<SetVersionRunnable> runnable;
|
||||
runnable.swap(mRunnable);
|
||||
mOp = nsnull;
|
||||
|
||||
// If the helper has a transaction, dispatch it to the transaction
|
||||
// threadpool.
|
||||
if (helper->HasTransaction()) {
|
||||
if (NS_FAILED(helper->DispatchToTransactionPool())) {
|
||||
NS_WARNING("Failed to dispatch to thread pool!");
|
||||
}
|
||||
}
|
||||
// Otherwise, dispatch it to the IO thread.
|
||||
else {
|
||||
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
|
||||
NS_ASSERTION(manager, "We should definitely have a manager here");
|
||||
|
||||
helper->Dispatch(manager->IOThread());
|
||||
}
|
||||
IndexedDatabaseManager::DispatchHelper(helper);
|
||||
|
||||
// The helper is responsible for calling
|
||||
// IndexedDatabaseManager::UnblockSetVersionRunnable.
|
||||
// IndexedDatabaseManager::AllowNextSynchronizedOp.
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
IndexedDatabaseManager::SynchronizedOp::SynchronizedOp(const nsACString& aOrigin,
|
||||
nsIAtom* aId)
|
||||
: mOrigin(aOrigin), mId(aId)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
MOZ_COUNT_CTOR(IndexedDatabaseManager::SynchronizedOp);
|
||||
}
|
||||
|
||||
IndexedDatabaseManager::SynchronizedOp::~SynchronizedOp()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
MOZ_COUNT_DTOR(IndexedDatabaseManager::SynchronizedOp);
|
||||
}
|
||||
|
||||
bool
|
||||
IndexedDatabaseManager::SynchronizedOp::MustWaitFor(const SynchronizedOp& aRhs)
|
||||
const
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
// If the origins don't match, the second can proceed.
|
||||
if (!aRhs.mOrigin.Equals(mOrigin)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the origins and the ids match, the second must wait.
|
||||
if (aRhs.mId == mId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Waiting is required if either one corresponds to an origin clearing
|
||||
// (a null Id).
|
||||
if (!aRhs.mId || !mId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, things for the same origin but different databases can proceed
|
||||
// independently.
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
IndexedDatabaseManager::SynchronizedOp::DelayRunnable(nsIRunnable* aRunnable)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(mDelayedRunnables.IsEmpty() || !mId,
|
||||
"Only ClearOrigin operations can delay multiple runnables!");
|
||||
|
||||
mDelayedRunnables.AppendElement(aRunnable);
|
||||
}
|
||||
|
||||
void
|
||||
IndexedDatabaseManager::SynchronizedOp::DispatchDelayedRunnables()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(!mHelper, "Any helper should be gone by now!");
|
||||
|
||||
PRUint32 count = mDelayedRunnables.Length();
|
||||
for (PRUint32 index = 0; index < count; index++) {
|
||||
NS_DispatchToCurrentThread(mDelayedRunnables[index]);
|
||||
}
|
||||
|
||||
mDelayedRunnables.Clear();
|
||||
}
|
||||
|
@ -81,11 +81,14 @@ public:
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
// Waits for databases to be cleared and for version change transactions to
|
||||
// complete before dispatching the give runnable.
|
||||
nsresult WaitForOpenAllowed(const nsAString& aName,
|
||||
const nsACString& aOrigin,
|
||||
// complete before dispatching the given runnable.
|
||||
nsresult WaitForOpenAllowed(const nsACString& aOrigin,
|
||||
nsIAtom* aId,
|
||||
nsIRunnable* aRunnable);
|
||||
|
||||
void AllowNextSynchronizedOp(const nsACString& aOrigin,
|
||||
nsIAtom* aId);
|
||||
|
||||
nsIThread* IOThread()
|
||||
{
|
||||
NS_ASSERTION(mIOThread, "This should never be null!");
|
||||
@ -95,12 +98,28 @@ public:
|
||||
// Returns true if we've begun the shutdown process.
|
||||
static bool IsShuttingDown();
|
||||
|
||||
// Begins the process of setting a database version.
|
||||
nsresult SetDatabaseVersion(IDBDatabase* aDatabase,
|
||||
IDBOpenDBRequest* aRequest,
|
||||
PRInt64 aOldVersion,
|
||||
PRInt64 aNewVersion,
|
||||
AsyncConnectionHelper* aHelper);
|
||||
typedef void (*WaitingOnDatabasesCallback)(nsTArray<nsRefPtr<IDBDatabase> >&, void*);
|
||||
|
||||
// Acquire exclusive access to the database given (waits for all others to
|
||||
// close). If databases need to close first, the callback will be invoked
|
||||
// with an array of said databases.
|
||||
nsresult AcquireExclusiveAccess(IDBDatabase* aDatabase,
|
||||
AsyncConnectionHelper* aHelper,
|
||||
WaitingOnDatabasesCallback aCallback,
|
||||
void* aClosure)
|
||||
{
|
||||
NS_ASSERTION(aDatabase, "Need a DB here!");
|
||||
return AcquireExclusiveAccess(aDatabase->Origin(), aDatabase, aHelper,
|
||||
aCallback, aClosure);
|
||||
}
|
||||
nsresult AcquireExclusiveAccess(const nsACString& aOrigin,
|
||||
AsyncConnectionHelper* aHelper,
|
||||
WaitingOnDatabasesCallback aCallback,
|
||||
void* aClosure)
|
||||
{
|
||||
return AcquireExclusiveAccess(aOrigin, nsnull, aHelper, aCallback,
|
||||
aClosure);
|
||||
}
|
||||
|
||||
// Called when a window is being purged from the bfcache or the user leaves
|
||||
// a page which isn't going into the bfcache. Forces any live database
|
||||
@ -122,6 +141,12 @@ private:
|
||||
IndexedDatabaseManager();
|
||||
~IndexedDatabaseManager();
|
||||
|
||||
nsresult AcquireExclusiveAccess(const nsACString& aOrigin,
|
||||
IDBDatabase* aDatabase,
|
||||
AsyncConnectionHelper* aHelper,
|
||||
WaitingOnDatabasesCallback aCallback,
|
||||
void* aClosure);
|
||||
|
||||
// Called when a database is created.
|
||||
bool RegisterDatabase(IDBDatabase* aDatabase);
|
||||
|
||||
@ -131,21 +156,14 @@ private:
|
||||
// Called when a database has been closed.
|
||||
void OnDatabaseClosed(IDBDatabase* aDatabase);
|
||||
|
||||
// Called when a version change transaction can run immediately.
|
||||
void RunSetVersionTransaction(IDBDatabase* aDatabase)
|
||||
{
|
||||
OnDatabaseClosed(aDatabase);
|
||||
}
|
||||
|
||||
// Responsible for clearing the database files for a particular origin on the
|
||||
// IO thread. Created when nsIIDBIndexedDatabaseManager::ClearDatabasesForURI
|
||||
// is called. Runs three times, first on the main thread, next on the IO
|
||||
// thread, and then finally again on the main thread. While on the IO thread
|
||||
// the runnable will actually remove the origin's database files and the
|
||||
// directory that contains them before dispatching itself back to the main
|
||||
// thread. When on the main thread the runnable will dispatch any queued
|
||||
// runnables and then notify the IndexedDatabaseManager that the job has been
|
||||
// completed.
|
||||
// thread. When back on the main thread the runnable will notify the
|
||||
// IndexedDatabaseManager that the job has been completed.
|
||||
class OriginClearRunnable : public nsIRunnable
|
||||
{
|
||||
public:
|
||||
@ -161,12 +179,10 @@ private:
|
||||
|
||||
nsCString mOrigin;
|
||||
nsCOMPtr<nsIThread> mThread;
|
||||
nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
|
||||
bool mFirstCallback;
|
||||
};
|
||||
|
||||
// Called when OriginClearRunnable has finished its Run() method.
|
||||
inline void OnOriginClearComplete(OriginClearRunnable* aRunnable);
|
||||
bool IsClearOriginPending(const nsACString& origin);
|
||||
|
||||
// Responsible for calculating the amount of space taken up by databases of a
|
||||
// certain origin. Created when nsIIDBIndexedDatabaseManager::GetUsageForURI
|
||||
@ -204,63 +220,59 @@ private:
|
||||
// Called when AsyncUsageRunnable has finished its Run() method.
|
||||
inline void OnUsageCheckComplete(AsyncUsageRunnable* aRunnable);
|
||||
|
||||
void UnblockSetVersionRunnable(IDBDatabase* aDatabase);
|
||||
|
||||
// Responsible for waiting until all databases have been closed before running
|
||||
// the version change transaction. Created when
|
||||
// IndexedDatabaseManager::SetDatabaseVersion is called. Runs only once on the
|
||||
// main thread when the version change transaction has completed.
|
||||
class SetVersionRunnable : public nsIRunnable
|
||||
// A struct that contains the information corresponding to a pending or
|
||||
// running operation that requires synchronization (e.g. opening a db,
|
||||
// clearing dbs for an origin, etc).
|
||||
struct SynchronizedOp
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIRUNNABLE
|
||||
SynchronizedOp(const nsACString& aOrigin, nsIAtom* aId);
|
||||
~SynchronizedOp();
|
||||
|
||||
SetVersionRunnable(IDBDatabase* aDatabase,
|
||||
nsTArray<nsRefPtr<IDBDatabase> >& aDatabases);
|
||||
~SetVersionRunnable();
|
||||
// Test whether the second SynchronizedOp needs to get behind this one.
|
||||
bool MustWaitFor(const SynchronizedOp& aRhs) const;
|
||||
|
||||
nsRefPtr<IDBDatabase> mRequestingDatabase;
|
||||
nsTArray<nsRefPtr<IDBDatabase> > mDatabases;
|
||||
void DelayRunnable(nsIRunnable* aRunnable);
|
||||
void DispatchDelayedRunnables();
|
||||
|
||||
const nsCString mOrigin;
|
||||
nsCOMPtr<nsIAtom> mId;
|
||||
nsRefPtr<AsyncConnectionHelper> mHelper;
|
||||
nsTArray<nsCOMPtr<nsIRunnable> > mDelayedRunnables;
|
||||
nsTArray<nsRefPtr<IDBDatabase> > mDatabases;
|
||||
};
|
||||
|
||||
// Called when SetVersionRunnable has finished its Run() method.
|
||||
inline void OnSetVersionRunnableComplete(SetVersionRunnable* aRunnable);
|
||||
|
||||
|
||||
// A callback runnable used by the TransactionPool when it's safe to proceed
|
||||
// with a SetVersion/DeleteDatabase/etc.
|
||||
class WaitForTransactionsToFinishRunnable : public nsIRunnable
|
||||
{
|
||||
public:
|
||||
WaitForTransactionsToFinishRunnable(SetVersionRunnable* aRunnable)
|
||||
: mRunnable(aRunnable)
|
||||
WaitForTransactionsToFinishRunnable(SynchronizedOp* aOp)
|
||||
: mOp(aOp)
|
||||
{
|
||||
NS_ASSERTION(mRunnable, "Why don't we have a runnable?");
|
||||
NS_ASSERTION(mRunnable->mDatabases.IsEmpty(), "We're here too early!");
|
||||
NS_ASSERTION(mOp, "Why don't we have a runnable?");
|
||||
NS_ASSERTION(mOp->mDatabases.IsEmpty(), "We're here too early!");
|
||||
NS_ASSERTION(mOp->mHelper, "What are we supposed to do when we're done?");
|
||||
}
|
||||
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIRUNNABLE
|
||||
|
||||
private:
|
||||
nsRefPtr<SetVersionRunnable> mRunnable;
|
||||
// The IndexedDatabaseManager holds this alive.
|
||||
SynchronizedOp* mOp;
|
||||
};
|
||||
|
||||
static nsresult DispatchHelper(AsyncConnectionHelper* aHelper);
|
||||
|
||||
// Maintains a list of live databases per origin.
|
||||
nsClassHashtable<nsCStringHashKey, nsTArray<IDBDatabase*> > mLiveDatabases;
|
||||
|
||||
// Maintains a list of origins that are currently being cleared.
|
||||
nsAutoTArray<nsRefPtr<OriginClearRunnable>, 1> mOriginClearRunnables;
|
||||
|
||||
// Maintains a list of origins that we're currently enumerating to gather
|
||||
// usage statistics.
|
||||
nsAutoTArray<nsRefPtr<AsyncUsageRunnable>, 1> mUsageRunnables;
|
||||
|
||||
// Maintains a list of SetVersion calls that are in progress.
|
||||
nsAutoTArray<nsRefPtr<SetVersionRunnable>, 1> mSetVersionRunnables;
|
||||
// Maintains a list of synchronized operatons that are in progress or queued.
|
||||
nsAutoTArray<nsAutoPtr<SynchronizedOp>, 5> mSynchronizedOps;
|
||||
|
||||
// Thread on which IO is performed.
|
||||
nsCOMPtr<nsIThread> mIOThread;
|
||||
|
@ -498,6 +498,9 @@ public:
|
||||
nsresult GetSuccessResult(JSContext* aCx,
|
||||
jsval* aVal);
|
||||
|
||||
static
|
||||
void QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
|
||||
void* aClosure);
|
||||
protected:
|
||||
nsresult DoDatabaseWork(mozIStorageConnection* aConnection);
|
||||
nsresult Init();
|
||||
@ -519,6 +522,91 @@ private:
|
||||
PRUint64 mCurrentVersion;
|
||||
};
|
||||
|
||||
// Responsible for firing "versionchange" events at all live and non-closed
|
||||
// databases, and for firing a "blocked" event at the requesting database if any
|
||||
// databases fail to close.
|
||||
class VersionChangeEventsRunnable : public nsRunnable
|
||||
{
|
||||
public:
|
||||
VersionChangeEventsRunnable(
|
||||
IDBDatabase* aRequestingDatabase,
|
||||
IDBOpenDBRequest* aRequest,
|
||||
nsTArray<nsRefPtr<IDBDatabase> >& aWaitingDatabases,
|
||||
PRInt64 aOldVersion,
|
||||
PRInt64 aNewVersion)
|
||||
: mRequestingDatabase(aRequestingDatabase),
|
||||
mRequest(aRequest),
|
||||
mOldVersion(aOldVersion),
|
||||
mNewVersion(aNewVersion)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(aRequestingDatabase, "Null pointer!");
|
||||
NS_ASSERTION(aRequest, "Null pointer!");
|
||||
|
||||
if (!mWaitingDatabases.SwapElements(aWaitingDatabases)) {
|
||||
NS_ERROR("This should never fail!");
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHOD Run()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
|
||||
// Fire version change events at all of the databases that are not already
|
||||
// closed. Also kick bfcached documents out of bfcache.
|
||||
PRUint32 count = mWaitingDatabases.Length();
|
||||
for (PRUint32 index = 0; index < count; index++) {
|
||||
nsRefPtr<IDBDatabase>& database = mWaitingDatabases[index];
|
||||
|
||||
if (database->IsClosed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// First check if the document the IDBDatabase is part of is bfcached.
|
||||
nsCOMPtr<nsIDocument> ownerDoc = database->GetOwnerDocument();
|
||||
nsIBFCacheEntry* bfCacheEntry;
|
||||
if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) {
|
||||
bfCacheEntry->RemoveFromBFCacheSync();
|
||||
NS_ASSERTION(database->IsClosed(),
|
||||
"Kicking doc out of bfcache should have closed database");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise fire a versionchange event.
|
||||
nsRefPtr<nsDOMEvent> event =
|
||||
IDBVersionChangeEvent::Create(mOldVersion, mNewVersion);
|
||||
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
|
||||
|
||||
bool dummy;
|
||||
database->DispatchEvent(event, &dummy);
|
||||
}
|
||||
|
||||
// Now check to see if any didn't close. If there are some running still
|
||||
// then fire the blocked event.
|
||||
for (PRUint32 index = 0; index < count; index++) {
|
||||
if (!mWaitingDatabases[index]->IsClosed()) {
|
||||
nsRefPtr<nsDOMEvent> event =
|
||||
IDBVersionChangeEvent::CreateBlocked(mOldVersion, mNewVersion);
|
||||
NS_ENSURE_TRUE(event, NS_ERROR_FAILURE);
|
||||
|
||||
bool dummy;
|
||||
mRequest->DispatchEvent(event, &dummy);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
nsRefPtr<IDBDatabase> mRequestingDatabase;
|
||||
nsRefPtr<IDBOpenDBRequest> mRequest;
|
||||
nsTArray<nsRefPtr<IDBDatabase> > mWaitingDatabases;
|
||||
PRInt64 mOldVersion;
|
||||
PRInt64 mNewVersion;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS1(OpenDatabaseHelper, nsIRunnable);
|
||||
@ -694,8 +782,9 @@ OpenDatabaseHelper::StartSetVersion()
|
||||
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
|
||||
NS_ASSERTION(mgr, "This should never be null!");
|
||||
|
||||
rv = mgr->SetDatabaseVersion(mDatabase, mOpenDBRequest, mCurrentVersion,
|
||||
mRequestedVersion, helper);
|
||||
rv = mgr->AcquireExclusiveAccess(mDatabase, helper,
|
||||
&SetVersionHelper::QueueVersionChange,
|
||||
helper);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
||||
|
||||
// The SetVersionHelper is responsible for dispatching us back to the
|
||||
@ -756,6 +845,11 @@ OpenDatabaseHelper::Run()
|
||||
DispatchSuccessEvent();
|
||||
}
|
||||
|
||||
IndexedDatabaseManager* manager = IndexedDatabaseManager::Get();
|
||||
NS_ASSERTION(manager, "This should never be null!");
|
||||
|
||||
manager->AllowNextSynchronizedOp(mASCIIOrigin, mDatabaseId);
|
||||
|
||||
ReleaseMainThreadObjects();
|
||||
|
||||
return NS_OK;
|
||||
@ -1000,6 +1094,27 @@ SetVersionHelper::GetSuccessResult(JSContext* aCx,
|
||||
aVal);
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
SetVersionHelper::QueueVersionChange(nsTArray<nsRefPtr<IDBDatabase> >& aDatabases,
|
||||
void* aClosure)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
||||
NS_ASSERTION(!aDatabases.IsEmpty(), "Why are we here?");
|
||||
|
||||
SetVersionHelper* helper = static_cast<SetVersionHelper*>(aClosure);
|
||||
NS_ASSERTION(helper, "Why don't we have a helper?");
|
||||
|
||||
nsRefPtr<VersionChangeEventsRunnable> eventsRunnable =
|
||||
new VersionChangeEventsRunnable(helper->mOpenHelper->Database(),
|
||||
helper->mOpenRequest,
|
||||
aDatabases,
|
||||
helper->mCurrentVersion,
|
||||
helper->mRequestedVersion);
|
||||
|
||||
NS_DispatchToCurrentThread(eventsRunnable);
|
||||
}
|
||||
|
||||
already_AddRefed<nsDOMEvent>
|
||||
SetVersionHelper::CreateSuccessEvent()
|
||||
{
|
||||
|
@ -86,6 +86,17 @@ public:
|
||||
nsresult NotifySetVersionFinished();
|
||||
void BlockDatabase();
|
||||
|
||||
nsIAtom* Id() const
|
||||
{
|
||||
return mDatabaseId.get();
|
||||
}
|
||||
|
||||
IDBDatabase* Database() const
|
||||
{
|
||||
NS_ASSERTION(mDatabase, "Calling at the wrong time!");
|
||||
return mDatabase;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Methods only called on the main thread
|
||||
nsresult EnsureSuccessResult();
|
||||
|
@ -39,7 +39,7 @@
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(2f182bf1-1542-47fe-b2f7-4b1741b5283c)]
|
||||
[scriptable, uuid(7aad2542-a5cb-4a57-b20c-c7d16b8582ab)]
|
||||
interface nsIIDBDatabaseException : nsISupports
|
||||
{
|
||||
// const unsigned short NO_ERR = 0;
|
||||
@ -52,10 +52,9 @@ interface nsIIDBDatabaseException : nsISupports
|
||||
const unsigned short TRANSACTION_INACTIVE_ERR = 7;
|
||||
const unsigned short ABORT_ERR = 8;
|
||||
const unsigned short READ_ONLY_ERR = 9;
|
||||
const unsigned short RECOVERABLE_ERR = 10;
|
||||
const unsigned short TRANSIENT_ERR = 11;
|
||||
const unsigned short TIMEOUT_ERR = 12;
|
||||
const unsigned short DEADLOCK_ERR = 13;
|
||||
const unsigned short TIMEOUT_ERR = 10;
|
||||
const unsigned short QUOTA_ERR = 11;
|
||||
const unsigned short VERSION_ERR = 12;
|
||||
|
||||
readonly attribute unsigned short code;
|
||||
};
|
||||
|
@ -92,6 +92,7 @@ TEST_FILES = \
|
||||
test_request_readyState.html \
|
||||
test_success_events_after_abort.html \
|
||||
test_third_party.html \
|
||||
test_traffic_jam.html \
|
||||
test_transaction_abort.html \
|
||||
test_transaction_lifetimes.html \
|
||||
test_transaction_lifetimes_nested.html \
|
||||
|
@ -12,10 +12,6 @@
|
||||
<script type="text/javascript;version=1.7">
|
||||
function testSteps()
|
||||
{
|
||||
todo(false, "Reenable me!");
|
||||
finishTest();
|
||||
yield;
|
||||
|
||||
const name = window.location.pathname;
|
||||
|
||||
let request = mozIndexedDB.open(name, 1);
|
||||
@ -79,6 +75,9 @@
|
||||
}
|
||||
|
||||
request2.onupgradeneeded = null;
|
||||
request2.onsuccess = grabEventAndContinueHandler;
|
||||
|
||||
yield;
|
||||
|
||||
finishTest();
|
||||
yield;
|
||||
|
99
dom/indexedDB/test/test_traffic_jam.html
Normal file
99
dom/indexedDB/test/test_traffic_jam.html
Normal file
@ -0,0 +1,99 @@
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Indexed Database Property Test</title>
|
||||
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
|
||||
<script type="text/javascript;version=1.7">
|
||||
function testSteps()
|
||||
{
|
||||
const name = window.location.pathname;
|
||||
|
||||
let requests = [];
|
||||
function doOpen(version, errorCallback, upgradeNeededCallback, successCallback) {
|
||||
let request = mozIndexedDB.open(name, version);
|
||||
request.onerror = errorCallback;
|
||||
request.onupgradeneeded = upgradeNeededCallback;
|
||||
request.onsuccess = successCallback;
|
||||
requests.push(request);
|
||||
}
|
||||
|
||||
doOpen(1, errorHandler, grabEventAndContinueHandler, grabEventAndContinueHandler);
|
||||
doOpen(2, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
|
||||
|
||||
let event = yield;
|
||||
is(event.type, "upgradeneeded", "expect an upgradeneeded event");
|
||||
is(event.target, requests[0], "fired at the right request");
|
||||
|
||||
let db = event.target.result;
|
||||
db.createObjectStore("foo");
|
||||
|
||||
doOpen(3, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
|
||||
doOpen(2, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
|
||||
doOpen(3, errorHandler, unexpectedSuccessHandler, unexpectedSuccessHandler);
|
||||
|
||||
event.target.transaction.oncomplete = grabEventAndContinueHandler;
|
||||
|
||||
event = yield;
|
||||
is(event.type, "complete", "expect a complete event");
|
||||
is(event.target, requests[0].transaction, "expect it to be fired at the transaction");
|
||||
|
||||
event = yield;
|
||||
is(event.type, "success", "expect a success event");
|
||||
is(event.target, requests[0], "fired at the right request");
|
||||
event.target.result.close();
|
||||
|
||||
requests[1].onupgradeneeded = grabEventAndContinueHandler;
|
||||
|
||||
event = yield;
|
||||
is(event.type, "upgradeneeded", "expect an upgradeneeded event");
|
||||
is(event.target, requests[1], "fired at the right request");
|
||||
|
||||
requests[1].onsuccess = grabEventAndContinueHandler;
|
||||
|
||||
event = yield;
|
||||
is(event.type, "success", "expect a success event");
|
||||
is(event.target, requests[1], "fired at the right request");
|
||||
event.target.result.close();
|
||||
|
||||
requests[2].onupgradeneeded = grabEventAndContinueHandler;
|
||||
|
||||
event = yield;
|
||||
is(event.type, "upgradeneeded", "expect an upgradeneeded event");
|
||||
is(event.target, requests[2], "fired at the right request");
|
||||
|
||||
requests[2].onsuccess = grabEventAndContinueHandler;
|
||||
|
||||
event = yield;
|
||||
is(event.type, "success", "expect a success event");
|
||||
is(event.target, requests[2], "fired at the right request");
|
||||
event.target.result.close();
|
||||
|
||||
requests[3].onerror = new ExpectError(IDBDatabaseException.VERSION_ERR);
|
||||
|
||||
event = yield;
|
||||
|
||||
requests[4].onsuccess = grabEventAndContinueHandler;
|
||||
|
||||
event = yield;
|
||||
is(event.type, "success", "expect a success event");
|
||||
is(event.target, requests[4], "fired at the right request");
|
||||
event.target.result.close();
|
||||
|
||||
finishTest();
|
||||
yield;
|
||||
}
|
||||
|
||||
</script>
|
||||
<script type="text/javascript;version=1.7" src="helpers.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body onload="runTest();"></body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user