michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "QuotaObject.h" michael@0: michael@0: #include "QuotaManager.h" michael@0: #include "Utilities.h" michael@0: michael@0: USING_QUOTA_NAMESPACE michael@0: michael@0: void michael@0: QuotaObject::AddRef() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: if (!quotaManager) { michael@0: NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); michael@0: michael@0: ++mRefCnt; michael@0: michael@0: return; michael@0: } michael@0: michael@0: MutexAutoLock lock(quotaManager->mQuotaMutex); michael@0: michael@0: ++mRefCnt; michael@0: } michael@0: michael@0: void michael@0: QuotaObject::Release() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: if (!quotaManager) { michael@0: NS_ERROR("Null quota manager, this shouldn't happen, possible leak!"); michael@0: michael@0: nsrefcnt count = --mRefCnt; michael@0: if (count == 0) { michael@0: mRefCnt = 1; michael@0: delete this; michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: { michael@0: MutexAutoLock lock(quotaManager->mQuotaMutex); michael@0: michael@0: --mRefCnt; michael@0: michael@0: if (mRefCnt > 0) { michael@0: return; michael@0: } michael@0: michael@0: if (mOriginInfo) { michael@0: mOriginInfo->mQuotaObjects.Remove(mPath); michael@0: } michael@0: } michael@0: michael@0: delete this; michael@0: } michael@0: michael@0: void michael@0: QuotaObject::UpdateSize(int64_t aSize) michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: MutexAutoLock lock(quotaManager->mQuotaMutex); michael@0: michael@0: if (!mOriginInfo) { michael@0: return; michael@0: } michael@0: michael@0: GroupInfo* groupInfo = mOriginInfo->mGroupInfo; michael@0: michael@0: if (groupInfo->IsForTemporaryStorage()) { michael@0: quotaManager->mTemporaryStorageUsage -= mSize; michael@0: } michael@0: groupInfo->mUsage -= mSize; michael@0: mOriginInfo->mUsage -= mSize; michael@0: michael@0: mSize = aSize; michael@0: michael@0: mOriginInfo->mUsage += mSize; michael@0: groupInfo->mUsage += mSize; michael@0: if (groupInfo->IsForTemporaryStorage()) { michael@0: quotaManager->mTemporaryStorageUsage += mSize; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: QuotaObject::MaybeAllocateMoreSpace(int64_t aOffset, int32_t aCount) michael@0: { michael@0: int64_t end = aOffset + aCount; michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: MutexAutoLock lock(quotaManager->mQuotaMutex); michael@0: michael@0: if (mSize >= end || !mOriginInfo) { michael@0: return true; michael@0: } michael@0: michael@0: GroupInfo* groupInfo = mOriginInfo->mGroupInfo; michael@0: michael@0: if (groupInfo->IsForPersistentStorage()) { michael@0: uint64_t newUsage = mOriginInfo->mUsage - mSize + end; michael@0: michael@0: if (newUsage > mOriginInfo->mLimit) { michael@0: // This will block the thread, but it will also drop the mutex while michael@0: // waiting. The mutex will be reacquired again when the waiting is michael@0: // finished. michael@0: if (!quotaManager->LockedQuotaIsLifted()) { michael@0: return false; michael@0: } michael@0: michael@0: // Threads raced, the origin info removal has been done by some other michael@0: // thread. michael@0: if (!mOriginInfo) { michael@0: // The other thread could allocate more space. michael@0: if (end > mSize) { michael@0: mSize = end; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsCString group = mOriginInfo->mGroupInfo->mGroup; michael@0: nsCString origin = mOriginInfo->mOrigin; michael@0: michael@0: mOriginInfo->LockedClearOriginInfos(); michael@0: NS_ASSERTION(!mOriginInfo, michael@0: "Should have cleared in LockedClearOriginInfos!"); michael@0: michael@0: quotaManager->LockedRemoveQuotaForOrigin(PERSISTENCE_TYPE_PERSISTENT, michael@0: group, origin); michael@0: michael@0: // Some other thread could increase the size without blocking (increasing michael@0: // the origin usage without hitting the limit), but no more than this one. michael@0: NS_ASSERTION(mSize < end, "This shouldn't happen!"); michael@0: michael@0: mSize = end; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: mOriginInfo->mUsage = newUsage; michael@0: michael@0: groupInfo->mUsage = groupInfo->mUsage - mSize + end; michael@0: michael@0: mSize = end; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: NS_ASSERTION(groupInfo->mPersistenceType == PERSISTENCE_TYPE_TEMPORARY, michael@0: "Huh?"); michael@0: michael@0: uint64_t delta = end - mSize; michael@0: michael@0: uint64_t newUsage = mOriginInfo->mUsage + delta; michael@0: michael@0: // Temporary storage has no limit for origin usage (there's a group and the michael@0: // global limit though). michael@0: michael@0: uint64_t newGroupUsage = groupInfo->mUsage + delta; michael@0: michael@0: // Temporary storage has a hard limit for group usage (20 % of the global michael@0: // limit). michael@0: if (newGroupUsage > quotaManager->GetGroupLimit()) { michael@0: return false; michael@0: } michael@0: michael@0: uint64_t newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + michael@0: delta; michael@0: michael@0: if (newTemporaryStorageUsage > quotaManager->mTemporaryStorageLimit) { michael@0: // This will block the thread without holding the lock while waitting. michael@0: michael@0: nsAutoTArray originInfos; michael@0: uint64_t sizeToBeFreed = michael@0: quotaManager->LockedCollectOriginsForEviction(delta, originInfos); michael@0: michael@0: if (!sizeToBeFreed) { michael@0: return false; michael@0: } michael@0: michael@0: NS_ASSERTION(sizeToBeFreed >= delta, "Huh?"); michael@0: michael@0: { michael@0: MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); michael@0: michael@0: for (uint32_t i = 0; i < originInfos.Length(); i++) { michael@0: quotaManager->DeleteTemporaryFilesForOrigin(originInfos[i]->mOrigin); michael@0: } michael@0: } michael@0: michael@0: // Relocked. michael@0: michael@0: NS_ASSERTION(mOriginInfo, "How come?!"); michael@0: michael@0: nsTArray origins; michael@0: for (uint32_t i = 0; i < originInfos.Length(); i++) { michael@0: OriginInfo* originInfo = originInfos[i]; michael@0: michael@0: NS_ASSERTION(originInfo != mOriginInfo, "Deleted itself!"); michael@0: michael@0: nsCString group = originInfo->mGroupInfo->mGroup; michael@0: nsCString origin = originInfo->mOrigin; michael@0: quotaManager->LockedRemoveQuotaForOrigin(PERSISTENCE_TYPE_TEMPORARY, michael@0: group, origin); michael@0: michael@0: #ifdef DEBUG michael@0: originInfos[i] = nullptr; michael@0: #endif michael@0: michael@0: origins.AppendElement(origin); michael@0: } michael@0: michael@0: // We unlocked and relocked several times so we need to recompute all the michael@0: // essential variables and recheck the group limit. michael@0: michael@0: delta = end - mSize; michael@0: michael@0: newUsage = mOriginInfo->mUsage + delta; michael@0: michael@0: newGroupUsage = groupInfo->mUsage + delta; michael@0: michael@0: if (newGroupUsage > quotaManager->GetGroupLimit()) { michael@0: // Unfortunately some other thread increased the group usage in the michael@0: // meantime and we are not below the group limit anymore. michael@0: michael@0: // However, the origin eviction must be finalized in this case too. michael@0: MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); michael@0: michael@0: quotaManager->FinalizeOriginEviction(origins); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: newTemporaryStorageUsage = quotaManager->mTemporaryStorageUsage + delta; michael@0: michael@0: NS_ASSERTION(newTemporaryStorageUsage <= michael@0: quotaManager->mTemporaryStorageLimit, "How come?!"); michael@0: michael@0: // Ok, we successfully freed enough space and the operation can continue michael@0: // without throwing the quota error. michael@0: michael@0: mOriginInfo->mUsage = newUsage; michael@0: groupInfo->mUsage = newGroupUsage; michael@0: quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage;; michael@0: michael@0: // Some other thread could increase the size in the meantime, but no more michael@0: // than this one. michael@0: NS_ASSERTION(mSize < end, "This shouldn't happen!"); michael@0: mSize = end; michael@0: michael@0: // Finally, release IO thread only objects and allow next synchronized michael@0: // ops for the evicted origins. michael@0: MutexAutoUnlock autoUnlock(quotaManager->mQuotaMutex); michael@0: michael@0: quotaManager->FinalizeOriginEviction(origins); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: mOriginInfo->mUsage = newUsage; michael@0: groupInfo->mUsage = newGroupUsage; michael@0: quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage; michael@0: michael@0: mSize = end; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: OriginInfo::LockedDecreaseUsage(int64_t aSize) michael@0: { michael@0: AssertCurrentThreadOwnsQuotaMutex(); michael@0: michael@0: mUsage -= aSize; michael@0: michael@0: mGroupInfo->mUsage -= aSize; michael@0: michael@0: if (mGroupInfo->IsForTemporaryStorage()) { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: quotaManager->mTemporaryStorageUsage -= aSize; michael@0: } michael@0: } michael@0: michael@0: // static michael@0: PLDHashOperator michael@0: OriginInfo::ClearOriginInfoCallback(const nsAString& aKey, michael@0: QuotaObject* aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: michael@0: aValue->mOriginInfo = nullptr; michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: already_AddRefed michael@0: GroupInfo::LockedGetOriginInfo(const nsACString& aOrigin) michael@0: { michael@0: AssertCurrentThreadOwnsQuotaMutex(); michael@0: michael@0: for (uint32_t index = 0; index < mOriginInfos.Length(); index++) { michael@0: nsRefPtr& originInfo = mOriginInfos[index]; michael@0: michael@0: if (originInfo->mOrigin == aOrigin) { michael@0: nsRefPtr result = originInfo; michael@0: return result.forget(); michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: GroupInfo::LockedAddOriginInfo(OriginInfo* aOriginInfo) michael@0: { michael@0: AssertCurrentThreadOwnsQuotaMutex(); michael@0: michael@0: NS_ASSERTION(!mOriginInfos.Contains(aOriginInfo), michael@0: "Replacing an existing entry!"); michael@0: mOriginInfos.AppendElement(aOriginInfo); michael@0: michael@0: mUsage += aOriginInfo->mUsage; michael@0: michael@0: if (IsForTemporaryStorage()) { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: quotaManager->mTemporaryStorageUsage += aOriginInfo->mUsage; michael@0: } michael@0: } michael@0: michael@0: void michael@0: GroupInfo::LockedRemoveOriginInfo(const nsACString& aOrigin) michael@0: { michael@0: AssertCurrentThreadOwnsQuotaMutex(); michael@0: michael@0: for (uint32_t index = 0; index < mOriginInfos.Length(); index++) { michael@0: if (mOriginInfos[index]->mOrigin == aOrigin) { michael@0: mUsage -= mOriginInfos[index]->mUsage; michael@0: michael@0: if (IsForTemporaryStorage()) { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: quotaManager->mTemporaryStorageUsage -= mOriginInfos[index]->mUsage; michael@0: } michael@0: michael@0: mOriginInfos.RemoveElementAt(index); michael@0: michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: GroupInfo::LockedRemoveOriginInfos() michael@0: { michael@0: AssertCurrentThreadOwnsQuotaMutex(); michael@0: michael@0: for (uint32_t index = mOriginInfos.Length(); index > 0; index--) { michael@0: mUsage -= mOriginInfos[index - 1]->mUsage; michael@0: michael@0: if (IsForTemporaryStorage()) { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: quotaManager->mTemporaryStorageUsage -= mOriginInfos[index - 1]->mUsage; michael@0: } michael@0: michael@0: mOriginInfos.RemoveElementAt(index - 1); michael@0: } michael@0: } michael@0: michael@0: void michael@0: GroupInfo::LockedRemoveOriginInfosForPattern(const nsACString& aPattern) michael@0: { michael@0: AssertCurrentThreadOwnsQuotaMutex(); michael@0: michael@0: for (uint32_t index = mOriginInfos.Length(); index > 0; index--) { michael@0: if (PatternMatchesOrigin(aPattern, mOriginInfos[index - 1]->mOrigin)) { michael@0: mUsage -= mOriginInfos[index - 1]->mUsage; michael@0: michael@0: if (IsForTemporaryStorage()) { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: quotaManager->mTemporaryStorageUsage -= mOriginInfos[index - 1]->mUsage; michael@0: } michael@0: michael@0: mOriginInfos.RemoveElementAt(index - 1); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsRefPtr& michael@0: GroupInfoPair::GetGroupInfoForPersistenceType(PersistenceType aPersistenceType) michael@0: { michael@0: switch (aPersistenceType) { michael@0: case PERSISTENCE_TYPE_PERSISTENT: michael@0: return mPersistentStorageGroupInfo; michael@0: case PERSISTENCE_TYPE_TEMPORARY: michael@0: return mTemporaryStorageGroupInfo; michael@0: michael@0: case PERSISTENCE_TYPE_INVALID: michael@0: default: michael@0: MOZ_CRASH("Bad persistence type value!"); michael@0: return mPersistentStorageGroupInfo; michael@0: } michael@0: }