]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/pconn.cc
2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 48 Persistent Connections */
12 #include "CachePeer.h"
14 #include "comm/Connection.h"
15 #include "comm/Read.h"
19 #include "mgr/Registration.h"
20 #include "neighbors.h"
22 #include "PeerPoolMgr.h"
23 #include "SquidConfig.h"
26 #define PCONN_FDS_SZ 8 /* pconn set size, increase for better memcache hit rate */
28 //TODO: re-attach to MemPools. WAS: static Mem::Allocator *pconn_fds_pool = nullptr;
29 PconnModule
* PconnModule::instance
= nullptr;
30 CBDATA_CLASS_INIT(IdleConnList
);
32 /* ========== IdleConnList ============================================ */
34 IdleConnList::IdleConnList(const char *aKey
, PconnPool
*thePool
) :
35 capacity_(PCONN_FDS_SZ
),
39 //Initialize hash_link members
43 theList_
= new Comm::ConnectionPointer
[capacity_
];
47 // TODO: re-attach to MemPools. WAS: theList = (?? *)pconn_fds_pool->alloc();
50 IdleConnList::~IdleConnList()
53 parent_
->unlinkList(this);
56 parent_
= nullptr; // prevent reentrant notifications and deletions
65 /** Search the list. Matches by FD socket number.
66 * Performed from the end of list where newest entries are.
68 * \retval <0 The connection is not listed
69 * \retval >=0 The connection array index
72 IdleConnList::findIndexOf(const Comm::ConnectionPointer
&conn
) const
74 for (int index
= size_
- 1; index
>= 0; --index
) {
75 if (conn
->fd
== theList_
[index
]->fd
) {
76 debugs(48, 3, "found " << conn
<< " at index " << index
);
81 debugs(48, 2, conn
<< " NOT FOUND!");
85 /** Remove the entry at specified index.
86 * May perform a shuffle of list entries to fill the gap.
87 * \retval false The index is not an in-use entry.
90 IdleConnList::removeAt(int index
)
92 if (index
< 0 || index
>= size_
)
95 // shuffle the remaining entries to fill the new gap.
96 for (; index
< size_
- 1; ++index
)
97 theList_
[index
] = theList_
[index
+ 1];
98 theList_
[--size_
] = nullptr;
101 parent_
->noteConnectionRemoved();
103 debugs(48, 3, "deleting " << hashKeyStr(this));
111 // almost a duplicate of removeFD. But drops multiple entries.
113 IdleConnList::closeN(size_t n
)
116 debugs(48, 2, "Nothing to do.");
118 } else if (n
>= (size_t)size_
) {
119 debugs(48, 2, "Closing all entries.");
121 const Comm::ConnectionPointer conn
= theList_
[--size_
];
122 theList_
[size_
] = nullptr;
126 parent_
->noteConnectionRemoved();
128 } else { //if (n < size_)
129 debugs(48, 2, "Closing " << n
<< " of " << size_
<< " entries.");
132 // ensure the first N entries are closed
133 for (index
= 0; index
< n
; ++index
) {
134 const Comm::ConnectionPointer conn
= theList_
[index
];
135 theList_
[index
] = nullptr;
139 parent_
->noteConnectionRemoved();
141 // shuffle the list N down.
142 for (index
= 0; index
< (size_t)size_
- n
; ++index
) {
143 theList_
[index
] = theList_
[index
+ n
];
145 // ensure the last N entries are unset
146 while (index
< ((size_t)size_
)) {
147 theList_
[index
] = nullptr;
153 if (parent_
&& size_
== 0) {
154 debugs(48, 3, "deleting " << hashKeyStr(this));
160 IdleConnList::clearHandlers(const Comm::ConnectionPointer
&conn
)
162 debugs(48, 3, "removing close handler for " << conn
);
163 comm_read_cancel(conn
->fd
, IdleConnList::Read
, this);
164 commUnsetConnTimeout(conn
);
168 IdleConnList::push(const Comm::ConnectionPointer
&conn
)
170 if (size_
== capacity_
) {
171 debugs(48, 3, "growing idle Connection array");
173 const Comm::ConnectionPointer
*oldList
= theList_
;
174 theList_
= new Comm::ConnectionPointer
[capacity_
];
175 for (int index
= 0; index
< size_
; ++index
)
176 theList_
[index
] = oldList
[index
];
182 parent_
->noteConnectionAdded();
184 theList_
[size_
] = conn
;
186 AsyncCall::Pointer readCall
= commCbCall(5,4, "IdleConnList::Read",
187 CommIoCbPtrFun(IdleConnList::Read
, this));
188 comm_read(conn
, fakeReadBuf_
, sizeof(fakeReadBuf_
), readCall
);
189 AsyncCall::Pointer timeoutCall
= commCbCall(5,4, "IdleConnList::Timeout",
190 CommTimeoutCbPtrFun(IdleConnList::Timeout
, this));
191 commSetConnTimeout(conn
, conn
->timeLeft(Config
.Timeout
.serverIdlePconn
), timeoutCall
);
194 /// Determine whether an entry in the idle list is available for use.
195 /// Returns false if the entry is unset, closed or closing.
197 IdleConnList::isAvailable(int i
) const
199 const Comm::ConnectionPointer
&conn
= theList_
[i
];
201 // connection already closed. useless.
202 if (!Comm::IsConnOpen(conn
))
205 // our connection early-read/close handler is scheduled to run already. unsafe
206 if (!COMMIO_FD_READCB(conn
->fd
)->active())
212 Comm::ConnectionPointer
215 for (int i
=size_
-1; i
>=0; --i
) {
220 // our connection timeout handler is scheduled to run already. unsafe for now.
221 // TODO: cancel the pending timeout callback and allow re-use of the conn.
222 if (fd_table
[theList_
[i
]->fd
].timeoutHandler
== nullptr)
225 // finally, a match. pop and return it.
226 Comm::ConnectionPointer result
= theList_
[i
];
227 clearHandlers(result
);
228 /* may delete this */
233 return Comm::ConnectionPointer();
237 * XXX this routine isn't terribly efficient - if there's a pending
238 * read event (which signifies the fd will close in the next IO loop!)
239 * we ignore the FD and move onto the next one. This means, as an example,
240 * if we have a lot of FDs open to a very popular server and we get a bunch
241 * of requests JUST as they timeout (say, it shuts down) we'll be wasting
242 * quite a bit of CPU. Just keep it in mind.
244 Comm::ConnectionPointer
245 IdleConnList::findUseable(const Comm::ConnectionPointer
&aKey
)
249 // small optimization: do the constant bool tests only once.
250 const bool keyCheckAddr
= !aKey
->local
.isAnyAddr();
251 const bool keyCheckPort
= aKey
->local
.port() > 0;
253 for (int i
=size_
-1; i
>=0; --i
) {
258 // local end port is required, but do not match.
259 if (keyCheckPort
&& aKey
->local
.port() != theList_
[i
]->local
.port())
262 // local address is required, but does not match.
263 if (keyCheckAddr
&& aKey
->local
.matchIPAddr(theList_
[i
]->local
) != 0)
266 // our connection timeout handler is scheduled to run already. unsafe for now.
267 // TODO: cancel the pending timeout callback and allow re-use of the conn.
268 if (fd_table
[theList_
[i
]->fd
].timeoutHandler
== nullptr)
271 // finally, a match. pop and return it.
272 Comm::ConnectionPointer result
= theList_
[i
];
273 clearHandlers(result
);
274 /* may delete this */
279 return Comm::ConnectionPointer();
282 /* might delete list */
284 IdleConnList::findAndClose(const Comm::ConnectionPointer
&conn
)
286 const int index
= findIndexOf(conn
);
289 parent_
->notifyManager("idle conn closure");
291 /* might delete this */
298 IdleConnList::Read(const Comm::ConnectionPointer
&conn
, char *, size_t len
, Comm::Flag flag
, int, void *data
)
300 debugs(48, 3, len
<< " bytes from " << conn
);
302 if (flag
== Comm::ERR_CLOSING
) {
303 debugs(48, 3, "Comm::ERR_CLOSING from " << conn
);
304 /* Bail out on Comm::ERR_CLOSING - may happen when shutdown aborts our idle FD */
308 IdleConnList
*list
= (IdleConnList
*) data
;
309 /* may delete list/data */
310 list
->findAndClose(conn
);
314 IdleConnList::Timeout(const CommTimeoutCbParams
&io
)
316 debugs(48, 3, io
.conn
);
317 IdleConnList
*list
= static_cast<IdleConnList
*>(io
.data
);
318 /* may delete list/data */
319 list
->findAndClose(io
.conn
);
323 IdleConnList::endingShutdown()
328 /* ========== PconnPool PRIVATE FUNCTIONS ============================================ */
331 PconnPool::key(const Comm::ConnectionPointer
&destLink
, const char *domain
)
333 LOCAL_ARRAY(char, buf
, SQUIDHOSTNAMELEN
* 3 + 10);
335 destLink
->remote
.toUrl(buf
, SQUIDHOSTNAMELEN
* 3 + 10);
337 // when connecting through a cache_peer, ignore the final destination
338 if (destLink
->getPeer())
342 const int used
= strlen(buf
);
343 snprintf(buf
+used
, SQUIDHOSTNAMELEN
* 3 + 10-used
, "/%s", domain
);
346 debugs(48,6,"PconnPool::key(" << destLink
<< ", " << (domain
?domain
:"[no domain]") << ") is {" << buf
<< "}" );
351 PconnPool::dumpHist(StoreEntry
* e
) const
354 "%s persistent connection counts:\n"
356 "\t Requests\t Connection Count\n"
357 "\t --------\t ----------------\n",
360 for (int i
= 0; i
< PCONN_HIST_SZ
; ++i
) {
364 storeAppendPrintf(e
, "\t%d\t%d\n", i
, hist
[i
]);
369 PconnPool::dumpHash(StoreEntry
*e
) const
371 hash_table
*hid
= table
;
375 for (hash_link
*walker
= hash_next(hid
); walker
; walker
= hash_next(hid
)) {
376 storeAppendPrintf(e
, "\t item %d:\t%s\n", i
, (char *)(walker
->key
));
381 /* ========== PconnPool PUBLIC FUNCTIONS ============================================ */
383 PconnPool::PconnPool(const char *aDescr
, const CbcPointer
<PeerPoolMgr
> &aMgr
):
384 table(nullptr), descr(aDescr
),
389 table
= hash_create((HASHCMP
*) strcmp
, 229, hash_string
);
391 for (i
= 0; i
< PCONN_HIST_SZ
; ++i
)
394 PconnModule::GetInstance()->add(this);
398 DeleteIdleConnList(void *hashItem
)
400 delete static_cast<IdleConnList
*>(hashItem
);
403 PconnPool::~PconnPool()
405 PconnModule::GetInstance()->remove(this);
406 hashFreeItems(table
, &DeleteIdleConnList
);
407 hashFreeMemory(table
);
412 PconnPool::push(const Comm::ConnectionPointer
&conn
, const char *domain
)
415 debugs(48, 3, "Not many unused FDs");
418 } else if (shutting_down
) {
420 debugs(48, 3, "Squid is shutting down. Refusing to do anything");
423 // TODO: also close used pconns if we exceed peer max-conn limit
425 const char *aKey
= key(conn
, domain
);
426 IdleConnList
*list
= (IdleConnList
*) hash_lookup(table
, aKey
);
428 if (list
== nullptr) {
429 list
= new IdleConnList(aKey
, this);
430 debugs(48, 3, "new IdleConnList for {" << hashKeyStr(list
) << "}" );
431 hash_join(table
, list
);
433 debugs(48, 3, "found IdleConnList for {" << hashKeyStr(list
) << "}" );
437 assert(!comm_has_incomplete_write(conn
->fd
));
439 LOCAL_ARRAY(char, desc
, FD_DESC_SZ
);
440 snprintf(desc
, FD_DESC_SZ
, "Idle server: %s", aKey
);
441 fd_note(conn
->fd
, desc
);
442 debugs(48, 3, "pushed " << conn
<< " for " << aKey
);
444 // successful push notifications resume multi-connection opening sequence
445 notifyManager("push");
448 Comm::ConnectionPointer
449 PconnPool::pop(const Comm::ConnectionPointer
&dest
, const char *domain
, bool keepOpen
)
451 // always call shared pool first because we need to close an idle
452 // connection there if we have to use a standby connection.
453 if (const auto direct
= popStored(dest
, domain
, keepOpen
))
456 // either there was no pconn to pop or this is not a retriable xaction
457 if (const auto peer
= dest
->getPeer()) {
458 if (peer
->standby
.pool
)
459 return peer
->standby
.pool
->popStored(dest
, domain
, true);
465 /// implements pop() API while disregarding peer standby pools
466 /// \returns an open connection or nil
467 Comm::ConnectionPointer
468 PconnPool::popStored(const Comm::ConnectionPointer
&dest
, const char *domain
, const bool keepOpen
)
470 const char * aKey
= key(dest
, domain
);
472 IdleConnList
*list
= (IdleConnList
*)hash_lookup(table
, aKey
);
473 if (list
== nullptr) {
474 debugs(48, 3, "lookup for key {" << aKey
<< "} failed.");
475 // failure notifications resume standby conn creation after fdUsageHigh
476 notifyManager("pop lookup failure");
477 return Comm::ConnectionPointer();
479 debugs(48, 3, "found " << hashKeyStr(list
) <<
480 (keepOpen
? " to use" : " to kill"));
483 if (const auto popped
= list
->findUseable(dest
)) { // may delete list
484 // successful pop notifications replenish standby connections pool
485 notifyManager("pop");
491 return Comm::ConnectionPointer();
494 // failure notifications resume standby conn creation after fdUsageHigh
495 notifyManager("pop usability failure");
496 return Comm::ConnectionPointer();
500 PconnPool::notifyManager(const char *reason
)
503 PeerPoolMgr::Checkpoint(mgr
, reason
);
507 PconnPool::closeN(int n
)
509 hash_table
*hid
= table
;
512 // close N connections, one per list, to treat all lists "fairly"
513 for (int i
= 0; i
< n
&& count(); ++i
) {
515 hash_link
*current
= hash_next(hid
);
518 current
= hash_next(hid
);
519 Must(current
); // must have one because the count() was positive
522 // may delete current
523 static_cast<IdleConnList
*>(current
)->closeN(1);
528 PconnPool::unlinkList(IdleConnList
*list
)
530 theCount
-= list
->count();
531 assert(theCount
>= 0);
532 hash_remove_link(table
, list
);
536 PconnPool::noteUses(int uses
)
538 if (uses
>= PCONN_HIST_SZ
)
539 uses
= PCONN_HIST_SZ
- 1;
544 /* ========== PconnModule ============================================ */
547 * This simple class exists only for the cache manager
550 PconnModule::PconnModule(): pools()
552 registerWithCacheManager();
556 PconnModule::GetInstance()
558 if (instance
== nullptr)
559 instance
= new PconnModule
;
565 PconnModule::registerWithCacheManager(void)
567 Mgr::RegisterAction("pconn",
568 "Persistent Connection Utilization Histograms",
573 PconnModule::add(PconnPool
*aPool
)
579 PconnModule::remove(PconnPool
*aPool
)
585 PconnModule::dump(StoreEntry
*e
)
587 typedef Pools::const_iterator PCI
;
588 int i
= 0; // TODO: Why number pools if they all have names?
589 for (PCI p
= pools
.begin(); p
!= pools
.end(); ++p
, ++i
) {
590 // TODO: Let each pool dump itself the way it wants to.
591 storeAppendPrintf(e
, "\n Pool %d Stats\n", i
);
593 storeAppendPrintf(e
, "\n Pool %d Hash Table\n",i
);
599 PconnModule::DumpWrapper(StoreEntry
*e
)
601 PconnModule::GetInstance()->dump(e
);