1 | /* |
2 | * Copyright (C) 2019 Apple Inc. All Rights Reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "StorageQuotaManager.h" |
28 | |
29 | #include "StorageQuotaUser.h" |
30 | |
31 | namespace WebCore { |
32 | |
33 | StorageQuotaManager::~StorageQuotaManager() |
34 | { |
35 | while (!m_pendingRequests.isEmpty()) |
36 | m_pendingRequests.takeFirst().callback(Decision::Deny); |
37 | } |
38 | |
39 | uint64_t StorageQuotaManager::spaceUsage() const |
40 | { |
41 | uint64_t usage = 0; |
42 | for (auto& user : m_users) |
43 | usage += user->spaceUsed(); |
44 | return usage; |
45 | } |
46 | |
47 | void StorageQuotaManager::updateQuotaBasedOnSpaceUsage() |
48 | { |
49 | if (!m_quota) |
50 | return; |
51 | |
52 | auto defaultQuotaStep = m_quota / 10; |
53 | m_quota = std::max(m_quota, defaultQuotaStep * ((spaceUsage() / defaultQuotaStep) + 1)); |
54 | } |
55 | |
56 | void StorageQuotaManager::initializeUsersIfNeeded() |
57 | { |
58 | if (m_pendingInitializationUsers.isEmpty()) |
59 | return; |
60 | |
61 | Vector<StorageQuotaUser*> usersToInitialize; |
62 | for (auto& keyValue : m_pendingInitializationUsers) { |
63 | if (keyValue.value == WhenInitializedCalled::No) { |
64 | keyValue.value = WhenInitializedCalled::Yes; |
65 | usersToInitialize.append(keyValue.key); |
66 | } |
67 | } |
68 | for (auto* user : usersToInitialize) { |
69 | if (m_pendingInitializationUsers.contains(user)) |
70 | askUserToInitialize(*user); |
71 | } |
72 | } |
73 | |
74 | void StorageQuotaManager::askUserToInitialize(StorageQuotaUser& user) |
75 | { |
76 | user.whenInitialized([this, &user, weakThis = makeWeakPtr(this)]() { |
77 | if (!weakThis) |
78 | return; |
79 | |
80 | if (m_pendingInitializationUsers.remove(&user)) |
81 | m_users.add(&user); |
82 | |
83 | if (!m_pendingInitializationUsers.isEmpty()) |
84 | return; |
85 | |
86 | updateQuotaBasedOnSpaceUsage(); |
87 | processPendingRequests({ }, ShouldDequeueFirstPendingRequest::No); |
88 | }); |
89 | } |
90 | |
91 | void StorageQuotaManager::addUser(StorageQuotaUser& user) |
92 | { |
93 | ASSERT(!m_pendingInitializationUsers.contains(&user)); |
94 | ASSERT(!m_users.contains(&user)); |
95 | m_pendingInitializationUsers.add(&user, WhenInitializedCalled::No); |
96 | |
97 | if (!m_pendingRequests.isEmpty()) |
98 | askUserToInitialize(user); |
99 | } |
100 | |
101 | bool StorageQuotaManager::shouldAskForMoreSpace(uint64_t spaceIncrease) const |
102 | { |
103 | if (!spaceIncrease) |
104 | return false; |
105 | |
106 | return spaceUsage() + spaceIncrease > m_quota; |
107 | } |
108 | |
109 | void StorageQuotaManager::removeUser(StorageQuotaUser& user) |
110 | { |
111 | ASSERT(m_users.contains(&user) || m_pendingInitializationUsers.contains(&user)); |
112 | m_users.remove(&user); |
113 | if (m_pendingInitializationUsers.remove(&user) && m_pendingInitializationUsers.isEmpty()) { |
114 | // When being cleared, quota users may remove themselves and add themselves to trigger reinitialization. |
115 | // Let's wait for addUser to be called before processing pending requests. |
116 | callOnMainThread([this, weakThis = makeWeakPtr(this)] { |
117 | if (!weakThis) |
118 | return; |
119 | |
120 | if (m_pendingInitializationUsers.isEmpty()) |
121 | this->processPendingRequests({ }, ShouldDequeueFirstPendingRequest::No); |
122 | }); |
123 | } |
124 | } |
125 | |
126 | void StorageQuotaManager::requestSpace(uint64_t spaceIncrease, RequestCallback&& callback) |
127 | { |
128 | if (!m_pendingRequests.isEmpty()) { |
129 | m_pendingRequests.append({ spaceIncrease, WTFMove(callback) }); |
130 | return; |
131 | } |
132 | |
133 | if (!spaceIncrease) { |
134 | callback(Decision::Grant); |
135 | return; |
136 | } |
137 | |
138 | initializeUsersIfNeeded(); |
139 | |
140 | if (!m_pendingInitializationUsers.isEmpty()) { |
141 | m_pendingRequests.append({ spaceIncrease, WTFMove(callback) }); |
142 | return; |
143 | } |
144 | |
145 | if (shouldAskForMoreSpace(spaceIncrease)) { |
146 | m_pendingRequests.append({ spaceIncrease, WTFMove(callback) }); |
147 | askForMoreSpace(spaceIncrease); |
148 | return; |
149 | } |
150 | |
151 | callback(Decision::Grant); |
152 | } |
153 | |
154 | void StorageQuotaManager::askForMoreSpace(uint64_t spaceIncrease) |
155 | { |
156 | ASSERT(shouldAskForMoreSpace(spaceIncrease)); |
157 | ASSERT(!m_isWaitingForSpaceIncreaseResponse); |
158 | m_isWaitingForSpaceIncreaseResponse = true; |
159 | m_spaceIncreaseRequester(m_quota, spaceUsage(), spaceIncrease, [this, weakThis = makeWeakPtr(*this)](Optional<uint64_t> newQuota) { |
160 | if (!weakThis) |
161 | return; |
162 | m_isWaitingForSpaceIncreaseResponse = false; |
163 | processPendingRequests(newQuota, ShouldDequeueFirstPendingRequest::Yes); |
164 | }); |
165 | } |
166 | |
167 | void StorageQuotaManager::processPendingRequests(Optional<uint64_t> newQuota, ShouldDequeueFirstPendingRequest shouldDequeueFirstPendingRequest) |
168 | { |
169 | if (m_pendingRequests.isEmpty()) |
170 | return; |
171 | |
172 | if (newQuota) |
173 | m_quota = *newQuota; |
174 | |
175 | if (m_isWaitingForSpaceIncreaseResponse) |
176 | return; |
177 | |
178 | if (!m_pendingInitializationUsers.isEmpty()) |
179 | return; |
180 | |
181 | if (shouldDequeueFirstPendingRequest == ShouldDequeueFirstPendingRequest::Yes) { |
182 | auto request = m_pendingRequests.takeFirst(); |
183 | auto decision = shouldAskForMoreSpace(request.spaceIncrease) ? Decision::Deny : Decision::Grant; |
184 | request.callback(decision); |
185 | } |
186 | |
187 | while (!m_pendingRequests.isEmpty()) { |
188 | auto& request = m_pendingRequests.first(); |
189 | |
190 | if (shouldAskForMoreSpace(request.spaceIncrease)) { |
191 | uint64_t spaceIncrease = 0; |
192 | for (auto& request : m_pendingRequests) |
193 | spaceIncrease += request.spaceIncrease; |
194 | askForMoreSpace(spaceIncrease); |
195 | return; |
196 | } |
197 | |
198 | m_pendingRequests.takeFirst().callback(Decision::Grant); |
199 | } |
200 | } |
201 | |
202 | } // namespace WebCore |
203 | |