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