blob: 8525752e633479530ef14ecb74f85baf68d9c55b [file] [log] [blame]
/*
* Copyright (C) 2019 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "StorageQuotaManager.h"
#include "Logging.h"
#include "StorageQuotaUser.h"
namespace WebCore {
StorageQuotaManager::~StorageQuotaManager()
{
while (!m_pendingRequests.isEmpty())
m_pendingRequests.takeFirst().callback(Decision::Deny);
}
uint64_t StorageQuotaManager::spaceUsage() const
{
uint64_t usage = 0;
for (auto& user : m_users)
usage += user->spaceUsed();
return usage;
}
void StorageQuotaManager::updateQuotaBasedOnSpaceUsage()
{
if (!m_quota)
return;
auto defaultQuotaStep = m_quota / 10;
m_quota = std::max(m_quota, defaultQuotaStep * ((spaceUsage() / defaultQuotaStep) + 1));
}
void StorageQuotaManager::initializeUsersIfNeeded()
{
if (m_pendingInitializationUsers.isEmpty())
return;
Vector<StorageQuotaUser*> usersToInitialize;
for (auto& keyValue : m_pendingInitializationUsers) {
if (keyValue.value == WhenInitializedCalled::No) {
keyValue.value = WhenInitializedCalled::Yes;
usersToInitialize.append(keyValue.key);
}
}
for (auto* user : usersToInitialize) {
if (m_pendingInitializationUsers.contains(user))
askUserToInitialize(*user);
}
}
void StorageQuotaManager::askUserToInitialize(StorageQuotaUser& user)
{
user.whenInitialized([this, &user, weakThis = makeWeakPtr(this)]() {
if (!weakThis)
return;
if (m_pendingInitializationUsers.remove(&user))
m_users.add(&user);
if (!m_pendingInitializationUsers.isEmpty())
return;
// Make sure quota is set before handling first request.
if (m_state == State::Uninitialized) {
updateQuotaBasedOnSpaceUsage();
m_state = State::MakingDecisionForRequest;
}
processPendingRequests({ });
});
}
void StorageQuotaManager::addUser(StorageQuotaUser& user)
{
ASSERT(!m_pendingInitializationUsers.contains(&user));
ASSERT(!m_users.contains(&user));
m_pendingInitializationUsers.add(&user, WhenInitializedCalled::No);
if (!m_pendingRequests.isEmpty())
askUserToInitialize(user);
}
bool StorageQuotaManager::shouldAskForMoreSpace(uint64_t spaceIncrease) const
{
if (!spaceIncrease)
return false;
return spaceUsage() + spaceIncrease > m_quota;
}
void StorageQuotaManager::removeUser(StorageQuotaUser& user)
{
ASSERT(m_users.contains(&user) || m_pendingInitializationUsers.contains(&user));
m_users.remove(&user);
if (m_pendingInitializationUsers.remove(&user) && m_pendingInitializationUsers.isEmpty()) {
// When being cleared, quota users may remove themselves and add themselves to trigger reinitialization.
// Let's wait for addUser to be called before processing pending requests.
callOnMainThread([this, weakThis = makeWeakPtr(this)] {
if (!weakThis)
return;
if (m_pendingInitializationUsers.isEmpty())
this->processPendingRequests({ });
});
}
}
void StorageQuotaManager::requestSpace(uint64_t spaceIncrease, RequestCallback&& callback)
{
if (!m_pendingRequests.isEmpty()) {
m_pendingRequests.append({ spaceIncrease, WTFMove(callback) });
return;
}
if (!spaceIncrease) {
callback(Decision::Grant);
return;
}
initializeUsersIfNeeded();
if (!m_pendingInitializationUsers.isEmpty()) {
m_pendingRequests.append({ spaceIncrease, WTFMove(callback) });
return;
}
if (shouldAskForMoreSpace(spaceIncrease)) {
m_pendingRequests.append({ spaceIncrease, WTFMove(callback) });
// Try processing request again after making sure usage is accurate.
m_state = State::ComputingSpaceUsed;
for (auto& user : copyToVector(m_users))
user->computeSpaceUsed();
if (!m_pendingInitializationUsers.isEmpty())
return;
m_state = State::AskingForMoreSpace;
askForMoreSpace(spaceIncrease);
return;
}
m_state = State::MakingDecisionForRequest;
callback(Decision::Grant);
}
void StorageQuotaManager::askForMoreSpace(uint64_t spaceIncrease)
{
ASSERT(shouldAskForMoreSpace(spaceIncrease));
ASSERT(m_state == State::AskingForMoreSpace);
RELEASE_LOG(Storage, "%p - StorageQuotaManager::askForMoreSpace %" PRIu64, this, spaceIncrease);
m_state = State::WaitingForSpaceIncreaseResponse;
m_spaceIncreaseRequester(m_quota, spaceUsage(), spaceIncrease, [this, weakThis = makeWeakPtr(*this)](Optional<uint64_t> newQuota) {
if (!weakThis)
return;
RELEASE_LOG(Storage, "%p - StorageQuotaManager::askForMoreSpace received response %" PRIu64, this, newQuota ? *newQuota : 0);
m_state = State::AskingForMoreSpace;
processPendingRequests(newQuota);
});
}
void StorageQuotaManager::processPendingRequests(Optional<uint64_t> newQuota)
{
if (m_pendingRequests.isEmpty())
return;
if (newQuota)
m_quota = *newQuota;
if (m_state == State::WaitingForSpaceIncreaseResponse)
return;
if (!m_pendingInitializationUsers.isEmpty())
return;
if (m_state == State::AskingForMoreSpace) {
auto request = m_pendingRequests.takeFirst();
bool shouldAllowRequest = !shouldAskForMoreSpace(request.spaceIncrease);
RELEASE_LOG(Storage, "%p - StorageQuotaManager::processPendingRequests first request decision is %d", this, shouldAllowRequest);
m_state = State::MakingDecisionForRequest;
request.callback(shouldAllowRequest ? Decision::Grant : Decision::Deny);
}
while (!m_pendingRequests.isEmpty()) {
auto& request = m_pendingRequests.first();
if (shouldAskForMoreSpace(request.spaceIncrease)) {
if (m_state == State::MakingDecisionForRequest) {
m_state = State::ComputingSpaceUsed;
for (auto& user : copyToVector(m_users))
user->computeSpaceUsed();
if (!m_pendingInitializationUsers.isEmpty())
return;
}
m_state = State::AskingForMoreSpace;
uint64_t spaceIncrease = 0;
for (auto& pendingRequest : m_pendingRequests)
spaceIncrease += pendingRequest.spaceIncrease;
askForMoreSpace(spaceIncrease);
return;
}
m_state = State::MakingDecisionForRequest;
m_pendingRequests.takeFirst().callback(Decision::Grant);
}
}
} // namespace WebCore