]> git.ipfire.org Git - thirdparty/squid.git/blob - src/pconn.cc
Merge from trunk
[thirdparty/squid.git] / src / pconn.cc
1 /*
2 * $Id$
3 *
4 * DEBUG: section 48 Persistent Connections
5 * AUTHOR: Duane Wessels
6 *
7 * SQUID Web Proxy Cache http://www.squid-cache.org/
8 * ----------------------------------------------------------
9 *
10 * Squid is the result of efforts by numerous individuals from
11 * the Internet community; see the CONTRIBUTORS file for full
12 * details. Many organizations have provided support for Squid's
13 * development; see the SPONSORS file for full details. Squid is
14 * Copyrighted (C) 2001 by the Regents of the University of
15 * California; see the COPYRIGHT file for full details. Squid
16 * incorporates software developed and/or copyrighted by other
17 * sources; see the CREDITS file for full details.
18 *
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
23 *
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
32 *
33 */
34
35 #include "squid.h"
36 #include "CacheManager.h"
37 #include "comm.h"
38 #include "comm/Connection.h"
39 #include "fde.h"
40 #include "pconn.h"
41 #include "Store.h"
42
43 #define PCONN_FDS_SZ 8 /* pconn set size, increase for better memcache hit rate */
44
45 //TODO: re-attach to MemPools. WAS: static MemAllocator *pconn_fds_pool = NULL;
46 PconnModule * PconnModule::instance = NULL;
47 CBDATA_CLASS_INIT(IdleConnList);
48
49 /* ========== IdleConnList ============================================ */
50
51 IdleConnList::IdleConnList(const char *key, PconnPool *thePool) :
52 capacity_(PCONN_FDS_SZ),
53 size_(0),
54 parent_(thePool)
55 {
56 hash.key = xstrdup(key);
57 theList_ = new Comm::ConnectionPointer[capacity_];
58 // TODO: re-attach to MemPools. WAS: theList = (?? *)pconn_fds_pool->alloc();
59 }
60
61 IdleConnList::~IdleConnList()
62 {
63 parent_->unlinkList(this);
64
65 /* TODO: re-attach to MemPools.
66 if (capacity_ == PCONN_FDS_SZ)
67 pconn_fds_pool->freeOne(theList_);
68 else
69 */
70 delete[] theList_;
71
72 xfree(hash.key);
73 }
74
75 /** Search the list. Matches by FD socket number.
76 * Performed from the end of list where newest entries are.
77 *
78 * \retval <0 The connection is not listed
79 * \retval >=0 The connection array index
80 */
81 int
82 IdleConnList::findIndexOf(const Comm::ConnectionPointer &conn) const
83 {
84 for (int index = size_ - 1; index >= 0; --index) {
85 if (conn->fd == theList_[index]->fd) {
86 debugs(48, 3, HERE << "found " << conn << " at index " << index);
87 return index;
88 }
89 }
90
91 debugs(48, 2, HERE << conn << " NOT FOUND!");
92 return -1;
93 }
94
95 /** Remove the entry at specified index.
96 * \retval false The index is not an in-use entry.
97 */
98 bool
99 IdleConnList::removeAt(int index)
100 {
101 if (index < 0 || index >= size_)
102 return false;
103
104 // shuffle the remaining entries to fill the new gap.
105 for (; index < size_ - 1; index++)
106 theList_[index] = theList_[index + 1];
107 theList_[size_] = NULL;
108
109 if (--size_ == 0) {
110 debugs(48, 3, HERE << "deleting " << hashKeyStr(&hash));
111 delete this;
112 }
113 return true;
114 }
115
116 void
117 IdleConnList::clearHandlers(const Comm::ConnectionPointer &conn)
118 {
119 comm_read_cancel(conn->fd, IdleConnList::Read, this);
120 commSetTimeout(conn->fd, -1, NULL, NULL);
121 }
122
123 void
124 IdleConnList::push(const Comm::ConnectionPointer &conn)
125 {
126 if (size_ == capacity_) {
127 debugs(48, 3, HERE << "growing idle Connection array");
128 capacity_ <<= 1;
129 const Comm::ConnectionPointer *oldList = theList_;
130 theList_ = new Comm::ConnectionPointer[capacity_];
131 for (int index = 0; index < size_; index++)
132 theList_[index] = oldList[index];
133
134 /* TODO: re-attach to MemPools.
135 if (size_ == PCONN_FDS_SZ)
136 pconn_fds_pool->freeOne(oldList);
137 else
138 */
139 delete[] oldList;
140 }
141
142 theList_[size_++] = conn;
143 comm_read(conn, fakeReadBuf_, sizeof(fakeReadBuf_), IdleConnList::Read, this);
144 commSetTimeout(conn->fd, Config.Timeout.pconn, IdleConnList::Timeout, this);
145 }
146
147 /*
148 * XXX this routine isn't terribly efficient - if there's a pending
149 * read event (which signifies the fd will close in the next IO loop!)
150 * we ignore the FD and move onto the next one. This means, as an example,
151 * if we have a lot of FDs open to a very popular server and we get a bunch
152 * of requests JUST as they timeout (say, it shuts down) we'll be wasting
153 * quite a bit of CPU. Just keep it in mind.
154 */
155 Comm::ConnectionPointer
156 IdleConnList::findUseable(const Comm::ConnectionPointer &key)
157 {
158 assert(size_);
159
160 for (int i=size_-1; i>=0; i--) {
161
162 // callback pending indicates that remote end of the conn has just closed.
163 if (comm_has_pending_read_callback(theList_[i]->fd))
164 continue;
165
166 // local end port is required, but dont match.
167 if (key->local.GetPort() > 0 && key->local.GetPort() != theList_[i]->local.GetPort())
168 continue;
169
170 // local address is required, but does not match.
171 if (!key->local.IsAnyAddr() && key->local.matchIPAddr(theList_[i]->local) != 0)
172 continue;
173
174 // finally, a match. pop and return it.
175 Comm::ConnectionPointer result = theList_[i];
176 /* may delete this */
177 removeAt(i);
178 return result;
179 }
180
181 return Comm::ConnectionPointer();
182 }
183
184 void
185 IdleConnList::Read(const Comm::ConnectionPointer &conn, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
186 {
187 debugs(48, 3, HERE << len << " bytes from " << conn);
188
189 if (flag == COMM_ERR_CLOSING) {
190 /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
191 return;
192 }
193
194 IdleConnList *list = (IdleConnList *) data;
195 int index = list->findIndexOf(conn);
196 if (index >= 0) {
197 /* might delete list */
198 list->removeAt(index);
199 conn->close();
200 }
201 }
202
203 void
204 IdleConnList::Timeout(int fd, void *data)
205 {
206 debugs(48, 3, HERE << "FD " << fd);
207 IdleConnList *list = (IdleConnList *) data;
208 Comm::ConnectionPointer temp = new Comm::Connection; // XXX: transition. make timeouts pass conn in
209 temp->fd = fd;
210 int index = list->findIndexOf(temp);
211 if (index >= 0) {
212 /* might delete list */
213 list->removeAt(index);
214 temp->close();
215 } else
216 temp->fd = -1; // XXX: transition. prevent temp erasure double-closing FD until timeout CB passess conn in.
217 }
218
219 /* ========== PconnPool PRIVATE FUNCTIONS ============================================ */
220
221 const char *
222 PconnPool::key(const Comm::ConnectionPointer &destLink, const char *domain)
223 {
224 LOCAL_ARRAY(char, buf, SQUIDHOSTNAMELEN * 3 + 10);
225
226 destLink->remote.ToURL(buf, SQUIDHOSTNAMELEN * 3 + 10);
227 if (domain) {
228 const int used = strlen(buf);
229 snprintf(buf+used, SQUIDHOSTNAMELEN * 3 + 10-used, "/%s", domain);
230 }
231
232 debugs(48,6,"PconnPool::key(" << destLink << ", " << (domain?domain:"[no domain]") << ") is {" << buf << "}" );
233 return buf;
234 }
235
236 void
237 PconnPool::dumpHist(StoreEntry * e) const
238 {
239 storeAppendPrintf(e,
240 "%s persistent connection counts:\n"
241 "\n"
242 "\treq/\n"
243 "\tconn count\n"
244 "\t---- ---------\n",
245 descr);
246
247 for (int i = 0; i < PCONN_HIST_SZ; i++) {
248 if (hist[i] == 0)
249 continue;
250
251 storeAppendPrintf(e, "\t%4d %9d\n", i, hist[i]);
252 }
253 }
254
255 void
256 PconnPool::dumpHash(StoreEntry *e) const
257 {
258 hash_table *hid = table;
259 hash_first(hid);
260
261 int i = 0;
262 for (hash_link *walker = hid->next; walker; walker = hash_next(hid)) {
263 storeAppendPrintf(e, "\t item %5d: %s\n", i++, (char *)(walker->key));
264 }
265 }
266
267 /* ========== PconnPool PUBLIC FUNCTIONS ============================================ */
268
269 PconnPool::PconnPool(const char *aDescr) : table(NULL), descr(aDescr)
270 {
271 table = hash_create((HASHCMP *) strcmp, 229, hash_string);
272
273 for (int i = 0; i < PCONN_HIST_SZ; i++)
274 hist[i] = 0;
275
276 PconnModule::GetInstance()->add(this);
277 }
278
279 PconnPool::~PconnPool()
280 {
281 descr = NULL;
282 hashFreeMemory(table);
283 }
284
285 void
286 PconnPool::push(const Comm::ConnectionPointer &conn, const char *domain)
287 {
288 if (fdUsageHigh()) {
289 debugs(48, 3, HERE << "Not many unused FDs");
290 conn->close();
291 return;
292 } else if (shutting_down) {
293 conn->close();
294 debugs(48, 3, HERE << "Squid is shutting down. Refusing to do anything");
295 return;
296 }
297
298 const char *aKey = key(conn, domain);
299 IdleConnList *list = (IdleConnList *) hash_lookup(table, aKey);
300
301 if (list == NULL) {
302 list = new IdleConnList(aKey, this);
303 debugs(48, 3, HERE << "new IdleConnList for {" << hashKeyStr(&list->hash) << "}" );
304 hash_join(table, &list->hash);
305 } else {
306 debugs(48, 3, HERE << "found IdleConnList for {" << hashKeyStr(&list->hash) << "}" );
307 }
308
309 list->push(conn);
310 assert(!comm_has_incomplete_write(conn->fd));
311
312 LOCAL_ARRAY(char, desc, FD_DESC_SZ);
313 snprintf(desc, FD_DESC_SZ, "Idle: %s", aKey);
314 fd_note(conn->fd, desc);
315 debugs(48, 3, HERE << "pushed " << conn << " for " << aKey);
316 }
317
318 Comm::ConnectionPointer
319 PconnPool::pop(const Comm::ConnectionPointer &destLink, const char *domain, bool isRetriable)
320 {
321 const char * aKey = key(destLink, domain);
322
323 IdleConnList *list = (IdleConnList *)hash_lookup(table, aKey);
324 if (list == NULL) {
325 debugs(48, 3, HERE << "lookup for key {" << aKey << "} failed.");
326 return Comm::ConnectionPointer();
327 } else {
328 debugs(48, 3, HERE << "found " << hashKeyStr(&list->hash) << (isRetriable?"(to use)":"(to kill)") );
329 }
330
331 /* may delete list */
332 Comm::ConnectionPointer temp = list->findUseable(destLink);
333 if (Comm::IsConnOpen(temp) && !isRetriable)
334 temp->close();
335
336 return temp;
337 }
338
339 void
340 PconnPool::unlinkList(IdleConnList *list) const
341 {
342 hash_remove_link(table, &list->hash);
343 }
344
345 void
346 PconnPool::count(int uses)
347 {
348 if (uses >= PCONN_HIST_SZ)
349 uses = PCONN_HIST_SZ - 1;
350
351 hist[uses]++;
352 }
353
354 /* ========== PconnModule ============================================ */
355
356 /*
357 * This simple class exists only for the cache manager
358 */
359
360 PconnModule::PconnModule() : pools(NULL), poolCount(0)
361 {
362 pools = (PconnPool **) xcalloc(MAX_NUM_PCONN_POOLS, sizeof(*pools));
363 //TODO: re-link to MemPools. WAS: pconn_fds_pool = memPoolCreate("pconn_fds", PCONN_FDS_SZ * sizeof(int));
364 debugs(48, 0, "persistent connection module initialized");
365 registerWithCacheManager();
366 }
367
368 PconnModule *
369 PconnModule::GetInstance()
370 {
371 if (instance == NULL)
372 instance = new PconnModule;
373
374 return instance;
375 }
376
377 void
378 PconnModule::registerWithCacheManager(void)
379 {
380 CacheManager::GetInstance()->
381 registerAction("pconn",
382 "Persistent Connection Utilization Histograms",
383 DumpWrapper, 0, 1);
384 }
385
386 void
387
388 PconnModule::add(PconnPool *aPool)
389 {
390 assert(poolCount < MAX_NUM_PCONN_POOLS);
391 *(pools+poolCount) = aPool;
392 poolCount++;
393 }
394
395 void
396 PconnModule::dump(StoreEntry *e)
397 {
398 for (int i = 0; i < poolCount; i++) {
399 storeAppendPrintf(e, "\n Pool %d Stats\n", i);
400 (*(pools+i))->dumpHist(e);
401 storeAppendPrintf(e, "\n Pool %d Hash Table\n",i);
402 (*(pools+i))->dumpHash(e);
403 }
404 }
405
406 void
407 PconnModule::DumpWrapper(StoreEntry *e)
408 {
409 PconnModule::GetInstance()->dump(e);
410 }