Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qnetworkaccesscache.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6#include "QtCore/qpointer.h"
7#include "QtCore/qdeadlinetimer.h"
8
9//#define DEBUG_ACCESSCACHE
10
11QT_BEGIN_NAMESPACE
12
13enum ExpiryTimeEnum {
14 ExpiryTime = 120
15};
16
17// idea copied from qcache.h
18struct QNetworkAccessCache::Node
19{
20 QDeadlineTimer timer;
21 QByteArray key;
22
23 Node *previous = nullptr; // "previous" nodes expire "previous"ly (before us)
24 Node *next = nullptr; // "next" nodes expire "next" (after us)
25 CacheableObject *object = nullptr;
26
27 int useCount = 0;
28};
29
31 : expires(options & Option::Expires),
32 shareable(options & Option::Shareable)
33{
34
35}
36
38{
39#if 0 //def QT_DEBUG
40 if (!key.isEmpty() && Ptr()->hasEntry(key))
41 qWarning() << "QNetworkAccessCache: object" << (void*)this << "key" << key
42 << "destroyed without being removed from cache first!";
43#endif
44}
45
50
52{
53 NodeHash hashCopy = hash;
54 hash.clear();
55
56 // remove all entries
57 NodeHash::Iterator it = hashCopy.begin();
58 NodeHash::Iterator end = hashCopy.end();
59 for ( ; it != end; ++it) {
60 (*it)->object->key.clear();
61 (*it)->object->dispose();
62 delete (*it);
63 }
64
65 // now delete:
66 hashCopy.clear();
67
68 timer.stop();
69
70 firstExpiringNode = lastExpiringNode = nullptr;
71}
72
73/*!
74 Appends the entry given by \a key to the end of the linked list.
75 (i.e., makes it the newest entry)
76 */
77void QNetworkAccessCache::linkEntry(const QByteArray &key)
78{
79 Node * const node = hash.value(key);
80 if (!node)
81 return;
82
83 Q_ASSERT(node != firstExpiringNode && node != lastExpiringNode);
84 Q_ASSERT(node->previous == nullptr && node->next == nullptr);
85 Q_ASSERT(node->useCount == 0);
86
87
88 node->timer.setPreciseRemainingTime(node->object->expiryTimeoutSeconds);
89#ifdef DEBUG_ACCESSCACHE
90 qDebug() << "QNetworkAccessCache case trying to insert=" << QString::fromUtf8(key)
91 << node->timer.remainingTime() << "milliseconds";
92 Node *current = lastExpiringNode;
93 while (current) {
94 qDebug() << "QNetworkAccessCache item=" << QString::fromUtf8(current->key)
95 << current->timer.remainingTime() << "milliseconds"
96 << (current == lastExpiringNode ? "[last to expire]" : "")
97 << (current == firstExpiringNode ? "[first to expire]" : "");
98 current = current->previous;
99 }
100#endif
101
102 if (lastExpiringNode) {
103 Q_ASSERT(lastExpiringNode->next == nullptr);
104 if (lastExpiringNode->timer < node->timer) {
105 // Insert as new last-to-expire node.
106 node->previous = lastExpiringNode;
107 lastExpiringNode->next = node;
108 lastExpiringNode = node;
109 } else {
110 // Insert in a sorted way, as different nodes might have had different expiryTimeoutSeconds set.
111 Node *current = lastExpiringNode;
112 while (current->previous != nullptr && current->previous->timer >= node->timer)
113 current = current->previous;
114 node->previous = current->previous;
115 if (node->previous)
116 node->previous->next = node;
117 node->next = current;
118 current->previous = node;
119 if (node->previous == nullptr)
120 firstExpiringNode = node;
121 }
122 } else {
123 // no current last-to-expire node
124 lastExpiringNode = node;
125 }
126 if (!firstExpiringNode) {
127 // there are no entries, so this is the next-to-expire too
128 firstExpiringNode = node;
129 }
130 Q_ASSERT(firstExpiringNode->previous == nullptr);
131 Q_ASSERT(lastExpiringNode->next == nullptr);
132}
133
134/*!
135 Removes the entry pointed by \a key from the linked list.
136 Returns \c true if the entry removed was the next to expire.
137 */
138bool QNetworkAccessCache::unlinkEntry(const QByteArray &key)
139{
140 Node * const node = hash.value(key);
141 if (!node)
142 return false;
143
144 bool wasFirst = false;
145 if (node == firstExpiringNode) {
146 firstExpiringNode = node->next;
147 wasFirst = true;
148 }
149 if (node == lastExpiringNode)
150 lastExpiringNode = node->previous;
151 if (node->previous)
152 node->previous->next = node->next;
153 if (node->next)
154 node->next->previous = node->previous;
155
156 node->next = node->previous = nullptr;
157 return wasFirst;
158}
159
160void QNetworkAccessCache::updateTimer()
161{
162 timer.stop();
163
164 if (!firstExpiringNode)
165 return;
166
167 qint64 interval = firstExpiringNode->timer.remainingTime();
168 if (interval <= 0) {
169 interval = 0;
170 }
171
172 // Plus 10 msec so we don't spam timer events if date comparisons are too fuzzy.
173 // This code used to do (broken) rounding, but for ConnectionCacheExpiryTimeoutSecondsAttribute
174 // to work we cannot do this.
175 // See discussion in https://codereview.qt-project.org/c/qt/qtbase/+/337464
176 timer.start(interval + 10, this);
177}
178
179void QNetworkAccessCache::timerEvent(QTimerEvent *)
180{
181 while (firstExpiringNode && firstExpiringNode->timer.hasExpired()) {
182 Node *next = firstExpiringNode->next;
183 firstExpiringNode->object->dispose();
184 hash.remove(firstExpiringNode->key); // `firstExpiringNode` gets deleted
185 delete firstExpiringNode;
186 firstExpiringNode = next;
187 }
188
189 // fixup the list
190 if (firstExpiringNode)
191 firstExpiringNode->previous = nullptr;
192 else
193 lastExpiringNode = nullptr;
194
195 updateTimer();
196}
197
198void QNetworkAccessCache::addEntry(const QByteArray &key, CacheableObject *entry, qint64 connectionCacheExpiryTimeoutSeconds)
199{
200 Q_ASSERT(!key.isEmpty());
201
202 if (unlinkEntry(key))
203 updateTimer();
204
205 Node *node = hash.value(key);
206 if (!node) {
207 node = new Node;
208 hash.insert(key, node);
209 }
210
211 if (node->useCount)
212 qWarning("QNetworkAccessCache::addEntry: overriding active cache entry '%s'", key.constData());
213 if (node->object)
214 node->object->dispose();
215 node->object = entry;
216 node->object->key = key;
217 if (connectionCacheExpiryTimeoutSeconds > -1) {
218 node->object->expiryTimeoutSeconds = connectionCacheExpiryTimeoutSeconds; // via ConnectionCacheExpiryTimeoutSecondsAttribute
219 } else {
220 node->object->expiryTimeoutSeconds = ExpiryTime;
221 }
222 node->key = key;
223 node->useCount = 1;
224
225 // It gets only put into the expiry list in linkEntry (from releaseEntry), when it is not used anymore.
226}
227
228bool QNetworkAccessCache::hasEntry(const QByteArray &key) const
229{
230 return hash.contains(key);
231}
232
234{
235 Node *node = hash.value(key);
236 if (!node)
237 return nullptr;
238
239 if (node->useCount > 0) {
240 if (node->object->shareable) {
241 ++node->useCount;
242 return node->object;
243 }
244
245 // object in use and not shareable
246 return nullptr;
247 }
248
249 // entry not in use, let the caller have it
250 bool wasNext = unlinkEntry(key);
251 ++node->useCount;
252
253 if (wasNext)
254 updateTimer();
255 return node->object;
256}
257
258void QNetworkAccessCache::releaseEntry(const QByteArray &key)
259{
260 Node *node = hash.value(key);
261 if (!node) {
262 qWarning("QNetworkAccessCache::releaseEntry: trying to release key '%s' that is not in cache", key.constData());
263 return;
264 }
265
266 Q_ASSERT(node->useCount > 0);
267
268 if (!--node->useCount) {
269 // no objects waiting; add it back to the expiry list
270 if (node->object->expires)
271 linkEntry(key);
272
273 if (firstExpiringNode == node)
274 updateTimer();
275 }
276}
277
278void QNetworkAccessCache::removeEntry(const QByteArray &key)
279{
280 Node *node = hash.value(key);
281 if (!node) {
282 qWarning("QNetworkAccessCache::removeEntry: trying to remove key '%s' that is not in cache", key.constData());
283 return;
284 }
285
286 if (unlinkEntry(key))
287 updateTimer();
288 if (node->useCount > 1)
289 qWarning("QNetworkAccessCache::removeEntry: removing active cache entry '%s'",
290 key.constData());
291
292 node->object->key.clear();
293 hash.remove(node->key);
294 delete node;
295}
296
297QT_END_NAMESPACE
298
299#include "moc_qnetworkaccesscache_p.cpp"
QHash< QByteArray, Node * > NodeHash
bool hasEntry(const QByteArray &key) const
void removeEntry(const QByteArray &key)
CacheableObject * requestEntryNow(const QByteArray &key)
void addEntry(const QByteArray &key, CacheableObject *entry, qint64 connectionCacheExpiryTimeoutSeconds=-1)
void releaseEntry(const QByteArray &key)
void timerEvent(QTimerEvent *) override
This event handler can be reimplemented in a subclass to receive timer events for the object.