Squid Web Cache master
Loading...
Searching...
No Matches
pconn.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9/* DEBUG: section 48 Persistent Connections */
10
11#include "squid.h"
12#include "base/IoManip.h"
13#include "base/PackableStream.h"
14#include "CachePeer.h"
15#include "comm.h"
16#include "comm/Connection.h"
17#include "comm/Read.h"
18#include "fd.h"
19#include "fde.h"
20#include "globals.h"
21#include "mgr/Registration.h"
22#include "neighbors.h"
23#include "pconn.h"
24#include "PeerPoolMgr.h"
25#include "SquidConfig.h"
26#include "Store.h"
27
28#define PCONN_FDS_SZ 8 /* pconn set size, increase for better memcache hit rate */
29
30//TODO: re-attach to MemPools. WAS: static Mem::Allocator *pconn_fds_pool = nullptr;
33
34/* ========== IdleConnList ============================================ */
35
36IdleConnList::IdleConnList(const char *aKey, PconnPool *thePool) :
37 capacity_(PCONN_FDS_SZ),
38 size_(0),
39 parent_(thePool)
40{
41 //Initialize hash_link members
42 key = xstrdup(aKey);
43 next = nullptr;
44
46
48
49// TODO: re-attach to MemPools. WAS: theList = (?? *)pconn_fds_pool->alloc();
50}
51
53{
54 if (parent_)
55 parent_->unlinkList(this);
56
57 if (size_) {
58 parent_ = nullptr; // prevent reentrant notifications and deletions
60 }
61
62 delete[] theList_;
63
64 xfree(key);
65}
66
73int
75{
76 for (auto right = size_; right > 0; --right) {
77 const auto index = right - 1;
78 if (conn->fd == theList_[index]->fd) {
79 debugs(48, 3, "found " << conn << " at index " << index);
80 return index;
81 }
82 }
83
84 debugs(48, 2, conn << " NOT FOUND!");
85 return -1;
86}
87
92bool
94{
95 if (index >= size_)
96 return false;
97 assert(size_ > 0);
98
99 // shuffle the remaining entries to fill the new gap.
100 for (; index < size_ - 1; ++index)
101 theList_[index] = theList_[index + 1];
102 theList_[--size_] = nullptr;
103
104 if (parent_) {
106 if (size_ == 0) {
107 debugs(48, 3, "deleting " << hashKeyStr(this));
108 delete this;
109 }
110 }
111
112 return true;
113}
114
115// almost a duplicate of removeFD. But drops multiple entries.
116void
117IdleConnList::closeN(const size_t n)
118{
119 if (n < 1) {
120 debugs(48, 2, "Nothing to do.");
121 return;
122 } else if (n >= size_) {
123 debugs(48, 2, "Closing all entries.");
124 while (size_ > 0) {
125 const Comm::ConnectionPointer conn = theList_[--size_];
126 theList_[size_] = nullptr;
127 clearHandlers(conn);
128 conn->close();
129 if (parent_)
131 }
132 } else { //if (n < size_)
133 debugs(48, 2, "Closing " << n << " of " << size_ << " entries.");
134
135 size_t index;
136 // ensure the first N entries are closed
137 for (index = 0; index < n; ++index) {
138 const Comm::ConnectionPointer conn = theList_[index];
139 theList_[index] = nullptr;
140 clearHandlers(conn);
141 conn->close();
142 if (parent_)
144 }
145 // shuffle the list N down.
146 for (index = 0; index < size_ - n; ++index) {
147 theList_[index] = theList_[index + n];
148 }
149 // ensure the last N entries are unset
150 while (index < size_) {
151 theList_[index] = nullptr;
152 ++index;
153 }
154 size_ -= n;
155 }
156
157 if (parent_ && size_ == 0) {
158 debugs(48, 3, "deleting " << hashKeyStr(this));
159 delete this;
160 }
161}
162
163void
165{
166 debugs(48, 3, "removing close handler for " << conn);
169}
170
171void
173{
174 if (size_ == capacity_) {
175 debugs(48, 3, "growing idle Connection array");
176 capacity_ <<= 1;
177 const Comm::ConnectionPointer *oldList = theList_;
179 for (size_t index = 0; index < size_; ++index)
180 theList_[index] = oldList[index];
181
182 delete[] oldList;
183 }
184
185 if (parent_)
187
188 theList_[size_] = conn;
189 ++size_;
190 AsyncCall::Pointer readCall = commCbCall(5,4, "IdleConnList::Read",
192 comm_read(conn, fakeReadBuf_, sizeof(fakeReadBuf_), readCall);
193 AsyncCall::Pointer timeoutCall = commCbCall(5,4, "IdleConnList::Timeout",
195 commSetConnTimeout(conn, conn->timeLeft(Config.Timeout.serverIdlePconn), timeoutCall);
196}
197
200bool
202{
203 const Comm::ConnectionPointer &conn = theList_[i];
204
205 // connection already closed. useless.
206 if (!Comm::IsConnOpen(conn))
207 return false;
208
209 // our connection early-read/close handler is scheduled to run already. unsafe
210 if (!COMMIO_FD_READCB(conn->fd)->active())
211 return false;
212
213 return true;
214}
215
218{
219 for (auto right = size_; right > 0; --right) {
220 const auto i = right - 1;
221
222 if (!isAvailable(i))
223 continue;
224
225 // our connection timeout handler is scheduled to run already. unsafe for now.
226 // TODO: cancel the pending timeout callback and allow reuse of the conn.
227 if (fd_table[theList_[i]->fd].timeoutHandler == nullptr)
228 continue;
229
230 // the cache_peer has been removed from the configuration
231 // TODO: remove all such connections at once during reconfiguration
232 if (theList_[i]->toGoneCachePeer())
233 continue;
234
235 // finally, a match. pop and return it.
237 clearHandlers(result);
238 /* may delete this */
239 removeAt(i);
240 return result;
241 }
242
244}
245
246/*
247 * XXX this routine isn't terribly efficient - if there's a pending
248 * read event (which signifies the fd will close in the next IO loop!)
249 * we ignore the FD and move onto the next one. This means, as an example,
250 * if we have a lot of FDs open to a very popular server and we get a bunch
251 * of requests JUST as they timeout (say, it shuts down) we'll be wasting
252 * quite a bit of CPU. Just keep it in mind.
253 */
256{
257 assert(size_);
258
259 // small optimization: do the constant bool tests only once.
260 const bool keyCheckAddr = !aKey->local.isAnyAddr();
261 const bool keyCheckPort = aKey->local.port() > 0;
262
263 for (auto right = size_; right > 0; --right) {
264 const auto i = right - 1;
265
266 if (!isAvailable(i))
267 continue;
268
269 // local end port is required, but do not match.
270 if (keyCheckPort && aKey->local.port() != theList_[i]->local.port())
271 continue;
272
273 // local address is required, but does not match.
274 if (keyCheckAddr && aKey->local.matchIPAddr(theList_[i]->local) != 0)
275 continue;
276
277 // our connection timeout handler is scheduled to run already. unsafe for now.
278 // TODO: cancel the pending timeout callback and allow reuse of the conn.
279 if (fd_table[theList_[i]->fd].timeoutHandler == nullptr)
280 continue;
281
282 // the cache_peer has been removed from the configuration
283 // TODO: remove all such connections at once during reconfiguration
284 if (theList_[i]->toGoneCachePeer())
285 continue;
286
287 // finally, a match. pop and return it.
289 clearHandlers(result);
290 /* may delete this */
291 removeAt(i);
292 return result;
293 }
294
296}
297
298/* might delete list */
299void
301{
302 const int index = findIndexOf(conn);
303 if (index >= 0) {
304 if (parent_)
305 parent_->notifyManager("idle conn closure");
306 clearHandlers(conn);
307 /* might delete this */
308 removeAt(index);
309 conn->close();
310 }
311}
312
313void
314IdleConnList::Read(const Comm::ConnectionPointer &conn, char *, size_t len, Comm::Flag flag, int, void *data)
315{
316 debugs(48, 3, len << " bytes from " << conn);
317
318 if (flag == Comm::ERR_CLOSING) {
319 debugs(48, 3, "Comm::ERR_CLOSING from " << conn);
320 /* Bail out on Comm::ERR_CLOSING - may happen when shutdown aborts our idle FD */
321 return;
322 }
323
324 IdleConnList *list = (IdleConnList *) data;
325 /* may delete list/data */
326 list->findAndClose(conn);
327}
328
329void
331{
332 debugs(48, 3, io.conn);
333 IdleConnList *list = static_cast<IdleConnList *>(io.data);
334 /* may delete list/data */
335 list->findAndClose(io.conn);
336}
337
338void
343
344/* ========== PconnPool PRIVATE FUNCTIONS ============================================ */
345
346const char *
347PconnPool::key(const Comm::ConnectionPointer &destLink, const char *domain)
348{
349 LOCAL_ARRAY(char, buf, SQUIDHOSTNAMELEN * 3 + 10);
350
351 destLink->remote.toUrl(buf, SQUIDHOSTNAMELEN * 3 + 10);
352
353 // when connecting through a cache_peer, ignore the final destination
354 if (destLink->getPeer())
355 domain = nullptr;
356
357 if (domain) {
358 const int used = strlen(buf);
359 snprintf(buf+used, SQUIDHOSTNAMELEN * 3 + 10-used, "/%s", domain);
360 }
361
362 debugs(48,6,"PconnPool::key(" << destLink << ", " << (domain?domain:"[no domain]") << ") is {" << buf << "}" );
363 return buf;
364}
365
366void
367PconnPool::dumpHist(std::ostream &yaml) const
368{
369 AtMostOnce heading(
370 " connection use histogram:\n"
371 " # requests per connection: closed connections that carried that many requests\n");
372
373 for (int i = 0; i < PCONN_HIST_SZ; ++i) {
374 if (hist[i] == 0)
375 continue;
376
377 yaml << heading <<
378 " " << i << ": " << hist[i] << "\n";
379 }
380}
381
382void
383PconnPool::dumpHash(std::ostream &yaml) const
384{
385 const auto hid = table;
386 hash_first(hid);
387 AtMostOnce title(" open connections list:\n");
388 for (auto *walker = hash_next(hid); walker; walker = hash_next(hid)) {
389 yaml << title <<
390 " \"" << static_cast<char *>(walker->key) << "\": " <<
391 static_cast<IdleConnList *>(walker)->count() <<
392 "\n";
393 }
394}
395
396void
397PconnPool::dump(std::ostream &yaml) const
398{
399 yaml << "pool " << descr << ":\n";
400 dumpHist(yaml);
401 dumpHash(yaml);
402}
403
404/* ========== PconnPool PUBLIC FUNCTIONS ============================================ */
405
406PconnPool::PconnPool(const char *aDescr, const CbcPointer<PeerPoolMgr> &aMgr):
407 table(nullptr), descr(aDescr),
408 mgr(aMgr),
409 theCount(0)
410{
411 int i;
412 table = hash_create((HASHCMP *) strcmp, 229, hash_string);
413
414 for (i = 0; i < PCONN_HIST_SZ; ++i)
415 hist[i] = 0;
416
418}
419
420static void
421DeleteIdleConnList(void *hashItem)
422{
423 delete static_cast<IdleConnList*>(hashItem);
424}
425
433
434void
435PconnPool::push(const Comm::ConnectionPointer &conn, const char *domain)
436{
437 if (fdUsageHigh()) {
438 debugs(48, 3, "Not many unused FDs");
439 conn->close();
440 return;
441 } else if (shutting_down) {
442 conn->close();
443 debugs(48, 3, "Squid is shutting down. Refusing to do anything");
444 return;
445 }
446 // TODO: also close used pconns if we exceed peer max-conn limit
447
448 const char *aKey = key(conn, domain);
449 IdleConnList *list = (IdleConnList *) hash_lookup(table, aKey);
450
451 if (list == nullptr) {
452 list = new IdleConnList(aKey, this);
453 debugs(48, 3, "new IdleConnList for {" << hashKeyStr(list) << "}" );
454 hash_join(table, list);
455 } else {
456 debugs(48, 3, "found IdleConnList for {" << hashKeyStr(list) << "}" );
457 }
458
459 list->push(conn);
461
462 LOCAL_ARRAY(char, desc, FD_DESC_SZ);
463 snprintf(desc, FD_DESC_SZ, "Idle server: %s", aKey);
464 fd_note(conn->fd, desc);
465 debugs(48, 3, "pushed " << conn << " for " << aKey);
466
467 // successful push notifications resume multi-connection opening sequence
468 notifyManager("push");
469}
470
472PconnPool::pop(const Comm::ConnectionPointer &dest, const char *domain, bool keepOpen)
473{
474 // always call shared pool first because we need to close an idle
475 // connection there if we have to use a standby connection.
476 if (const auto direct = popStored(dest, domain, keepOpen))
477 return direct;
478
479 // either there was no pconn to pop or this is not a retriable xaction
480 if (const auto peer = dest->getPeer()) {
481 if (peer->standby.pool)
482 return peer->standby.pool->popStored(dest, domain, true);
483 }
484
485 return nullptr;
486}
487
491PconnPool::popStored(const Comm::ConnectionPointer &dest, const char *domain, const bool keepOpen)
492{
493 const char * aKey = key(dest, domain);
494
495 IdleConnList *list = (IdleConnList *)hash_lookup(table, aKey);
496 if (list == nullptr) {
497 debugs(48, 3, "lookup for key {" << aKey << "} failed.");
498 // failure notifications resume standby conn creation after fdUsageHigh
499 notifyManager("pop lookup failure");
501 } else {
502 debugs(48, 3, "found " << hashKeyStr(list) <<
503 (keepOpen ? " to use" : " to kill"));
504 }
505
506 if (const auto popped = list->findUseable(dest)) { // may delete list
507 // successful pop notifications replenish standby connections pool
508 notifyManager("pop");
509
510 if (keepOpen)
511 return popped;
512
513 popped->close();
515 }
516
517 // failure notifications resume standby conn creation after fdUsageHigh
518 notifyManager("pop usability failure");
520}
521
522void
523PconnPool::notifyManager(const char *reason)
524{
525 if (mgr.valid())
527}
528
529void
531{
532 hash_table *hid = table;
533 hash_first(hid);
534
535 // close N connections, one per list, to treat all lists "fairly"
536 for (int i = 0; i < n && count(); ++i) {
537
538 hash_link *current = hash_next(hid);
539 if (!current) {
540 hash_first(hid);
541 current = hash_next(hid);
542 Must(current); // must have one because the count() was positive
543 }
544
545 // may delete current
546 static_cast<IdleConnList*>(current)->closeN(1);
547 }
548}
549
550void
552{
553 theCount -= list->count();
554 assert(theCount >= 0);
555 hash_remove_link(table, list);
556}
557
558void
560{
561 if (uses >= PCONN_HIST_SZ)
562 uses = PCONN_HIST_SZ - 1;
563
564 ++hist[uses];
565}
566
567/* ========== PconnModule ============================================ */
568
569/*
570 * This simple class exists only for the cache manager
571 */
572
577
580{
581 if (instance == nullptr)
582 instance = new PconnModule;
583
584 return instance;
585}
586
587void
589{
590 Mgr::RegisterAction("pconn",
591 "Persistent Connection Utilization Histograms",
594}
595
596void
598{
599 pools.insert(aPool);
600}
601
602void
604{
605 pools.erase(aPool);
606}
607
608void
609PconnModule::dump(std::ostream &yaml)
610{
611 for (const auto &p: pools)
612 p->dump(yaml);
613}
614
615void
621
CommCbFunPtrCallT< Dialer > * commCbCall(int debugSection, int debugLevel, const char *callName, const Dialer &dialer)
Definition CommCalls.h:312
#define COMMIO_FD_READCB(fd)
Definition IoCallback.h:75
void comm_read_cancel(int fd, IOCB *callback, void *data)
Definition Read.cc:181
void comm_read(const Comm::ConnectionPointer &conn, char *buf, int len, AsyncCall::Pointer &callback)
Definition Read.h:59
class SquidConfig Config
#define Must(condition)
#define assert(EX)
Definition assert.h:17
#define CBDATA_CLASS_INIT(type)
Definition cbdata.h:325
Cbc * valid() const
was set and is valid
Definition CbcPointer.h:41
Comm::ConnectionPointer conn
Definition CommCalls.h:80
time_t timeLeft(const time_t idleTimeout) const
CachePeer * getPeer() const
Ip::Address remote
Definition Connection.h:152
Ip::Address local
Definition Connection.h:149
IdleConnList(const char *key, PconnPool *parent)
Definition pconn.cc:36
PconnPool * parent_
Definition pconn.h:95
void findAndClose(const Comm::ConnectionPointer &conn)
Definition pconn.cc:300
Comm::ConnectionPointer pop()
get first conn which is not pending read fd.
Definition pconn.cc:217
void clearHandlers(const Comm::ConnectionPointer &conn)
Definition pconn.cc:164
bool isAvailable(int i) const
Definition pconn.cc:201
~IdleConnList() override
Definition pconn.cc:52
int count() const
Definition pconn.h:63
int findIndexOf(const Comm::ConnectionPointer &conn) const
Definition pconn.cc:74
Comm::ConnectionPointer findUseable(const Comm::ConnectionPointer &key)
Definition pconn.cc:255
Comm::ConnectionPointer * theList_
Definition pconn.h:83
void closeN(size_t count)
Definition pconn.cc:117
void endingShutdown() override
Definition pconn.cc:339
void push(const Comm::ConnectionPointer &conn)
Pass control of the connection to the idle list.
Definition pconn.cc:172
size_t size_
Definition pconn.h:88
static CTCB Timeout
Definition pconn.h:75
char fakeReadBuf_[4096]
Definition pconn.h:97
bool removeAt(size_t index)
Definition pconn.cc:93
static IOCB Read
Definition pconn.h:74
size_t capacity_
Number of entries theList can currently hold without re-allocating (capacity).
Definition pconn.h:86
int matchIPAddr(const Address &rhs) const
Definition Address.cc:715
bool isAnyAddr() const
Definition Address.cc:190
char * toUrl(char *buf, unsigned int len) const
Definition Address.cc:886
unsigned short port() const
Definition Address.cc:790
void remove(PconnPool *)
unregister and forget about this pool object
Definition pconn.cc:603
Pools pools
all live pools
Definition pconn.h:193
void add(PconnPool *)
Definition pconn.cc:597
PconnModule()
Definition pconn.cc:573
static PconnModule * instance
Definition pconn.h:195
void registerWithCacheManager(void)
Definition pconn.cc:588
void dump(std::ostream &yaml)
Definition pconn.cc:609
static PconnModule * GetInstance()
Definition pconn.cc:579
static void DumpWrapper(StoreEntry *e)
Definition pconn.cc:616
void closeN(int n)
closes any n connections, regardless of their destination
Definition pconn.cc:530
void dumpHist(std::ostream &) const
Definition pconn.cc:367
int hist[PCONN_HIST_SZ]
Definition pconn.h:159
PconnPool(const char *aDescription, const CbcPointer< PeerPoolMgr > &aMgr)
Definition pconn.cc:406
void notifyManager(const char *reason)
Definition pconn.cc:523
hash_table * table
Definition pconn.h:160
void unlinkList(IdleConnList *list)
Definition pconn.cc:551
void noteConnectionAdded()
Definition pconn.h:145
void dump(std::ostream &) const
Definition pconn.cc:397
Comm::ConnectionPointer popStored(const Comm::ConnectionPointer &dest, const char *domain, const bool keepOpen)
Definition pconn.cc:491
CbcPointer< PeerPoolMgr > mgr
optional pool manager (for notifications)
Definition pconn.h:162
~PconnPool()
Definition pconn.cc:426
int count() const
Definition pconn.h:144
const char * descr
Definition pconn.h:161
static const char * key(const Comm::ConnectionPointer &destLink, const char *domain)
Definition pconn.cc:347
void dumpHash(std::ostream &) const
Definition pconn.cc:383
void noteConnectionRemoved()
Definition pconn.h:146
void push(const Comm::ConnectionPointer &serverConn, const char *domain)
Definition pconn.cc:435
Comm::ConnectionPointer pop(const Comm::ConnectionPointer &dest, const char *domain, bool keepOpen)
Definition pconn.cc:472
void noteUses(int uses)
Definition pconn.cc:559
int theCount
the number of pooled connections
Definition pconn.h:163
static void Checkpoint(const Pointer &mgr, const char *reason)
struct SquidConfig::@77 Timeout
time_t serverIdlePconn
void commUnsetConnTimeout(const Comm::ConnectionPointer &conn)
Definition comm.cc:618
void commSetConnTimeout(const Comm::ConnectionPointer &conn, time_t timeout, AsyncCall::Pointer &callback)
Definition comm.cc:594
bool comm_has_incomplete_write(int fd)
Definition comm.cc:154
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define FD_DESC_SZ
Definition defines.h:32
void fd_note(int fd, const char *s)
Definition fd.cc:211
int fdUsageHigh(void)
Definition fd.cc:268
#define fd_table
Definition fde.h:189
int shutting_down
#define PCONN_HIST_SZ
Definition pconn.h:33
void hashFreeMemory(hash_table *)
Definition hash.cc:268
HASHHASH hash_string
Definition hash.h:45
void hashFreeItems(hash_table *, HASHFREE *)
Definition hash.cc:252
hash_link * hash_lookup(hash_table *, const void *)
Definition hash.cc:146
hash_table * hash_create(HASHCMP *, int, HASHHASH *)
Definition hash.cc:108
int HASHCMP(const void *, const void *)
Definition hash.h:13
hash_link * hash_next(hash_table *)
Definition hash.cc:188
void hash_first(hash_table *)
Definition hash.cc:172
void hash_join(hash_table *, hash_link *)
Definition hash.cc:131
void hash_remove_link(hash_table *, hash_link *)
Definition hash.cc:220
const char * hashKeyStr(const hash_link *)
Definition hash.cc:313
RefCount< Comm::Connection > ConnectionPointer
Definition forward.h:28
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition Connection.cc:27
Flag
Definition Flag.h:15
@ ERR_CLOSING
Definition Flag.h:24
void RegisterAction(char const *action, char const *desc, OBJH *handler, Protected, Atomic, Format)
#define xfree
#define xstrdup
static void DeleteIdleConnList(void *hashItem)
Definition pconn.cc:421
#define PCONN_FDS_SZ
Definition pconn.cc:28
#define SQUIDHOSTNAMELEN
Definition rfc2181.h:30
#define LOCAL_ARRAY(type, name, size)
Definition squid.h:62