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 
36 IdleConnList::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
59  closeN(size_);
60  }
61 
62  delete[] theList_;
63 
64  xfree(key);
65 }
66 
73 int
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 
92 bool
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.
116 void
117 IdleConnList::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 
163 void
165 {
166  debugs(48, 3, "removing close handler for " << conn);
167  comm_read_cancel(conn->fd, IdleConnList::Read, this);
168  commUnsetConnTimeout(conn);
169 }
170 
171 void
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 
200 bool
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 re-use 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.
236  Comm::ConnectionPointer result = theList_[i];
237  clearHandlers(result);
238  /* may delete this */
239  removeAt(i);
240  return result;
241  }
242 
243  return Comm::ConnectionPointer();
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 re-use 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.
288  Comm::ConnectionPointer result = theList_[i];
289  clearHandlers(result);
290  /* may delete this */
291  removeAt(i);
292  return result;
293  }
294 
295  return Comm::ConnectionPointer();
296 }
297 
298 /* might delete list */
299 void
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 
313 void
314 IdleConnList::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 
329 void
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 
338 void
340 {
341  closeN(size_);
342 }
343 
344 /* ========== PconnPool PRIVATE FUNCTIONS ============================================ */
345 
346 const char *
347 PconnPool::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 
366 void
367 PconnPool::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 
382 void
383 PconnPool::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 
396 void
397 PconnPool::dump(std::ostream &yaml) const
398 {
399  yaml << "pool " << descr << ":\n";
400  dumpHist(yaml);
401  dumpHash(yaml);
402 }
403 
404 /* ========== PconnPool PUBLIC FUNCTIONS ============================================ */
405 
406 PconnPool::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 
417  PconnModule::GetInstance()->add(this);
418 }
419 
420 static void
421 DeleteIdleConnList(void *hashItem)
422 {
423  delete static_cast<IdleConnList*>(hashItem);
424 }
425 
427 {
431  descr = nullptr;
432 }
433 
434 void
435 PconnPool::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 
472 PconnPool::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 
491 PconnPool::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");
500  return Comm::ConnectionPointer();
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();
514  return Comm::ConnectionPointer();
515  }
516 
517  // failure notifications resume standby conn creation after fdUsageHigh
518  notifyManager("pop usability failure");
519  return Comm::ConnectionPointer();
520 }
521 
522 void
523 PconnPool::notifyManager(const char *reason)
524 {
525  if (mgr.valid())
526  PeerPoolMgr::Checkpoint(mgr, reason);
527 }
528 
529 void
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 
550 void
552 {
553  theCount -= list->count();
554  assert(theCount >= 0);
555  hash_remove_link(table, list);
556 }
557 
558 void
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 
574 {
576 }
577 
578 PconnModule *
580 {
581  if (instance == nullptr)
582  instance = new PconnModule;
583 
584  return instance;
585 }
586 
587 void
589 {
590  Mgr::RegisterAction("pconn",
591  "Persistent Connection Utilization Histograms",
594 }
595 
596 void
598 {
599  pools.insert(aPool);
600 }
601 
602 void
604 {
605  pools.erase(aPool);
606 }
607 
608 void
609 PconnModule::dump(std::ostream &yaml)
610 {
611  for (const auto &p: pools)
612  p->dump(yaml);
613 }
614 
615 void
617 {
618  PackableStream yaml(*e);
620 }
621 
#define FD_DESC_SZ
Definition: defines.h:32
void closeN(int n)
closes any n connections, regardless of their destination
Definition: pconn.cc:530
void commUnsetConnTimeout(const Comm::ConnectionPointer &conn)
Definition: comm.cc:616
size_t size_
Definition: pconn.h:88
time_t serverIdlePconn
Definition: SquidConfig.h:120
#define LOCAL_ARRAY(type, name, size)
Definition: squid.h:62
~PconnPool()
Definition: pconn.cc:426
void fd_note(int fd, const char *s)
Definition: fd.cc:216
int findIndexOf(const Comm::ConnectionPointer &conn) const
Definition: pconn.cc:74
void closeN(size_t count)
Definition: pconn.cc:117
int fdUsageHigh(void)
Definition: fd.cc:273
static IOCB Read
Definition: pconn.h:74
Comm::ConnectionPointer pop(const Comm::ConnectionPointer &dest, const char *domain, bool keepOpen)
Definition: pconn.cc:472
bool isAnyAddr() const
Definition: Address.cc:190
#define COMMIO_FD_READCB(fd)
Definition: IoCallback.h:75
void dumpHash(std::ostream &) const
Definition: pconn.cc:383
HASHHASH hash_string
Definition: hash.h:45
void hash_remove_link(hash_table *, hash_link *)
Definition: hash.cc:220
void noteConnectionAdded()
Definition: pconn.h:145
#define xstrdup
static PconnModule * GetInstance()
Definition: pconn.cc:579
void noteUses(int uses)
Definition: pconn.cc:559
Comm::ConnectionPointer popStored(const Comm::ConnectionPointer &dest, const char *domain, const bool keepOpen)
Definition: pconn.cc:491
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:27
hash_link * hash_lookup(hash_table *, const void *)
Definition: hash.cc:146
int HASHCMP(const void *, const void *)
Definition: hash.h:13
char * toUrl(char *buf, unsigned int len) const
Definition: Address.cc:894
int matchIPAddr(const Address &rhs) const
Definition: Address.cc:723
@ ERR_CLOSING
Definition: Flag.h:24
void hashFreeMemory(hash_table *)
Definition: hash.cc:268
bool comm_has_incomplete_write(int fd)
Definition: comm.cc:152
~IdleConnList() override
Definition: pconn.cc:52
void hashFreeItems(hash_table *, HASHFREE *)
Definition: hash.cc:252
const char * hashKeyStr(const hash_link *)
Definition: hash.cc:313
void findAndClose(const Comm::ConnectionPointer &conn)
Definition: pconn.cc:300
static void DumpWrapper(StoreEntry *e)
Definition: pconn.cc:616
Comm::ConnectionPointer findUseable(const Comm::ConnectionPointer &key)
Definition: pconn.cc:255
void push(const Comm::ConnectionPointer &conn)
Pass control of the connection to the idle list.
Definition: pconn.cc:172
Pools pools
all live pools
Definition: pconn.h:193
Comm::ConnectionPointer pop()
get first conn which is not pending read fd.
Definition: pconn.cc:217
void comm_read(const Comm::ConnectionPointer &conn, char *buf, int len, AsyncCall::Pointer &callback)
Definition: Read.h:59
static void DeleteIdleConnList(void *hashItem)
Definition: pconn.cc:421
const char * descr
Definition: pconn.h:161
void notifyManager(const char *reason)
Definition: pconn.cc:523
void add(PconnPool *)
Definition: pconn.cc:597
time_t timeLeft(const time_t idleTimeout) const
Definition: Connection.cc:149
CbcPointer< PeerPoolMgr > mgr
optional pool manager (for notifications)
Definition: pconn.h:162
IdleConnList(const char *key, PconnPool *parent)
Definition: pconn.cc:36
void dump(std::ostream &yaml)
Definition: pconn.cc:609
void dumpHist(std::ostream &) const
Definition: pconn.cc:367
unsigned short port() const
Definition: Address.cc:798
Ip::Address local
Definition: Connection.h:149
CachePeer * getPeer() const
Definition: Connection.cc:121
void noteConnectionRemoved()
Definition: pconn.h:146
void push(const Comm::ConnectionPointer &serverConn, const char *domain)
Definition: pconn.cc:435
int count() const
Definition: pconn.h:63
Comm::ConnectionPointer conn
Definition: CommCalls.h:80
Ip::Address remote
Definition: Connection.h:152
CommCbFunPtrCallT< Dialer > * commCbCall(int debugSection, int debugLevel, const char *callName, const Dialer &dialer)
Definition: CommCalls.h:312
#define assert(EX)
Definition: assert.h:17
void registerWithCacheManager(void)
Definition: pconn.cc:588
int count() const
Definition: pconn.h:144
#define CBDATA_CLASS_INIT(type)
Definition: cbdata.h:325
void endingShutdown() override
Definition: pconn.cc:339
void hash_first(hash_table *)
Definition: hash.cc:172
PconnPool * parent_
Definition: pconn.h:95
#define xfree
void unlinkList(IdleConnList *list)
Definition: pconn.cc:551
void clearHandlers(const Comm::ConnectionPointer &conn)
Definition: pconn.cc:164
Flag
Definition: Flag.h:15
bool isAvailable(int i) const
Definition: pconn.cc:201
RefCount< Comm::Connection > ConnectionPointer
Definition: forward.h:26
#define fd_table
Definition: fde.h:189
void remove(PconnPool *)
unregister and forget about this pool object
Definition: pconn.cc:603
PconnPool(const char *aDescription, const CbcPointer< PeerPoolMgr > &aMgr)
Definition: pconn.cc:406
int theCount
the number of pooled connections
Definition: pconn.h:163
size_t capacity_
Number of entries theList can currently hold without re-allocating (capacity).
Definition: pconn.h:86
int hist[PCONN_HIST_SZ]
Definition: pconn.h:159
void commSetConnTimeout(const Comm::ConnectionPointer &conn, time_t timeout, AsyncCall::Pointer &callback)
Definition: comm.cc:592
Comm::ConnectionPointer * theList_
Definition: pconn.h:83
static CTCB Timeout
Definition: pconn.h:75
struct SquidConfig::@84 Timeout
hash_table * hash_create(HASHCMP *, int, HASHHASH *)
Definition: hash.cc:108
bool removeAt(size_t index)
Definition: pconn.cc:93
void RegisterAction(char const *action, char const *desc, OBJH *handler, Protected, Atomic, Format)
Definition: Registration.cc:54
#define PCONN_HIST_SZ
Definition: pconn.h:33
#define Must(condition)
Definition: TextException.h:75
PconnModule()
Definition: pconn.cc:573
int shutting_down
void comm_read_cancel(int fd, IOCB *callback, void *data)
Definition: Read.cc:181
hash_table * table
Definition: pconn.h:160
static PconnModule * instance
Definition: pconn.h:195
static const char * key(const Comm::ConnectionPointer &destLink, const char *domain)
Definition: pconn.cc:347
#define PCONN_FDS_SZ
Definition: pconn.cc:28
static void Checkpoint(const Pointer &mgr, const char *reason)
Definition: PeerPoolMgr.cc:232
hash_link * hash_next(hash_table *)
Definition: hash.cc:188
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
void dump(std::ostream &) const
Definition: pconn.cc:397
void hash_join(hash_table *, hash_link *)
Definition: hash.cc:131
#define SQUIDHOSTNAMELEN
Definition: rfc2181.h:30
char fakeReadBuf_[4096]
Definition: pconn.h:97
class SquidConfig Config
Definition: SquidConfig.cc:12

 

Introduction

Documentation

Support

Miscellaneous